mirror of
https://git.joinplu.me/Plume/Plume.git
synced 2024-12-22 18:16:30 +00:00
Merge branch 'master' of https://github.com/Plume-org/Plume
This commit is contained in:
commit
a033a9d74e
48 changed files with 978 additions and 183 deletions
296
Cargo.lock
generated
296
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE notifications DROP COLUMN creation_date;
|
|
@ -0,0 +1,2 @@
|
|||
-- Your SQL goes here
|
||||
ALTER TABLE notifications ADD COLUMN creation_date TIMESTAMP NOT NULL DEFAULT now();
|
|
@ -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(¬e.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()]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -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())
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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": {}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
38
src/routes/reshares.rs
Normal 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))
|
||||
}
|
|
@ -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")))
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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
103
src/safe_string.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -73,6 +73,7 @@ table! {
|
|||
content -> Nullable<Text>,
|
||||
link -> Nullable<Varchar>,
|
||||
user_id -> Int4,
|
||||
creation_date -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>
|
26
templates/blogs/details.html.tera
Normal file
26
templates/blogs/details.html.tera
Normal 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 %}
|
|
@ -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 %}
|
11
templates/macros.html.tera
Normal file
11
templates/macros.html.tera
Normal 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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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">
|
|
@ -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" />
|
25
templates/users/dashboard.tera
Normal file
25
templates/users/dashboard.tera
Normal 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 %}
|
|
@ -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 %}
|
|
@ -22,8 +22,8 @@
|
|||
<div class="cards">
|
||||
{% for follower in followers %}
|
||||
<div class="card">
|
||||
<h3><a href="{{ follower.ap_url }}">{{ follower.display_name }}</a> — @{{ follower.fqn }}</h3>
|
||||
<main><p>{{ follower.summary }}</p></main>
|
||||
<h3><a href="{{ follower.ap_url }}/">{{ follower.display_name }}</a> — @{{ follower.fqn }}</h3>
|
||||
<main><p>{{ follower.summary | safe }}</p></main>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
Loading…
Reference in a new issue