This commit is contained in:
Matthieu 2018-06-12 13:57:30 +02:00
commit a033a9d74e
48 changed files with 978 additions and 183 deletions

296
Cargo.lock generated
View File

@ -1,10 +1,14 @@
[[package]]
name = "activitystreams"
name = "activitypub"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"activitystreams-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"activitystreams-types 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"activitystreams-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -29,7 +33,7 @@ dependencies = [
[[package]]
name = "activitystreams-types"
version = "0.1.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"activitystreams-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -54,6 +58,27 @@ dependencies = [
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ammonia"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"html5ever 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "antidote"
version = "1.0.0"
@ -72,6 +97,16 @@ dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace"
version = "0.3.6"
@ -196,6 +231,36 @@ dependencies = [
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clap"
version = "2.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "comrak"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
"entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"pest_derive 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"typed-arena 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cookie"
version = "0.9.2"
@ -350,6 +415,11 @@ dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "entities"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "error-chain"
version = "0.10.0"
@ -412,6 +482,15 @@ name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures"
version = "0.1.21"
@ -457,6 +536,19 @@ name = "hex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "html5ever"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"markup5ever 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "httparse"
version = "1.2.4"
@ -623,6 +715,31 @@ dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maplit"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "markup5ever"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "matches"
version = "0.1.6"
@ -729,6 +846,14 @@ dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.12"
@ -824,6 +949,21 @@ name = "pest"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pest"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pest_derive"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf"
version = "0.7.22"
@ -868,14 +1008,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "plume"
version = "0.1.0"
dependencies = [
"activitystreams 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"activitystreams-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"activitystreams-types 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"activitypub 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ammonia 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"array_tool 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bcrypt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"comrak 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -903,6 +1042,11 @@ dependencies = [
"vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "0.2.3"
@ -995,6 +1139,14 @@ name = "redox_syscall"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.2.10"
@ -1256,6 +1408,42 @@ name = "state"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "string_cache"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "string_cache_codegen"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "string_cache_shared"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.11.11"
@ -1325,6 +1513,16 @@ dependencies = [
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tendril"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"utf-8 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tera"
version = "0.10.10"
@ -1343,6 +1541,24 @@ dependencies = [
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.5"
@ -1524,11 +1740,24 @@ name = "traitobject"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "twoway"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typeable"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "typed-arena"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "typenum"
version = "1.10.0"
@ -1573,6 +1802,11 @@ name = "unicode-segmentation"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.0.4"
@ -1583,6 +1817,11 @@ name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unidecode"
version = "0.3.0"
@ -1611,6 +1850,11 @@ dependencies = [
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf-8"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "utf8-ranges"
version = "1.0.0"
@ -1629,6 +1873,11 @@ name = "vcpkg"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.3"
@ -1683,15 +1932,18 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum activitystreams 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "638541e5169c839f6581302c50e38876312389475cd911ecc7c446c7491004cc"
"checksum activitypub 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab73fd715ad1e74fb44e4b2356db91a158793a2472a0c19e9002ab3184773d87"
"checksum activitystreams-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48db826c588a009960d74530e7c215e21fae130f585362504dc6b6357e5ce86b"
"checksum activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "670ef03168e704b0cae242e7a5d8b40506772b339687e01a3496fc4afe2e8542"
"checksum activitystreams-types 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aff9aa0c3412fe4da72a1f6e4b1c2e9792bfdf1308b709389192f17aa8e2b3cd"
"checksum activitystreams-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "14806b3c88c439e1670fdc99d9b18bf1a47d4fa7152fe8a3bd7da08b6ced3e95"
"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum ammonia 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd4c682378117e4186a492b2252b9537990e1617f44aed9788b9a1149de45477"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
"checksum array_tool 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8f8cb5d814eb646a863c4f24978cff2880c4be96ad8cde2c0f0678732902e271"
"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1"
"checksum backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbe525f66f42d207968308ee86bc2dd60aa5fab535b22e616323a173d097d8e"
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
@ -1708,6 +1960,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum cc 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8b9d2900f78631a5876dc5d6c9033ede027253efcd33dd36b1309fc6cab97ee0"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6"
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
"checksum comrak 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "053b26c8ce23b4c505a9479beace98f95899e0bf5c5255cf0219e9b0f48cf6ea"
"checksum cookie 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "477eb650753e319be2ae77ec368a58c638f9f0c4d941c39bad95e950fb1d1d0d"
"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
@ -1724,6 +1978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a70de3c590ce18df70743cace1cf12565637a0b26fd8b04ef10c7d33fdc66cdc"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d"
"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
"checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
@ -1732,6 +1987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
@ -1739,6 +1995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum html5ever 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b04478cf718862650a0bf66acaf8f2f8c906fbc703f35c916c1f4211b069a364"
"checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37"
"checksum humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
@ -1758,6 +2015,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "1a429b86418868c7ea91ee50e9170683f47fd9d94f5375438ec86ec3adb74e8e"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43"
"checksum markup5ever 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfedc97d5a503e96816d10fedcd5b42f760b2e525ce2f7ec71f6a41780548475"
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
@ -1769,6 +2029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0"
"checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0"
"checksum new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
"checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364"
@ -1782,12 +2043,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum pear_codegen 0.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ca34109829349aeefe22772916da5404b3f5cd0e63a72c5d91209fc809342265"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e823a5967bb4cdc6d3e46f47baaf4ecfeae44413a642b74ad44e59e49c7f6"
"checksum pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0fce5d8b5cc33983fc74f78ad552b5522ab41442c4ca91606e4236eb4b5ceefc"
"checksum pest_derive 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ab94faafeb93f4c5e3ce81ca0e5a779529a602ad5d09ae6d21996bfb8b6a52bf"
"checksum phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "7d37a244c75a9748e049225155f56dbcb98fe71b192fd25fd23cb914b5ad62f2"
"checksum phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "4e4048fe7dd7a06b8127ecd6d3803149126e9b33c7558879846da3a63f734f2b"
"checksum phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "05a079dd052e7b674d21cb31cbb6c05efd56a2cd2827db7692e2f1a507ebd998"
"checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930"
"checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f"
"checksum pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfb5e575ef93a1b7b2a381d47ba7c5d4e4f73bff37cee932195de769aad9a54"
"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
"checksum proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "49b6a521dc81b643e9a51e0d1cf05df46d5a2f3c0280ea72bcb68276ba64a118"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
@ -1799,6 +2063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a"
"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb"
"checksum regex-syntax 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bd90079345f4a4c3409214734ae220fd773c6f2e8a543d07370c6c1c369cfbfb"
"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a"
@ -1828,6 +2093,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
"checksum smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4f357e8cd37bf8822e1b964e96fd39e2cb5a0424f8aaa284ccaccc2162411c"
"checksum state 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5562ac59585fe3d9a1ccf6b4e298ce773f5063db80d59f783776b410c1714c2"
"checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423"
"checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191"
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97c05b8ebc34ddd6b967994d5c6e9852fa92f8b82b3858c39451f97346dcce5"
"checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
@ -1836,7 +2105,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum syntex_fmt_macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5386bdc48758d136af85b3880548e1f3a9fad8d7dc2b38bdb48c36a9cdefc0"
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
"checksum tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d706c3bec8103f346fc7b8a3887a2ff4195cf704bdbc6307069f32ea8a2b3af5"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
"checksum tokio 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "be15ef40f675c9fe66e354d74c73f3ed012ca1aa14d65846a33ee48f1ae8d922"
@ -1853,7 +2125,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum tokio-udp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "137bda266504893ac4774e0ec4c2108f7ccdbcb7ac8dced6305fe9e4e0b5041a"
"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9"
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
"checksum twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
"checksum typed-arena 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5934776c3ac1bea4a9d56620d6bf2d483b20d394e49581db40f187e1118ff667"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
@ -1861,15 +2135,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
"checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
"checksum unidecode 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae"
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
"checksum utf-8 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1262dfab4c30d5cb7c07026be00ee343a6cf5027fdc0104a9160f354e5db75c"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
"checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"

View File

@ -3,13 +3,11 @@ authors = ["Bat' <baptiste@gelez.xyz>"]
name = "plume"
version = "0.1.0"
[dependencies]
activitystreams = "0.1"
activitystreams-derive = "0.1"
activitystreams-traits = "0.1"
activitystreams-types = "0.1"
activitypub = "0.1.1"
array_tool = "1.0"
base64 = "0.9"
bcrypt = "0.2"
comrak = "0.2"
dotenv = "*"
failure = "0.1"
failure_derive = "0.1"
@ -25,6 +23,7 @@ serde = "*"
serde_derive = "1.0"
serde_json = "1.0"
url = "1.7"
ammonia = "1.1.0"
[dependencies.chrono]
features = ["serde"]

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE notifications DROP COLUMN creation_date;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE notifications ADD COLUMN creation_date TIMESTAMP NOT NULL DEFAULT now();

View File

@ -1,7 +1,6 @@
use activitystreams_traits::Actor;
use activitystreams_types::{
actor::Person,
activity::{Accept, Create, Follow, Like, Undo},
use activitypub::{
Actor,
activity::{Accept, Announce, Create, Follow, Like, Undo},
object::{Article, Note}
};
use diesel::PgConnection;
@ -19,8 +18,10 @@ use models::{
follows,
likes,
posts::*,
reshares::*,
users::User
};
use safe_string::SafeString;
#[derive(Fail, Debug)]
enum InboxError {
@ -40,7 +41,7 @@ pub trait Inbox {
blog_id: 0, // TODO
slug: String::from(""), // TODO
title: article.object_props.name_string().unwrap(),
content: article.object_props.content_string().unwrap(),
content: SafeString::new(&article.object_props.content_string().unwrap()),
published: true,
license: String::from("CC-0"),
ap_url: article.object_props.url_string()?
@ -52,8 +53,8 @@ pub trait Inbox {
let previous_url = note.object_props.in_reply_to.clone().unwrap().as_str().unwrap().to_string();
let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone());
Comment::insert(conn, NewComment {
content: note.object_props.content_string().unwrap(),
spoiler_text: note.object_props.summary_string().unwrap(),
content: SafeString::new(&note.object_props.content_string().unwrap()),
spoiler_text: note.object_props.summary_string().unwrap_or(String::from("")),
ap_url: note.object_props.id_string().ok(),
in_response_to_id: previous_comment.clone().map(|c| c.id),
post_id: previous_comment
@ -66,11 +67,11 @@ pub trait Inbox {
}
fn follow(&self, conn: &PgConnection, follow: Follow) -> Result<(), Error> {
let from = User::from_url(conn, follow.actor.as_str().unwrap().to_string()).unwrap();
match User::from_url(conn, follow.object.as_str().unwrap().to_string()) {
let from = User::from_url(conn, follow.follow_props.actor.as_str().unwrap().to_string()).unwrap();
match User::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()) {
Some(u) => self.accept_follow(conn, &from, &u, follow, from.id, u.id),
None => {
let blog = Blog::from_url(conn, follow.object.as_str().unwrap().to_string()).unwrap();
let blog = Blog::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()).unwrap();
self.accept_follow(conn, &from, &blog, follow, from.id, blog.id)
}
};
@ -78,8 +79,8 @@ pub trait Inbox {
}
fn like(&self, conn: &PgConnection, like: Like) -> Result<(), Error> {
let liker = User::from_url(conn, like.actor.as_str().unwrap().to_string());
let post = Post::find_by_ap_url(conn, like.object.as_str().unwrap().to_string());
let liker = User::from_url(conn, like.like_props.actor.as_str().unwrap().to_string());
let post = Post::find_by_ap_url(conn, like.like_props.object.as_str().unwrap().to_string());
likes::Like::insert(conn, likes::NewLike {
post_id: post.unwrap().id,
user_id: liker.unwrap().id,
@ -89,20 +90,32 @@ pub trait Inbox {
}
fn unlike(&self, conn: &PgConnection, undo: Undo) -> Result<(), Error> {
let like = likes::Like::find_by_ap_url(conn, undo.object_object::<Like>()?.object_props.id_string()?).unwrap();
let like = likes::Like::find_by_ap_url(conn, undo.undo_props.object_object::<Like>()?.object_props.id_string()?).unwrap();
like.delete(conn);
Ok(())
}
fn announce(&self, conn: &PgConnection, announce: Announce) -> Result<(), Error> {
let user = User::from_url(conn, announce.announce_props.actor.as_str().unwrap().to_string());
let post = Post::find_by_ap_url(conn, announce.announce_props.object.as_str().unwrap().to_string());
Reshare::insert(conn, NewReshare {
post_id: post.unwrap().id,
user_id: user.unwrap().id,
ap_url: announce.object_props.id_string()?
});
Ok(())
}
fn save(&self, conn: &PgConnection, act: serde_json::Value) -> Result<(), Error> {
match act["type"].as_str() {
Some(t) => {
match t {
"Announce" => self.announce(conn, serde_json::from_value(act.clone())?),
"Create" => {
let act: Create = serde_json::from_value(act.clone())?;
match act.object["type"].as_str().unwrap() {
"Article" => self.new_article(conn, act.object_object().unwrap()),
"Note" => self.new_comment(conn, act.object_object().unwrap(), act.actor_object::<Person>()?.object_props.id_string()?),
match act.create_props.object["type"].as_str().unwrap() {
"Article" => self.new_article(conn, act.create_props.object_object()?),
"Note" => self.new_comment(conn, act.create_props.object_object()?, act.create_props.actor_link::<Id>()?.0),
_ => Err(InboxError::InvalidType)?
}
},
@ -110,7 +123,7 @@ pub trait Inbox {
"Like" => self.like(conn, serde_json::from_value(act.clone())?),
"Undo" => {
let act: Undo = serde_json::from_value(act.clone())?;
match act.object["type"].as_str().unwrap() {
match act.undo_props.object["type"].as_str().unwrap() {
"Like" => self.unlike(conn, act),
_ => Err(InboxError::CantUndo)?
}
@ -137,8 +150,8 @@ pub trait Inbox {
});
let mut accept = Accept::default();
accept.set_actor_link::<Id>(from.clone().into_id()).unwrap();
accept.set_object_object(follow).unwrap();
accept.accept_props.set_actor_link::<Id>(from.clone().into_id()).unwrap();
accept.accept_props.set_object_object(follow).unwrap();
broadcast(conn, &*from, accept, vec![target.clone()]);
}
}

View File

@ -1,4 +1,4 @@
use activitystreams_traits::{Activity, Actor, Object, Link};
use activitypub::{Activity, Actor, Object, Link};
use array_tool::vec::Uniq;
use diesel::PgConnection;
use reqwest::Client;
@ -103,16 +103,11 @@ pub fn broadcast<A: Activity + Clone, S: sign::Signer, T: inbox::WithInbox + Act
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Id {
#[serde(flatten)]
id: String
}
pub struct Id(String);
impl Id {
pub fn new<T: Into<String>>(id: T) -> Id {
Id {
id: id.into()
}
Id(id.into())
}
}

View File

@ -1,15 +1,12 @@
#![feature(plugin, custom_derive, iterator_find_map)]
#![plugin(rocket_codegen)]
extern crate activitystreams;
#[macro_use]
extern crate activitystreams_derive;
extern crate activitystreams_traits;
extern crate activitystreams_types;
extern crate activitypub;
extern crate array_tool;
extern crate base64;
extern crate bcrypt;
extern crate chrono;
extern crate comrak;
extern crate failure;
#[macro_use]
extern crate failure_derive;
@ -32,6 +29,7 @@ extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate url;
extern crate ammonia;
use diesel::{pg::PgConnection, r2d2::{ConnectionManager, Pool}};
use dotenv::dotenv;
@ -44,6 +42,7 @@ mod models;
mod schema;
mod routes;
mod utils;
mod safe_string;
lazy_static! {
pub static ref BASE_URL: String = env::var("BASE_URL")
@ -70,19 +69,24 @@ fn main() {
routes::blogs::activity_details,
routes::blogs::outbox,
routes::blogs::new,
routes::blogs::new_auth,
routes::blogs::create,
routes::comments::new,
routes::comments::new_auth,
routes::comments::create,
routes::instance::index,
routes::instance::configure,
routes::instance::post_config,
routes::instance::shared_inbox,
routes::instance::nodeinfo,
routes::likes::create,
routes::likes::create_auth,
routes::notifications::notifications,
routes::notifications::notifications_auth,
routes::posts::details,
routes::posts::activity_details,
@ -90,7 +94,11 @@ fn main() {
routes::posts::new_auth,
routes::posts::create,
routes::reshares::create,
routes::reshares::create_auth,
routes::session::new,
routes::session::new_message,
routes::session::create,
routes::session::delete,
@ -98,10 +106,14 @@ fn main() {
routes::user::me,
routes::user::details,
routes::user::dashboard,
routes::user::dashboard_auth,
routes::user::followers,
routes::user::edit,
routes::user::edit_auth,
routes::user::update,
routes::user::follow,
routes::user::follow_auth,
routes::user::activity_details,
routes::user::outbox,
routes::user::inbox,
@ -110,6 +122,7 @@ fn main() {
routes::user::create,
routes::well_known::host_meta,
routes::well_known::nodeinfo,
routes::well_known::webfinger
])
.manage(init_pool())

View File

@ -1,5 +1,4 @@
use activitystreams_traits::{Actor, Object};
use activitystreams_types::collection::OrderedCollection;
use activitypub::{Actor, Object, collection::OrderedCollection};
use reqwest::{
Client,
header::{Accept, qitem},
@ -8,7 +7,7 @@ use reqwest::{
use serde_json;
use url::Url;
use chrono::NaiveDateTime;
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection};
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection, dsl::any};
use openssl::{
hash::MessageDigest,
pkey::{PKey, Private},
@ -72,6 +71,14 @@ impl Blog {
.into_iter().nth(0)
}
pub fn find_for_author(conn: &PgConnection, author_id: i32) -> Vec<Blog> {
use schema::blog_authors;
let author_ids = blog_authors::table.filter(blog_authors::author_id.eq(author_id)).select(blog_authors::blog_id);
blogs::table.filter(blogs::id.eq(any(author_ids)))
.load::<Blog>(conn)
.expect("Couldn't load blogs ")
}
pub fn find_by_name(conn: &PgConnection, name: String, instance_id: i32) -> Option<Blog> {
blogs::table.filter(blogs::actor_id.eq(name))
.filter(blogs::instance_id.eq(instance_id))

View File

@ -1,9 +1,9 @@
use activitystreams_types::{
use activitypub::{
activity::Create,
object::{Note, properties::ObjectProperties}
};
use chrono;
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods};
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, dsl::any};
use serde_json;
use activity_pub::{
@ -12,15 +12,17 @@ use activity_pub::{
object::Object
};
use models::{
instance::Instance,
posts::Post,
users::User
};
use schema::comments;
use safe_string::SafeString;
#[derive(Queryable, Identifiable, Serialize, Clone)]
pub struct Comment {
pub id: i32,
pub content: String,
pub content: SafeString,
pub in_response_to_id: Option<i32>,
pub post_id: i32,
pub author_id: i32,
@ -33,7 +35,7 @@ pub struct Comment {
#[derive(Insertable)]
#[table_name = "comments"]
pub struct NewComment {
pub content: String,
pub content: SafeString,
pub in_response_to_id: Option<i32>,
pub post_id: i32,
pub author_id: i32,
@ -105,11 +107,20 @@ impl Comment {
pub fn create_activity(&self, conn: &PgConnection) -> Create {
let mut act = Create::default();
act.set_actor_link(self.get_author(conn).into_id()).unwrap();
act.set_object_object(self.into_activity(conn)).unwrap();
act.create_props.set_actor_link(self.get_author(conn).into_id()).unwrap();
act.create_props.set_object_object(self.into_activity(conn)).unwrap();
act.object_props.set_id_string(format!("{}/activity", self.ap_url.clone().unwrap())).unwrap();
act
}
pub fn count_local(conn: &PgConnection) -> usize {
use schema::users;
let local_authors = users::table.filter(users::instance_id.eq(Instance::local_id(conn))).select(users::id);
comments::table.filter(comments::author_id.eq(any(local_authors)))
.load::<Comment>(conn)
.expect("Couldn't load local comments")
.len()
}
}
impl Object for Comment {

View File

@ -1,4 +1,4 @@
use activitystreams_types::activity;
use activitypub::activity;
use chrono;
use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods};
use serde_json;
@ -76,15 +76,15 @@ impl Like {
diesel::delete(self).execute(conn).unwrap();
let mut act = activity::Undo::default();
act.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.set_object_object(self.into_activity(conn)).unwrap();
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.undo_props.set_object_object(self.into_activity(conn)).unwrap();
act
}
pub fn into_activity(&self, conn: &PgConnection) -> activity::Like {
let mut act = activity::Like::default();
act.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.set_object_link(Post::get(conn, self.post_id).unwrap().into_id()).unwrap();
act.like_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.like_props.set_object_link(Post::get(conn, self.post_id).unwrap().into_id()).unwrap();
act.object_props.set_id_string(format!("{}/like/{}",
User::get(conn, self.user_id).unwrap().ap_url,
Post::get(conn, self.post_id).unwrap().ap_url

View File

@ -1,3 +1,4 @@
use chrono::NaiveDateTime;
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods};
use models::users::User;
@ -9,7 +10,8 @@ pub struct Notification {
pub title: String,
pub content: Option<String>,
pub link: Option<String>,
pub user_id: i32
pub user_id: i32,
pub creation_date: NaiveDateTime
}
#[derive(Insertable)]
@ -39,6 +41,7 @@ impl Notification {
pub fn find_for_user(conn: &PgConnection, user: &User) -> Vec<Notification> {
notifications::table.filter(notifications::user_id.eq(user.id))
.order_by(notifications::creation_date.desc())
.load::<Notification>(conn)
.expect("Couldn't load user notifications")
}

View File

@ -1,10 +1,9 @@
use activitystreams_types::{
use activitypub::{
activity::Create,
object::{Article, properties::ObjectProperties}
};
use chrono::NaiveDateTime;
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, BelongingToDsl};
use diesel::dsl::any;
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, BelongingToDsl, dsl::any};
use serde_json;
use BASE_URL;
@ -15,11 +14,14 @@ use activity_pub::{
};
use models::{
blogs::Blog,
instance::Instance,
likes::Like,
post_authors::PostAuthor,
reshares::Reshare,
users::User
};
use schema::posts;
use safe_string::SafeString;
#[derive(Queryable, Identifiable, Serialize)]
pub struct Post {
@ -27,7 +29,7 @@ pub struct Post {
pub blog_id: i32,
pub slug: String,
pub title: String,
pub content: String,
pub content: SafeString,
pub published: bool,
pub license: String,
pub creation_date: NaiveDateTime,
@ -40,7 +42,7 @@ pub struct NewPost {
pub blog_id: i32,
pub slug: String,
pub title: String,
pub content: String,
pub content: SafeString,
pub published: bool,
pub license: String,
pub ap_url: String
@ -62,6 +64,17 @@ impl Post {
.into_iter().nth(0)
}
pub fn count_local(conn: &PgConnection) -> usize {
use schema::post_authors;
use schema::users;
let local_authors = users::table.filter(users::instance_id.eq(Instance::local_id(conn))).select(users::id);
let local_posts_id = post_authors::table.filter(post_authors::author_id.eq(any(local_authors))).select(post_authors::post_id);
posts::table.filter(posts::id.eq(any(local_posts_id)))
.load::<Post>(conn)
.expect("Couldn't load local posts")
.len()
}
pub fn find_by_slug(conn: &PgConnection, slug: String) -> Option<Post> {
posts::table.filter(posts::slug.eq(slug))
.limit(1)
@ -127,6 +140,13 @@ impl Post {
.expect("Couldn't load likes associted to post")
}
pub fn get_reshares(&self, conn: &PgConnection) -> Vec<Reshare> {
use schema::reshares;
reshares::table.filter(reshares::post_id.eq(self.id))
.load::<Reshare>(conn)
.expect("Couldn't load reshares associted to post")
}
pub fn update_ap_url(&self, conn: &PgConnection) {
if self.ap_url.len() == 0 {
diesel::update(self)
@ -169,8 +189,8 @@ impl Post {
pub fn create_activity(&self, conn: &PgConnection) -> Create {
let mut act = Create::default();
act.object_props.set_id_string(format!("{}/activity", self.ap_url)).unwrap();
act.set_actor_link(Id::new(self.get_authors(conn)[0].clone().ap_url)).unwrap();
act.set_object_object(self.into_activity(conn)).unwrap();
act.create_props.set_actor_link(Id::new(self.get_authors(conn)[0].clone().ap_url)).unwrap();
act.create_props.set_object_object(self.into_activity(conn)).unwrap();
act
}
}
@ -183,7 +203,7 @@ impl IntoId for Post {
impl Object for Post {
fn compute_id(&self, conn: &PgConnection) -> String {
ap_url(format!("{}/~/{}/{}", BASE_URL.as_str(), self.get_blog(conn).actor_id, self.slug))
ap_url(format!("{}/~/{}/{}/", BASE_URL.as_str(), self.get_blog(conn).actor_id, self.slug))
}
fn serialize(&self, conn: &PgConnection) -> serde_json::Value {

View File

@ -1,23 +1,26 @@
use activitypub::activity;
use chrono::NaiveDateTime;
use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods};
use activity_pub::{IntoId, actor::Actor, object::Object};
use models::{posts::Post, users::User};
use schema::reshares;
#[derive(Serialize, Deserialize, Queryable, Identifiable)]
pub struct Reshare {
id: i32,
user_id: i32,
post_id: i32,
ap_url: String,
creation_date: NaiveDateTime
pub id: i32,
pub user_id: i32,
pub post_id: i32,
pub ap_url: String,
pub creation_date: NaiveDateTime
}
#[derive(Insertable)]
#[table_name = "reshares"]
pub struct NewReshare {
user_id: i32,
post_id: i32,
ap_url: String
pub user_id: i32,
pub post_id: i32,
pub ap_url: String
}
impl Reshare {
@ -35,4 +38,63 @@ impl Reshare {
.expect("Could'nt load reshare")
.into_iter().nth(0)
}
pub fn update_ap_url(&self, conn: &PgConnection) {
if self.ap_url.len() == 0 {
diesel::update(self)
.set(reshares::ap_url.eq(format!(
"{}/reshare/{}",
User::get(conn, self.user_id).unwrap().compute_id(conn),
Post::get(conn, self.post_id).unwrap().compute_id(conn)
)))
.get_result::<Reshare>(conn).expect("Couldn't update AP URL");
}
}
pub fn find_by_ap_url(conn: &PgConnection, ap_url: String) -> Option<Reshare> {
reshares::table.filter(reshares::ap_url.eq(ap_url))
.limit(1)
.load::<Reshare>(conn)
.expect("Error loading reshare by AP URL")
.into_iter().nth(0)
}
pub fn find_by_user_on_post(conn: &PgConnection, user: &User, post: &Post) -> Option<Reshare> {
reshares::table.filter(reshares::post_id.eq(post.id))
.filter(reshares::user_id.eq(user.id))
.limit(1)
.load::<Reshare>(conn)
.expect("Error loading reshare for user and post")
.into_iter().nth(0)
}
pub fn get_recents_for_author(conn: &PgConnection, user: &User, limit: i64) -> Vec<Reshare> {
reshares::table.filter(reshares::user_id.eq(user.id))
.order(reshares::creation_date.desc())
.limit(limit)
.load::<Reshare>(conn)
.expect("Error loading recent reshares for user")
}
pub fn get_post(&self, conn: &PgConnection) -> Option<Post> {
Post::get(conn, self.post_id)
}
pub fn delete(&self, conn: &PgConnection) -> activity::Undo {
diesel::delete(self).execute(conn).unwrap();
let mut act = activity::Undo::default();
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.undo_props.set_object_object(self.into_activity(conn)).unwrap();
act
}
pub fn into_activity(&self, conn: &PgConnection) -> activity::Announce {
let mut act = activity::Announce::default();
act.announce_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.announce_props.set_object_link(Post::get(conn, self.post_id).unwrap().into_id()).unwrap();
act.object_props.set_id_string(self.ap_url.clone()).unwrap();
act
}
}

View File

@ -1,9 +1,8 @@
use activitystreams_traits::{Actor, Object, Link};
use activitystreams_types::{
actor::Person,
use activitypub::{
Actor, Object,
actor::{Person, properties::ApActorProperties},
collection::OrderedCollection,
object::properties::ObjectProperties,
CustomObject
object::properties::ObjectProperties
};
use bcrypt;
use chrono::NaiveDateTime;
@ -36,6 +35,8 @@ use activity_pub::{
};
use db_conn::DbConn;
use models::{
blogs::Blog,
blog_authors::BlogAuthor,
comments::Comment,
follows::Follow,
instance::Instance,
@ -44,6 +45,7 @@ use models::{
posts::Post
};
use schema::users;
use safe_string::SafeString;
pub const AUTH_COOKIE: &'static str = "user_id";
@ -55,7 +57,7 @@ pub struct User {
pub outbox_url: String,
pub inbox_url: String,
pub is_admin: bool,
pub summary: String,
pub summary: SafeString,
pub email: Option<String>,
pub hashed_password: Option<String>,
pub instance_id: i32,
@ -74,7 +76,7 @@ pub struct NewUser {
pub outbox_url: String,
pub inbox_url: String,
pub is_admin: bool,
pub summary: String,
pub summary: SafeString,
pub email: Option<String>,
pub hashed_password: Option<String>,
pub instance_id: i32,
@ -118,6 +120,13 @@ impl User {
.into_iter().nth(0)
}
pub fn count_local(conn: &PgConnection) -> usize {
users::table.filter(users::instance_id.eq(Instance::local_id(conn)))
.load::<User>(conn)
.expect("Couldn't load local users")
.len()
}
pub fn find_by_email(conn: &PgConnection, email: String) -> Option<User> {
users::table.filter(users::email.eq(email))
.limit(1)
@ -192,7 +201,7 @@ impl User {
outbox_url: acct["outbox"].as_str().unwrap().to_string(),
inbox_url: acct["inbox"].as_str().unwrap().to_string(),
is_admin: false,
summary: acct["summary"].as_str().unwrap().to_string(),
summary: SafeString::new(&acct["summary"].as_str().unwrap().to_string()),
email: None,
hashed_password: None,
instance_id: instance.id,
@ -287,11 +296,31 @@ impl User {
.len() > 0
}
pub fn has_reshared(&self, conn: &PgConnection, post: &Post) -> bool {
use schema::reshares;
use models::reshares::Reshare;
reshares::table
.filter(reshares::post_id.eq(post.id))
.filter(reshares::user_id.eq(self.id))
.load::<Reshare>(conn)
.expect("Couldn't load reshares")
.len() > 0
}
pub fn is_author_in(&self, conn: &PgConnection, blog: Blog) -> bool {
use schema::blog_authors;
blog_authors::table.filter(blog_authors::author_id.eq(self.id))
.filter(blog_authors::blog_id.eq(blog.id))
.load::<BlogAuthor>(conn)
.expect("Couldn't load blog/author relationship")
.len() > 0
}
pub fn get_keypair(&self) -> PKey<Private> {
PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.clone().unwrap().as_ref()).unwrap()).unwrap()
}
pub fn into_activity(&self, conn: &PgConnection) -> CustomObject<ApProps, Person> {
pub fn into_activity(&self, conn: &PgConnection) -> Person {
let mut actor = Person::default();
actor.object_props = ObjectProperties {
id: Some(serde_json::to_value(self.compute_id(conn)).unwrap()),
@ -300,34 +329,22 @@ impl User {
url: Some(serde_json::to_value(self.compute_id(conn)).unwrap()),
..ObjectProperties::default()
};
CustomObject::new(actor, ApProps {
inbox: Some(serde_json::to_value(self.compute_inbox(conn)).unwrap()),
outbox: Some(serde_json::to_value(self.compute_outbox(conn)).unwrap()),
actor.ap_actor_props = ApActorProperties {
inbox: serde_json::to_value(self.compute_inbox(conn)).unwrap(),
outbox: serde_json::to_value(self.compute_outbox(conn)).unwrap(),
preferred_username: Some(serde_json::to_value(self.get_actor_id()).unwrap()),
endpoints: Some(json!({
"sharedInbox": ap_url(format!("{}/inbox", BASE_URL.as_str()))
}))
})
})),
followers: None,
following: None,
liked: None,
streams: None
};
actor
}
}
#[derive(Serialize, Deserialize, Default, Properties)]
#[serde(rename_all = "camelCase")]
pub struct ApProps {
#[activitystreams(ab(Object, Link))]
inbox: Option<serde_json::Value>,
#[activitystreams(ab(Object, Link))]
outbox: Option<serde_json::Value>,
#[activitystreams(ab(Object, Link))]
preferred_username: Option<serde_json::Value>,
#[activitystreams(ab(Object))]
endpoints: Option<serde_json::Value>
}
impl<'a, 'r> FromRequest<'a, 'r> for User {
type Error = ();
@ -355,7 +372,7 @@ impl APActor for User {
}
fn get_summary(&self) -> String {
self.summary.clone()
self.summary.get().clone()
}
fn get_instance(&self, conn: &PgConnection) -> Instance {
@ -427,10 +444,22 @@ impl WithInbox for User {
impl Inbox for User {
fn received(&self, conn: &PgConnection, act: serde_json::Value) {
self.save(conn, act.clone()).unwrap();
if let Err(err) = self.save(conn, act.clone()) {
println!("Inbox error:\n{}\n{}\n\nActivity was: {}", err.cause(), err.backtrace(), act.to_string());
}
// Notifications
match act["type"].as_str().unwrap() {
"Announce" => {
let actor = User::from_url(conn, act["actor"].as_str().unwrap().to_string()).unwrap();
let post = Post::find_by_ap_url(conn, act["object"].as_str().unwrap().to_string()).unwrap();
Notification::insert(conn, NewNotification {
title: format!("{} reshared your article", actor.display_name.clone()),
content: Some(post.title),
link: Some(post.ap_url),
user_id: self.id
});
},
"Follow" => {
let follower = User::from_url(conn, act["actor"].as_str().unwrap().to_string()).unwrap();
Notification::insert(conn, NewNotification {
@ -453,13 +482,17 @@ impl Inbox for User {
"Create" => {
match act["object"]["type"].as_str().unwrap() {
"Note" => {
let comment = Comment::find_by_ap_url(conn, act["object"]["id"].as_str().unwrap().to_string()).unwrap();
Notification::insert(conn, NewNotification {
title: format!("{} commented your article", comment.get_author(conn).display_name.clone()),
content: Some(comment.get_post(conn).title),
link: comment.ap_url,
user_id: self.id
});
match Comment::find_by_ap_url(conn, act["object"]["id"].as_str().unwrap().to_string()) {
Some(comment) => {
Notification::insert(conn, NewNotification {
title: format!("{} commented your article", comment.get_author(conn).display_name.clone()),
content: Some(comment.get_post(conn).title),
link: comment.ap_url,
user_id: self.id
});
},
None => println!("Couldn't find comment by AP id, to create a new notification")
};
}
_ => {}
}
@ -529,7 +562,7 @@ impl NewUser {
outbox_url: String::from(""),
inbox_url: String::from(""),
is_admin: is_admin,
summary: summary,
summary: SafeString::new(&summary),
email: Some(email),
hashed_password: Some(password),
instance_id: instance_id,

View File

@ -1,7 +1,7 @@
use activitystreams_types::collection::OrderedCollection;
use activitypub::collection::OrderedCollection;
use rocket::{
request::Form,
response::Redirect
response::{Redirect, Flash}
};
use rocket_contrib::Template;
use serde_json;
@ -24,6 +24,7 @@ fn details(name: String, conn: DbConn, user: Option<User>) -> Template {
Template::render("blogs/details", json!({
"blog": blog,
"account": user,
"is_author": user.map(|x| x.is_author_in(&*conn, blog)),
"recents": recents.into_iter().map(|p| {
json!({
"post": p,
@ -53,6 +54,11 @@ fn new(user: User) -> Template {
}))
}
#[get("/blogs/new", rank = 2)]
fn new_auth() -> Flash<Redirect>{
utils::requires_login("You need to be logged in order to create a new blog", "/blogs/new")
}
#[derive(FromForm)]
struct NewBlogForm {
pub title: String
@ -77,7 +83,7 @@ fn create(conn: DbConn, data: Form<NewBlogForm>, user: User) -> Redirect {
is_owner: true
});
Redirect::to(format!("/~/{}", slug).as_str())
Redirect::to(format!("/~/{}/", slug).as_str())
}
#[get("/~/<name>/outbox")]

View File

@ -1,4 +1,7 @@
use rocket::{ request::Form, response::Redirect};
use rocket::{
request::Form,
response::{Redirect, Flash}
};
use rocket_contrib::Template;
use activity_pub::broadcast;
@ -9,6 +12,9 @@ use models::{
users::User
};
use utils;
use safe_string::SafeString;
#[get("/~/<_blog>/<slug>/comment")]
fn new(_blog: String, slug: String, user: User, conn: DbConn) -> Template {
let post = Post::find_by_slug(&*conn, slug).unwrap();
@ -18,6 +24,11 @@ fn new(_blog: String, slug: String, user: User, conn: DbConn) -> Template {
}))
}
#[get("/~/<blog>/<slug>/comment", rank=2)]
fn new_auth(blog: String, slug: String) -> Flash<Redirect>{
utils::requires_login("You need to be logged in order to post a comment", &format!("~/{}/{}/comment", blog, slug))
}
#[derive(FromForm)]
struct CommentQuery {
responding_to: Option<i32>
@ -33,7 +44,7 @@ fn create(blog: String, slug: String, query: CommentQuery, data: Form<NewComment
let post = Post::find_by_slug(&*conn, slug.clone()).unwrap();
let form = data.get();
let comment = Comment::insert(&*conn, NewComment {
content: form.content.clone(),
content: SafeString::new(&form.content.clone()),
in_response_to_id: query.responding_to,
post_id: post.id,
author_id: user.id,

View File

@ -1,11 +1,12 @@
use rocket::{request::Form, response::Redirect};
use rocket_contrib::Template;
use rocket_contrib::{Json, Template};
use serde_json;
use BASE_URL;
use activity_pub::inbox::Inbox;
use db_conn::DbConn;
use models::{
comments::Comment,
posts::Post,
users::User,
instance::*
@ -15,7 +16,7 @@ use models::{
fn index(conn: DbConn, user: Option<User>) -> Template {
match Instance::get_local(&*conn) {
Some(inst) => {
let recents = Post::get_recents(&*conn, 5);
let recents = Post::get_recents(&*conn, 6);
Template::render("instance/index", json!({
"instance": inst,
@ -75,3 +76,28 @@ fn shared_inbox(conn: DbConn, data: String) -> String {
instance.received(&*conn, act);
String::from("")
}
#[get("/nodeinfo")]
fn nodeinfo(conn: DbConn) -> Json {
Json(json!({
"version": "2.0",
"software": {
"name": "Plume",
"version": "0.1.0"
},
"protocols": ["activitypub"],
"services": {
"inbound": [],
"outbound": []
},
"openRegistrations": true,
"usage": {
"users": {
"total": User::count_local(&*conn)
},
"localPosts": Post::count_local(&*conn),
"localComments": Comment::count_local(&*conn)
},
"metadata": {}
}))
}

View File

@ -1,4 +1,4 @@
use rocket::response::Redirect;
use rocket::response::{Redirect, Flash};
use activity_pub::broadcast;
use db_conn::DbConn;
@ -8,6 +8,8 @@ use models::{
users::User
};
use utils;
#[get("/~/<blog>/<slug>/like")]
fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect {
let post = Post::find_by_slug(&*conn, slug.clone()).unwrap();
@ -29,3 +31,8 @@ fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect {
Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref())
}
#[get("/~/<blog>/<slug>/like", rank = 2)]
fn create_auth(blog: String, slug: String) -> Flash<Redirect>{
utils::requires_login("You need to be logged in order to like a post", &format!("/~/{}/{}/like", blog, slug))
}

View File

@ -7,6 +7,7 @@ pub mod instance;
pub mod likes;
pub mod notifications;
pub mod posts;
pub mod reshares;
pub mod session;
pub mod user;
pub mod well_known;

View File

@ -1,8 +1,11 @@
use rocket::response::{Redirect, Flash};
use rocket_contrib::Template;
use db_conn::DbConn;
use models::{notifications::Notification, users::User};
use utils;
#[get("/notifications")]
fn notifications(conn: DbConn, user: User) -> Template {
Template::render("notifications/index", json!({
@ -10,3 +13,8 @@ fn notifications(conn: DbConn, user: User) -> Template {
"notifications": Notification::find_for_user(&*conn, &user)
}))
}
#[get("/notifications", rank = 2)]
fn notifications_auth() -> Flash<Redirect>{
utils::requires_login("You need to be logged in order to see your notifications", "/notifications")
}

View File

@ -1,6 +1,7 @@
use comrak::{markdown_to_html, ComrakOptions};
use heck::KebabCase;
use rocket::request::Form;
use rocket::response::Redirect;
use rocket::response::{Redirect, Flash};
use rocket_contrib::Template;
use serde_json;
@ -14,6 +15,7 @@ use models::{
users::User
};
use utils;
use safe_string::SafeString;
#[get("/~/<blog>/<slug>", rank = 4)]
fn details(blog: String, slug: String, conn: DbConn, user: Option<User>) -> Template {
@ -39,6 +41,8 @@ fn details(blog: String, slug: String, conn: DbConn, user: Option<User>) -> Temp
}).collect::<Vec<serde_json::Value>>(),
"n_likes": post.get_likes(&*conn).len(),
"has_liked": user.clone().map(|u| u.has_liked(&*conn, &post)).unwrap_or(false),
"n_reshares": post.get_reshares(&*conn).len(),
"has_reshared": user.clone().map(|u| u.has_reshared(&*conn, &post)).unwrap_or(false),
"account": user,
"date": &post.creation_date.timestamp()
}))
@ -54,9 +58,9 @@ fn activity_details(_blog: String, slug: String, conn: DbConn) -> ActivityPub {
activity_pub(act)
}
#[get("/~/<_blog>/new", rank = 2)]
fn new_auth(_blog: String) -> Redirect {
utils::requires_login()
#[get("/~/<blog>/new", rank = 2)]
fn new_auth(blog: String) -> Flash<Redirect> {
utils::requires_login("You need to be logged in order to write a new post", &format!("/~/{}/new",blog))
}
#[get("/~/<_blog>/new", rank = 1)]
@ -78,11 +82,26 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn)
let blog = Blog::find_by_fqn(&*conn, blog_name.to_string()).unwrap();
let form = data.get();
let slug = form.title.to_string().to_kebab_case();
let content = markdown_to_html(form.content.to_string().as_ref(), &ComrakOptions{
smart: true,
safe: true,
ext_strikethrough: true,
ext_tagfilter: true,
ext_table: true,
ext_autolink: true,
ext_tasklist: true,
ext_superscript: true,
ext_header_ids: Some("title".to_string()),
ext_footnotes: true,
..ComrakOptions::default()
});
let post = Post::insert(&*conn, NewPost {
blog_id: blog.id,
slug: slug.to_string(),
title: form.title.to_string(),
content: form.content.to_string(),
content: SafeString::new(&content),
published: true,
license: form.license.to_string(),
ap_url: "".to_string()
@ -96,5 +115,5 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn)
let act = post.create_activity(&*conn);
broadcast(&*conn, &user, act, user.get_followers(&*conn));
Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str())
Redirect::to(format!("/~/{}/{}/", blog_name, slug).as_str())
}

38
src/routes/reshares.rs Normal file
View File

@ -0,0 +1,38 @@
use rocket::response::{Redirect, Flash};
use activity_pub::broadcast;
use db_conn::DbConn;
use models::{
posts::Post,
reshares::*,
users::User
};
use utils;
#[get("/~/<blog>/<slug>/reshare")]
fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect {
let post = Post::find_by_slug(&*conn, slug.clone()).unwrap();
if !user.has_reshared(&*conn, &post) {
let reshare = Reshare::insert(&*conn, NewReshare {
post_id: post.id,
user_id: user.id,
ap_url: "".to_string()
});
reshare.update_ap_url(&*conn);
broadcast(&*conn, &user, reshare.into_activity(&*conn), user.get_followers(&*conn));
} else {
let reshare = Reshare::find_by_user_on_post(&*conn, &user, &post).unwrap();
let delete_act = reshare.delete(&*conn);
broadcast(&*conn, &user, delete_act, user.get_followers(&*conn));
}
Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref())
}
#[get("/~/<blog>/<slug>/reshare", rank=1)]
fn create_auth(blog: String, slug: String) -> Flash<Redirect> {
utils::requires_login("You need to be logged in order to reshare a post", &format!("/~/{}/{}/reshare",blog, slug))
}

View File

@ -1,7 +1,7 @@
use rocket::{
http::{Cookie, Cookies},
response::{Redirect, status::NotFound},
request::Form
request::{Form,FlashMessage}
};
use rocket_contrib::Template;
@ -15,6 +15,20 @@ fn new(user: Option<User>) -> Template {
}))
}
#[derive(FromForm)]
struct Message {
m: String
}
#[get("/login?<message>")]
fn new_message(user: Option<User>, message: Message) -> Template {
Template::render("session/login", json!({
"account": user,
"message": message.m
}))
}
#[derive(FromForm)]
struct LoginForm {
email_or_name: String,
@ -22,7 +36,7 @@ struct LoginForm {
}
#[post("/login", data = "<data>")]
fn create(conn: DbConn, data: Form<LoginForm>, mut cookies: Cookies) -> Result<Redirect, NotFound<String>> {
fn create(conn: DbConn, data: Form<LoginForm>, flash: Option<FlashMessage>, mut cookies: Cookies) -> Result<Redirect, NotFound<String>> {
let form = data.get();
let user = match User::find_by_email(&*conn, form.email_or_name.to_string()) {
Some(usr) => Ok(usr),
@ -31,12 +45,14 @@ fn create(conn: DbConn, data: Form<LoginForm>, mut cookies: Cookies) -> Result<R
None => Err("Invalid username or password")
}
};
match user {
Ok(usr) => {
if usr.auth(form.password.to_string()) {
cookies.add_private(Cookie::new(AUTH_COOKIE, usr.id.to_string()));
Ok(Redirect::to("/"))
Ok(Redirect::to(&flash
.and_then(|f| if f.name()=="callback" { Some(f.msg().to_owned()) } else { None })
.unwrap_or("/".to_owned()))
)
} else {
Err(NotFound(String::from("Invalid username or password")))
}

View File

@ -1,8 +1,10 @@
use activitystreams_types::{
use activitypub::{
activity::Follow,
collection::OrderedCollection
};
use rocket::{request::Form, response::Redirect};
use rocket::{request::Form,
response::{Redirect, Flash}
};
use rocket_contrib::Template;
use serde_json;
@ -13,21 +15,28 @@ use activity_pub::{
};
use db_conn::DbConn;
use models::{
blogs::Blog,
follows,
instance::Instance,
posts::Post,
reshares::Reshare,
users::*
};
use utils;
#[get("/me")]
fn me(user: User) -> Redirect {
Redirect::to(format!("/@/{}", user.username).as_ref())
fn me(user: Option<User>) -> Result<Redirect,Flash<Redirect>> {
match user {
Some(user) => Ok(Redirect::to(format!("/@/{}/", user.username).as_ref())),
None => Err(utils::requires_login("", "/me"))
}
}
#[get("/@/<name>", rank = 2)]
fn details(name: String, conn: DbConn, account: Option<User>) -> Template {
let user = User::find_by_fqn(&*conn, name).unwrap();
let recents = Post::get_recents_for_author(&*conn, &user, 5);
let recents = Post::get_recents_for_author(&*conn, &user, 6);
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6);
let user_id = user.id.clone();
let n_followers = user.get_followers(&*conn).len();
@ -47,11 +56,39 @@ fn details(name: String, conn: DbConn, account: Option<User>) -> Template {
"date": p.creation_date.timestamp()
})
}).collect::<Vec<serde_json::Value>>(),
"reshares": reshares.into_iter().map(|r| {
let p = r.get_post(&*conn).unwrap();
json!({
"post": p,
"author": ({
let author = &p.get_authors(&*conn)[0];
let mut json = serde_json::to_value(author).unwrap();
json["fqn"] = serde_json::Value::String(author.get_fqn(&*conn));
json
}),
"url": format!("/~/{}/{}/", p.get_blog(&*conn).actor_id, p.slug),
"date": p.creation_date.timestamp()
})
}).collect::<Vec<serde_json::Value>>(),
"is_self": account.map(|a| a.id == user_id).unwrap_or(false),
"n_followers": n_followers
}))
}
#[get("/dashboard")]
fn dashboard(user: User, conn: DbConn) -> Template {
let blogs = Blog::find_for_author(&*conn, user.id);
Template::render("users/dashboard", json!({
"account": user,
"blogs": blogs
}))
}
#[get("/dashboard", rank = 2)]
fn dashboard_auth() -> Flash<Redirect> {
utils::requires_login("You need to be logged in order to access your dashboard", "/dashboard")
}
#[get("/@/<name>/follow")]
fn follow(name: String, conn: DbConn, user: User) -> Redirect {
let target = User::find_by_fqn(&*conn, name.clone()).unwrap();
@ -60,11 +97,16 @@ fn follow(name: String, conn: DbConn, user: User) -> Redirect {
following_id: target.id
});
let mut act = Follow::default();
act.set_actor_link::<Id>(user.clone().into_id()).unwrap();
act.set_object_object(user.into_activity(&*conn)).unwrap();
act.follow_props.set_actor_link::<Id>(user.clone().into_id()).unwrap();
act.follow_props.set_object_object(user.into_activity(&*conn)).unwrap();
act.object_props.set_id_string(format!("{}/follow/{}", user.ap_url, target.ap_url)).unwrap();
broadcast(&*conn, &user, act, vec![target]);
Redirect::to(format!("/@/{}", name).as_ref())
Redirect::to(format!("/@/{}/", name).as_ref())
}
#[get("/@/<name>/follow", rank = 2)]
fn follow_auth(name: String) -> Flash<Redirect> {
utils::requires_login("You need to be logged in order to follow someone", &format!("/@/{}/follow", name))
}
#[get("/@/<name>/followers", rank = 2)]
@ -109,6 +151,11 @@ fn edit(name: String, user: User) -> Option<Template> {
}
}
#[get("/@/<name>/edit", rank = 2)]
fn edit_auth(name: String) -> Flash<Redirect> {
utils::requires_login("You need to be logged in order to edit your profile", &format!("/@/{}/edit", name))
}
#[derive(FromForm)]
struct UpdateUserForm {
display_name: Option<String>,
@ -155,7 +202,7 @@ fn create(conn: DbConn, data: Form<NewUserForm>) -> Result<Redirect, String> {
User::hash_pass(form.password.to_string()),
inst.id
)).update_boxes(&*conn);
Ok(Redirect::to(format!("/@/{}", data.get().username).as_str()))
Ok(Redirect::to(format!("/@/{}/", data.get().username).as_str()))
} else {
Err(String::from("Passwords don't match"))
}

View File

@ -6,6 +6,18 @@ use activity_pub::{ap_url, webfinger::Webfinger};
use db_conn::DbConn;
use models::{blogs::Blog, users::User};
#[get("/.well-known/nodeinfo")]
fn nodeinfo() -> Content<String> {
Content(ContentType::new("application", "jrd+json"), json!({
"links": [
{
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
"href": ap_url(format!("{domain}/nodeinfo", domain = BASE_URL.as_str()))
}
]
}).to_string())
}
#[get("/.well-known/host-meta", format = "application/xml")]
fn host_meta() -> String {
format!(r#"

103
src/safe_string.rs Normal file
View File

@ -0,0 +1,103 @@
use ammonia::clean;
use serde::{self, Serialize, Deserialize,
Serializer, Deserializer, de::Visitor};
use std::{fmt::{self, Display},
borrow::Borrow, io::Write,
ops::Deref};
use diesel::{self, deserialize::Queryable,
types::ToSql,
sql_types::Text,
serialize::{self, Output}};
#[derive(Debug,Clone,AsExpression,FromSqlRow)]
#[sql_type = "Text"]
pub struct SafeString{
value: String,
}
impl SafeString{
pub fn new(value: &str) -> Self {
SafeString{
value: clean(&value),
}
}
pub fn set(&mut self, value: &str) {
self.value = clean(value);
}
pub fn get(&self) -> &String {
&self.value
}
}
impl Serialize for SafeString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer, {
serializer.serialize_str(&self.value)
}
}
struct SafeStringVisitor;
impl<'de> Visitor<'de> for SafeStringVisitor {
type Value = SafeString;
fn expecting(&self, formatter:&mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E>(self, value: &str) -> Result<SafeString, E>
where E: serde::de::Error{
Ok(SafeString::new(value))
}
}
impl<'de> Deserialize<'de> for SafeString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>, {
Ok(
deserializer.deserialize_string(SafeStringVisitor)?
)
}
}
impl Queryable<Text, diesel::pg::Pg> for SafeString {
type Row = String;
fn build(value: Self::Row) -> Self {
SafeString::new(&value)
}
}
impl<DB> ToSql<diesel::sql_types::Text, DB> for SafeString
where
DB: diesel::backend::Backend,
str: ToSql<diesel::sql_types::Text, DB>, {
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
str::to_sql(&self.value, out)
}
}
impl Borrow<str> for SafeString {
fn borrow(&self) -> &str {
&self.value
}
}
impl Display for SafeString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
impl Deref for SafeString {
type Target = str;
fn deref(&self) -> &str {
&self.value
}
}
impl AsRef<str> for SafeString {
fn as_ref(&self) -> &str {
&self.value
}
}

View File

@ -73,6 +73,7 @@ table! {
content -> Nullable<Text>,
link -> Nullable<Varchar>,
user_id -> Int4,
creation_date -> Timestamp,
}
}

View File

@ -1,5 +1,5 @@
use heck::CamelCase;
use rocket::response::Redirect;
use rocket::response::{Redirect, Flash};
/// Remove non alphanumeric characters and CamelCase a string
pub fn make_actor_id(name: String) -> String {
@ -11,6 +11,6 @@ pub fn make_actor_id(name: String) -> String {
.collect()
}
pub fn requires_login() -> Redirect {
Redirect::to("/login")
pub fn requires_login(message: &str, url: &str) -> Flash<Redirect> {
Flash::new(Redirect::to(&format!("/login?m={}", message)), "callback", url)
}

View File

@ -17,6 +17,7 @@
</nav>
<nav>
{% if account %}
<a href="/dashboard">Dashboard</a>
<a href="/notifications">Notifications</a>
<a href="/me">My account</a>
<a href="/logout">Log Out</a>

View File

@ -0,0 +1,26 @@
{% extends "base" %}
{% import "macros" as macros %}
{% block title %}
{{ blog.title }}
{% endblock title %}
{% block content %}
<h1>{{ blog.title }} (~{{ blog.actor_id }})</h1>
<p>{{ blog.summary }}</p>
<section>
<h2>Latest articles</h2>
{% if recents | length < 1 %}
<p>No posts to see here yet.</p>
{% endif %}
{% if is_author %}
<a href="new" class="button inline-block">New article</a>
{% endif %}
<div class="cards">
{% for article in recents %}
{{ macros::post_card(article=article) }}
{% endfor %}
</div>
</section>
{% endblock content %}

View File

@ -1,18 +0,0 @@
{% extends "base" %}
{% import "macros" as macros %}
{% block title %}
{{ blog.title }}
{% endblock title %}
{% block content %}
<h1>{{ blog.title }} (~{{ blog.actor_id }})</h1>
<p>{{ blog.summary }}</p>
<h2>Latest articles</h2>
<div class="cards">
{% for article in recents %}
{{ macros::post_card(article=article) }}
{% endfor %}
</div>
{% endblock content %}

View File

@ -0,0 +1,11 @@
{% macro post_card(article) %}
<div class="card">
<h3><a href="{{ article.url }}">{{ article.post.title }}</a></h3>
<main
<p>{{ article.post.content | striptags | truncate(length=200) }}</p>
</main>
<p class="author">
By <a href="/@/{{ article.author.fqn }}/">{{ article.author.display_name }}</a> ⋅ {{ article.date | date(format="%B %e") }}
</p>
</div>
{% endmacro post_card %}

View File

@ -1,9 +0,0 @@
{% macro post_card(article) %}
<div class="card">
<h3><a href="{{ article.url }}">{{ article.post.title }}</a></h3>
<main
<p>{{ article.post.content | escape | truncate(length=200) }}</p>
</main>
<p class="author">By <a href="/@/{{ article.author.fqn }}/">{{ article.author.display_name }}</a> ⋅ {{ article.date | date(format="%B %e") }}</p>
</div>
{% endmacro post_card %}

View File

@ -9,7 +9,7 @@ Notifications
<div class="list">
{% for notification in notifications %}
<div class="card">
<h3><a href="{% if notification.link %}{{ notification.link }}{% else %}#{% endif %}">{{ notification.title }}</h3>
<h3><a href="{% if notification.link %}{{ notification.link }}/{% else %}#{% endif %}">{{ notification.title }}</h3>
{% if notification.content %}
<p>{{ notification.content }}</p>
{% endif %}

View File

@ -26,11 +26,23 @@
<p>
{{ n_likes }} like{{ n_likes | pluralize }}
</p>
{% if has_liked %}
<a class="button liked" href="like">I don't like this anymore</a>
{% else %}
<a class="button" href="like">Like</a>
{% endif %}
<p>
{{ n_reshares }} reshare{{ n_reshares | pluralize }}
</p>
<a class="button" href="reshare">
{% if has_reshared %}
I don't want to reshare this anymore
{% else %}
Reshare
{% endif %}
</a>
</div>
<div class="comments">

View File

@ -6,6 +6,9 @@ Login
{% block content %}
<h1>Login</h1>
{% if message %}
<p>{{ message }}</p>
{% endif %}
<form method="post">
<label for="email_or_name">Username or email</label>
<input type="text" id="email_or_name" name="email_or_name" />

View File

@ -0,0 +1,25 @@
{% extends "base" %}
{% import "macros" as macros %}
{% block title %}
Dashboard
{% endblock title %}
{% block content %}
<h1>Your Dashboard</h1>
<section>
<h2>Your Blogs</h2>
{% if blogs | length < 1 %}
<p>You don't have any blog yet. Create your own, or ask to join one.</p>
{% endif %}
<a class="button inline-block" href="/blogs/new">Start a new blog</a>
<div class="list">
{% for blog in blogs %}
<div class="card">
<h3><a href="/~/{{ blog.actor_id }}/">{{ blog.title }}</a></h3>
</div>
{% endfor %}
</div>
</section>
{% endblock content %}

View File

@ -31,10 +31,21 @@
{{ user.summary | safe }}
</div>
<h2>Latest articles</h2>
<div class="cards">
{% for article in recents %}
{{ macros::post_card(article=article) }}
{% endfor %}
</div>
{% if recents | length != 0 %}
<h2>Latest articles</h2>
<div class="cards">
{% for article in recents %}
{{ macros::post_card(article=article) }}
{% endfor %}
</div>
{% endif %}
{% if reshares | length != 0 %}
<h2>Recently reshared</h2>
<div class="cards">
{% for article in reshares %}
{{ macros::post_card(article=article) }}
{% endfor %}
</div>
{% endif %}
{% endblock content %}

View File

@ -22,8 +22,8 @@
<div class="cards">
{% for follower in followers %}
<div class="card">
<h3><a href="{{ follower.ap_url }}">{{ follower.display_name }}</a> &mdash; @{{ follower.fqn }}</h3>
<main><p>{{ follower.summary }}</p></main>
<h3><a href="{{ follower.ap_url }}/">{{ follower.display_name }}</a> &mdash; @{{ follower.fqn }}</h3>
<main><p>{{ follower.summary | safe }}</p></main>
</div>
{% endfor %}
</div>