Merge pull request #27 from Plume-org/activitystreams

Use the Activitystreams crate
This commit is contained in:
Baptiste Gelez 2018-05-18 23:14:55 +01:00 committed by GitHub
commit d7552ba369
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 551 additions and 179 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
rls rls
/target /target
**/*.rs.bk **/*.rs.bk
rls

116
Cargo.lock generated
View file

@ -1,3 +1,46 @@
[[package]]
name = "activitystreams"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
]
[[package]]
name = "activitystreams-derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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 = "activitystreams-traits"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"failure 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)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "activitystreams-types"
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)",
"chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.3.5 (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]] [[package]]
name = "adler32" name = "adler32"
version = "1.0.2" version = "1.0.2"
@ -323,6 +366,25 @@ dependencies = [
"backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "failure"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure_derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.3.2" version = "0.3.2"
@ -806,12 +868,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "plume" name = "plume"
version = "0.1.0" version = "0.1.0"
dependencies = [ 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)",
"array_tool 1.0.3 (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)", "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)", "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)", "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel 1.2.2 (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)", "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)",
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)",
@ -851,6 +919,11 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "quote"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "quote" name = "quote"
version = "0.4.2" version = "0.4.2"
@ -1183,6 +1256,16 @@ name = "state"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "0.12.15" version = "0.12.15"
@ -1203,6 +1286,23 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "synom"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]] [[package]]
name = "syntex_fmt_macros" name = "syntex_fmt_macros"
version = "0.5.0" version = "0.5.0"
@ -1473,6 +1573,11 @@ name = "unicode-segmentation"
version = "1.2.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.1.0" version = "0.1.0"
@ -1578,6 +1683,10 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata] [metadata]
"checksum activitystreams 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "638541e5169c839f6581302c50e38876312389475cd911ecc7c446c7491004cc"
"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 adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" "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 aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
@ -1617,6 +1726,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d" "checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d"
"checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" "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 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"
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum 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 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
@ -1679,6 +1790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfb5e575ef93a1b7b2a381d47ba7c5d4e4f73bff37cee932195de769aad9a54" "checksum pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfb5e575ef93a1b7b2a381d47ba7c5d4e4f73bff37cee932195de769aad9a54"
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "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 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"
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
"checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46" "checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46"
@ -1716,8 +1828,11 @@ 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.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 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 state 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5562ac59585fe3d9a1ccf6b4e298ce773f5063db80d59f783776b410c1714c2"
"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.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" "checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
"checksum syntex_fmt_macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5386bdc48758d136af85b3880548e1f3a9fad8d7dc2b38bdb48c36a9cdefc0" "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 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 tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
@ -1746,6 +1861,7 @@ 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-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-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-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946"
"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-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unidecode 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc" "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 unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"

View file

@ -3,10 +3,16 @@ authors = ["Bat' <baptiste@gelez.xyz>"]
name = "plume" name = "plume"
version = "0.1.0" version = "0.1.0"
[dependencies] [dependencies]
activitystreams = "0.1"
activitystreams-derive = "0.1"
activitystreams-traits = "0.1"
activitystreams-types = "0.1"
array_tool = "1.0" array_tool = "1.0"
base64 = "0.9" base64 = "0.9"
bcrypt = "0.2" bcrypt = "0.2"
dotenv = "*" dotenv = "*"
failure = "0.1"
failure_derive = "0.1"
heck = "0.3.0" heck = "0.3.0"
hex = "0.3" hex = "0.3"
hyper = "*" hyper = "*"

View file

@ -1,100 +1,146 @@
use activitystreams_traits::Actor;
use activitystreams_types::{
actor::Person,
activity::{Accept, Create, Follow, Like, Undo},
object::{Article, Note}
};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error;
use serde_json; use serde_json;
use activity_pub::activity; use activity_pub::{broadcast, Id, IntoId};
use activity_pub::actor::Actor; use activity_pub::actor::Actor as APActor;
use activity_pub::outbox::broadcast;
use activity_pub::sign::*; use activity_pub::sign::*;
use models::blogs::Blog; use models::blogs::Blog;
use models::comments::*; use models::comments::*;
use models::follows::*; use models::follows;
use models::likes::*; use models::likes;
use models::posts::*; use models::posts::*;
use models::users::User; use models::users::User;
#[derive(Fail, Debug)]
enum InboxError {
#[fail(display = "The `type` property is required, but was not present")]
NoType,
#[fail(display = "Invalid activity type")]
InvalidType,
#[fail(display = "Couldn't undo activity")]
CantUndo
}
pub trait Inbox { pub trait Inbox {
fn received(&self, conn: &PgConnection, act: serde_json::Value); fn received(&self, conn: &PgConnection, act: serde_json::Value);
fn save(&self, conn: &PgConnection, act: serde_json::Value) { fn new_article(&self, conn: &PgConnection, article: Article) -> Result<(), Error> {
match act["type"].as_str().unwrap() { Post::insert(conn, NewPost {
"Create" => { blog_id: 0, // TODO
match act["object"]["type"].as_str().unwrap() { slug: String::from(""), // TODO
"Article" => { title: article.object_props.name_string().unwrap(),
Post::insert(conn, NewPost { content: article.object_props.content_string().unwrap(),
blog_id: 0, // TODO published: true,
slug: String::from(""), // TODO license: String::from("CC-0"),
title: String::from(""), // TODO ap_url: article.object_props.url_string()?
content: act["object"]["content"].as_str().unwrap().to_string(), });
published: true, Ok(())
license: String::from("CC-0"), }
ap_url: act["object"]["url"].as_str().unwrap().to_string()
}); fn new_comment(&self, conn: &PgConnection, note: Note, actor_id: String) -> Result<(), Error> {
}, let previous_url = note.object_props.in_reply_to.clone().unwrap().as_str().unwrap().to_string();
"Note" => { let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone());
let previous_comment = Comment::find_by_ap_url(conn, act["object"]["inReplyTo"].as_str().unwrap().to_string()); Comment::insert(conn, NewComment {
Comment::insert(conn, NewComment { content: note.object_props.content_string().unwrap(),
content: act["object"]["content"].as_str().unwrap().to_string(), spoiler_text: note.object_props.summary_string().unwrap(),
spoiler_text: act["object"]["summary"].as_str().unwrap_or("").to_string(), ap_url: note.object_props.id_string().ok(),
ap_url: Some(act["object"]["id"].as_str().unwrap().to_string()), in_response_to_id: previous_comment.clone().map(|c| c.id),
in_response_to_id: previous_comment.clone().map(|c| c.id), post_id: previous_comment
post_id: previous_comment .map(|c| c.post_id)
.map(|c| c.post_id) .unwrap_or_else(|| Post::find_by_ap_url(conn, previous_url).unwrap().id),
.unwrap_or_else(|| Post::find_by_ap_url(conn, act["object"]["inReplyTo"].as_str().unwrap().to_string()).unwrap().id), author_id: User::from_url(conn, actor_id).unwrap().id,
author_id: User::from_url(conn, act["actor"].as_str().unwrap().to_string()).unwrap().id, sensitive: false // "sensitive" is not a standard property, we need to think about how to support it with the activitystreams crate
sensitive: act["object"]["sensitive"].as_bool().unwrap_or(false) });
}); Ok(())
} }
x => println!("Received a new {}, but didn't saved it", x)
} fn follow(&self, conn: &PgConnection, follow: Follow) -> Result<(), Error> {
}, let from = User::from_url(conn, follow.actor.as_str().unwrap().to_string()).unwrap();
"Follow" => { match User::from_url(conn, follow.object.as_str().unwrap().to_string()) {
let follow_act = activity::Follow::deserialize(act.clone()); Some(u) => self.accept_follow(conn, &from, &u, follow, from.id, u.id),
let from = User::from_url(conn, act["actor"].as_str().unwrap().to_string()).unwrap(); None => {
match User::from_url(conn, act["object"].as_str().unwrap().to_string()) { let blog = Blog::from_url(conn, follow.object.as_str().unwrap().to_string()).unwrap();
Some(u) => self.accept_follow(conn, &from, &u, &follow_act, from.id, u.id), self.accept_follow(conn, &from, &blog, follow, from.id, blog.id)
None => {
let blog = Blog::from_url(conn, follow_act.get_target_id()).unwrap();
self.accept_follow(conn, &from, &blog, &follow_act, from.id, blog.id)
}
};
} }
"Like" => { };
let liker = User::from_url(conn, act["actor"].as_str().unwrap().to_string()); Ok(())
let post = Post::find_by_ap_url(conn, act["object"].as_str().unwrap().to_string()); }
Like::insert(conn, NewLike {
post_id: post.unwrap().id, fn like(&self, conn: &PgConnection, like: Like) -> Result<(), Error> {
user_id: liker.unwrap().id, let liker = User::from_url(conn, like.actor.as_str().unwrap().to_string());
ap_url: act["id"].as_str().unwrap().to_string() let post = Post::find_by_ap_url(conn, like.object.as_str().unwrap().to_string());
}); likes::Like::insert(conn, likes::NewLike {
}, post_id: post.unwrap().id,
"Undo" => { user_id: liker.unwrap().id,
match act["object"]["type"].as_str().unwrap() { ap_url: like.object_props.id_string()?
"Like" => { });
let like = Like::find_by_ap_url(conn, act["object"]["id"].as_str().unwrap().to_string()).unwrap(); Ok(())
like.delete(conn); }
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();
like.delete(conn);
Ok(())
}
fn save(&self, conn: &PgConnection, act: serde_json::Value) -> Result<(), Error> {
match act["type"].as_str() {
Some(t) => {
match t {
"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()?),
_ => Err(InboxError::InvalidType)?
}
},
"Follow" => self.follow(conn, serde_json::from_value(act.clone())?),
"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() {
"Like" => self.unlike(conn, act),
_ => Err(InboxError::CantUndo)?
}
} }
x => println!("Wanted to Undo a {}, but it is not supported yet", x) _ => Err(InboxError::InvalidType)?
} }
}, },
x => println!("Received unknow activity type: {}", x) None => Err(InboxError::NoType)?
} }
} }
fn accept_follow<A: Actor + Signer, B: Actor + Clone, T: activity::Activity>( fn accept_follow<A: Signer + IntoId + Clone, B: Clone + WithInbox + Actor>(
&self, &self,
conn: &PgConnection, conn: &PgConnection,
from: &A, from: &A,
target: &B, target: &B,
follow: &T, follow: Follow,
from_id: i32, from_id: i32,
target_id: i32 target_id: i32
) { ) {
Follow::insert(conn, NewFollow { follows::Follow::insert(conn, follows::NewFollow {
follower_id: from_id, follower_id: from_id,
following_id: target_id following_id: target_id
}); });
let accept = activity::Accept::new(target, follow, conn); let mut accept = Accept::default();
broadcast(conn, from, accept, vec![target.clone()]); accept.set_actor_link::<Id>(from.clone().into_id()).unwrap();
accept.set_object_object(follow).unwrap();
broadcast(conn, &*from, accept, vec![target.clone()]);
} }
} }
pub trait WithInbox {
fn get_inbox_url(&self) -> String;
fn get_shared_inbox_url(&self) -> Option<String>;
}

View file

@ -1,13 +1,20 @@
use rocket::http::ContentType; use activitystreams_traits::{Activity, Actor, Object, Link};
use rocket::response::Content; use array_tool::vec::Uniq;
use diesel::PgConnection;
use reqwest::Client;
use rocket::http::{ContentType, Status};
use rocket::response::{Response, Responder, Content};
use rocket::request::Request;
use rocket_contrib::Json; use rocket_contrib::Json;
use serde_json; use serde_json;
pub mod activity; use self::sign::Signable;
// pub mod activity;
pub mod actor; pub mod actor;
pub mod inbox; pub mod inbox;
pub mod object; pub mod object;
pub mod outbox; // pub mod outbox;
pub mod request; pub mod request;
pub mod sign; pub mod sign;
pub mod webfinger; pub mod webfinger;
@ -54,3 +61,63 @@ pub fn context() -> serde_json::Value {
pub fn activity_pub(json: serde_json::Value) -> ActivityPub { pub fn activity_pub(json: serde_json::Value) -> ActivityPub {
Content(ContentType::new("application", "activity+json"), Json(json)) Content(ContentType::new("application", "activity+json"), Json(json))
} }
pub struct ActivityStream<T> (T);
impl<T> ActivityStream<T> {
pub fn new(t: T) -> ActivityStream<T> {
ActivityStream(t)
}
}
impl<'r, O: Object> Responder<'r> for ActivityStream<O> {
fn respond_to(self, request: &Request) -> Result<Response<'r>, Status> {
serde_json::to_string(&self.0).respond_to(request)
}
}
pub fn broadcast<A: Activity + Clone, S: sign::Signer, T: inbox::WithInbox + Actor>(conn: &PgConnection, sender: &S, act: A, to: Vec<T>) {
let boxes = to.into_iter()
.map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url()))
.collect::<Vec<String>>()
.unique();
for inbox in boxes {
// TODO: run it in Sidekiq or something like that
let mut act = serde_json::to_value(act.clone()).unwrap();
act["@context"] = context();
let signed = act.sign(sender, conn);
let res = Client::new()
.post(&inbox[..])
.headers(request::headers())
.header(request::signature(sender, request::headers(), conn))
.header(request::digest(signed.to_string()))
.body(signed.to_string())
.send();
match res {
Ok(mut r) => println!("Successfully sent activity to inbox ({})\n\n{:?}", inbox, r.text().unwrap()),
Err(e) => println!("Error while sending to inbox ({:?})", e)
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Id {
#[serde(flatten)]
id: String
}
impl Id {
pub fn new<T: Into<String>>(id: T) -> Id {
Id {
id: id.into()
}
}
}
pub trait IntoId {
fn into_id(self) -> Id;
}
impl Link for Id {}

View file

@ -1,3 +1,4 @@
use activitystreams_traits::{Activity, Object};
use array_tool::vec::Uniq; use array_tool::vec::Uniq;
use diesel::PgConnection; use diesel::PgConnection;
use reqwest::Client; use reqwest::Client;
@ -8,64 +9,7 @@ use serde_json;
use std::sync::Arc; use std::sync::Arc;
use activity_pub::{activity_pub, ActivityPub, context}; use activity_pub::{activity_pub, ActivityPub, context};
use activity_pub::activity::Activity;
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::request; use activity_pub::request;
use activity_pub::sign::*; use activity_pub::sign::*;
pub struct Outbox {
id: String,
items: Vec<Arc<Activity>>
}
impl Outbox {
pub fn new(id: String, items: Vec<Arc<Activity>>) -> Outbox {
Outbox {
id: id,
items: items
}
}
fn serialize(&self) -> ActivityPub {
let items = self.items.clone().into_iter().map(|i| i.serialize()).collect::<Vec<serde_json::Value>>();
activity_pub(json!({
"@context": context(),
"type": "OrderedCollection",
"id": self.id,
"totalItems": items.len(),
"orderedItems": items
}))
}
}
impl<'r> Responder<'r> for Outbox {
fn respond_to(self, request: &Request) -> Result<Response<'r>, Status> {
self.serialize().respond_to(request)
}
}
pub fn broadcast<A: Activity + Clone, S: Actor + Signer, T: Actor>(conn: &PgConnection, sender: &S, act: A, to: Vec<T>) {
let boxes = to.into_iter()
.map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url()))
.collect::<Vec<String>>()
.unique();
for inbox in boxes {
// TODO: run it in Sidekiq or something like that
let mut act = act.serialize();
act["@context"] = context();
let signed = act.sign(sender, conn);
let res = Client::new()
.post(&inbox[..])
.headers(request::headers())
.header(request::signature(sender, request::headers(), conn))
.header(request::digest(signed.to_string()))
.body(signed.to_string())
.send();
match res {
Ok(mut r) => println!("Successfully sent activity to inbox ({})\n\n{:?}", inbox, r.text().unwrap()),
Err(e) => println!("Error while sending to inbox ({:?})", e)
}
}
}

View file

@ -1,10 +1,18 @@
#![feature(plugin, custom_derive, iterator_find_map)] #![feature(plugin, custom_derive, iterator_find_map)]
#![plugin(rocket_codegen)] #![plugin(rocket_codegen)]
extern crate activitystreams;
#[macro_use]
extern crate activitystreams_derive;
extern crate activitystreams_traits;
extern crate activitystreams_types;
extern crate array_tool; extern crate array_tool;
extern crate base64; extern crate base64;
extern crate bcrypt; extern crate bcrypt;
extern crate chrono; extern crate chrono;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate heck; extern crate heck;
extern crate hex; extern crate hex;
#[macro_use] #[macro_use]

View file

@ -1,3 +1,5 @@
use activitystreams_traits::{Actor, Object};
use activitystreams_types::collection::OrderedCollection;
use reqwest::Client; use reqwest::Client;
use reqwest::header::{Accept, qitem}; use reqwest::header::{Accept, qitem};
use reqwest::mime::Mime; use reqwest::mime::Mime;
@ -9,18 +11,17 @@ use openssl::hash::MessageDigest;
use openssl::pkey::{PKey, Private}; use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa; use openssl::rsa::Rsa;
use openssl::sign::Signer; use openssl::sign::Signer;
use std::sync::Arc;
use activity_pub::activity::Activity; use activity_pub::{ActivityStream, Id, IntoId};
use activity_pub::actor::{Actor, ActorType}; use activity_pub::actor::{Actor as APActor, ActorType};
use activity_pub::outbox::Outbox; use activity_pub::inbox::WithInbox;
use activity_pub::sign; use activity_pub::sign;
use activity_pub::webfinger::*; use activity_pub::webfinger::*;
use models::instance::Instance; use models::instance::Instance;
use schema::blogs; use schema::blogs;
#[derive(Queryable, Identifiable, Serialize, Clone)] #[derive(Queryable, Identifiable, Serialize, Deserialize, Clone)]
pub struct Blog { pub struct Blog {
pub id: i32, pub id: i32,
pub actor_id: String, pub actor_id: String,
@ -158,11 +159,14 @@ impl Blog {
} }
} }
pub fn outbox(&self, conn: &PgConnection) -> Outbox { pub fn outbox(&self, conn: &PgConnection) -> ActivityStream<OrderedCollection> {
Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) let mut coll = OrderedCollection::default();
coll.collection_props.items = serde_json::to_value(self.get_activities(conn)).unwrap();
coll.collection_props.set_total_items_u64(self.get_activities(conn).len() as u64).unwrap();
ActivityStream::new(coll)
} }
fn get_activities(&self, _conn: &PgConnection) -> Vec<Arc<Activity>> { fn get_activities(&self, _conn: &PgConnection) -> Vec<serde_json::Value> {
vec![] vec![]
} }
@ -171,7 +175,26 @@ impl Blog {
} }
} }
impl Actor for Blog { impl IntoId for Blog {
fn into_id(self) -> Id {
Id::new(self.ap_url)
}
}
impl Object for Blog {}
impl Actor for Blog {}
impl WithInbox for Blog {
fn get_inbox_url(&self) -> String {
self.inbox_url.clone()
}
fn get_shared_inbox_url(&self) -> Option<String> {
None
}
}
impl APActor for Blog {
fn get_box_prefix() -> &'static str { fn get_box_prefix() -> &'static str {
"~" "~"
} }

View file

@ -1,8 +1,12 @@
use activitystreams_types::{
activity::Create,
object::{Note, properties::ObjectProperties}
};
use chrono; use chrono;
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods}; use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods};
use serde_json; use serde_json;
use activity_pub::{ap_url, PUBLIC_VISIBILTY}; use activity_pub::{ap_url, IntoId, PUBLIC_VISIBILTY};
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::object::Object; use activity_pub::object::Object;
use models::posts::Post; use models::posts::Post;
@ -71,6 +75,37 @@ impl Comment {
pub fn get_post(&self, conn: &PgConnection) -> Post { pub fn get_post(&self, conn: &PgConnection) -> Post {
Post::get(conn, self.post_id).unwrap() Post::get(conn, self.post_id).unwrap()
} }
pub fn into_activity(&self, conn: &PgConnection) -> Note {
let mut to = self.get_author(conn).get_followers(conn).into_iter().map(|f| f.ap_url).collect::<Vec<String>>();
to.append(&mut self.get_post(conn).get_receivers_urls(conn));
to.push(PUBLIC_VISIBILTY.to_string());
let mut comment = Note::default();
comment.object_props = ObjectProperties {
id: Some(serde_json::to_value(self.ap_url.clone()).unwrap()),
summary: Some(serde_json::to_value(self.spoiler_text.clone()).unwrap()),
content: Some(serde_json::to_value(self.content.clone()).unwrap()),
in_reply_to: Some(serde_json::to_value(self.in_response_to_id.map_or_else(|| self.get_post(conn).ap_url, |id| {
let comm = Comment::get(conn, id).unwrap();
comm.ap_url.clone().unwrap_or(comm.compute_id(conn))
})).unwrap()),
published: Some(serde_json::to_value(self.creation_date).unwrap()),
attributed_to: Some(serde_json::to_value(self.get_author(conn).compute_id(conn)).unwrap()),
to: Some(serde_json::to_value(to).unwrap()),
cc: Some(serde_json::to_value(Vec::<serde_json::Value>::new()).unwrap()),
..ObjectProperties::default()
};
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.object_props.set_id_string(format!("{}/activity", self.ap_url.clone().unwrap())).unwrap();
act
}
} }
impl Object for Comment { impl Object for Comment {

View file

@ -86,7 +86,7 @@ impl Instance {
impl Inbox for Instance { impl Inbox for Instance {
fn received(&self, conn: &PgConnection, act: serde_json::Value) { fn received(&self, conn: &PgConnection, act: serde_json::Value) {
self.save(conn, act.clone()); self.save(conn, act.clone()).unwrap();
// TODO: add to stream, or whatever needs to be done // TODO: add to stream, or whatever needs to be done
} }

View file

@ -1,7 +1,9 @@
use activitystreams_types::activity;
use chrono; use chrono;
use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods}; use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods};
use serde_json; use serde_json;
use activity_pub::IntoId;
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::object::Object; use activity_pub::object::Object;
use models::posts::Post; use models::posts::Post;
@ -66,8 +68,25 @@ impl Like {
.into_iter().nth(0) .into_iter().nth(0)
} }
pub fn delete(&self, conn: &PgConnection) { pub fn delete(&self, conn: &PgConnection) -> activity::Undo {
diesel::delete(self).execute(conn).unwrap(); 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
}
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.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
)).unwrap();
act
} }
} }

View file

@ -1,10 +1,14 @@
use activitystreams_types::{
activity::Create,
object::{Article, properties::ObjectProperties}
};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, BelongingToDsl}; use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, BelongingToDsl};
use diesel::dsl::any; use diesel::dsl::any;
use serde_json; use serde_json;
use BASE_URL; use BASE_URL;
use activity_pub::{PUBLIC_VISIBILTY, ap_url}; use activity_pub::{PUBLIC_VISIBILTY, ap_url, Id, IntoId};
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::object::Object; use activity_pub::object::Object;
use models::blogs::Blog; use models::blogs::Blog;
@ -137,6 +141,40 @@ impl Post {
}); });
to to
} }
pub fn into_activity(&self, conn: &PgConnection) -> Article {
let mut to = self.get_receivers_urls(conn);
to.push(PUBLIC_VISIBILTY.to_string());
let mut article = Article::default();
article.object_props = ObjectProperties {
name: Some(serde_json::to_value(self.title.clone()).unwrap()),
id: Some(serde_json::to_value(self.ap_url.clone()).unwrap()),
attributed_to: Some(serde_json::to_value(self.get_authors(conn).into_iter().map(|x| x.ap_url).collect::<Vec<String>>()).unwrap()),
content: Some(serde_json::to_value(self.content.clone()).unwrap()),
published: Some(serde_json::to_value(self.creation_date).unwrap()),
tag: Some(serde_json::to_value(Vec::<serde_json::Value>::new()).unwrap()),
url: Some(serde_json::to_value(self.compute_id(conn)).unwrap()),
to: Some(serde_json::to_value(to).unwrap()),
cc: Some(serde_json::to_value(Vec::<serde_json::Value>::new()).unwrap()),
..ObjectProperties::default()
};
article
}
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
}
}
impl IntoId for Post {
fn into_id(self) -> Id {
Id::new(self.ap_url.clone())
}
} }
impl Object for Post { impl Object for Post {

View file

@ -1,3 +1,10 @@
use activitystreams_traits::{Actor, Object, Link};
use activitystreams_types::{
actor::Person,
collection::OrderedCollection,
object::properties::ObjectProperties,
CustomObject
};
use bcrypt; use bcrypt;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection}; use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection};
@ -12,15 +19,12 @@ use reqwest::mime::Mime;
use rocket::request::{self, FromRequest, Request}; use rocket::request::{self, FromRequest, Request};
use rocket::outcome::IntoOutcome; use rocket::outcome::IntoOutcome;
use serde_json; use serde_json;
use std::sync::Arc;
use url::Url; use url::Url;
use BASE_URL; use BASE_URL;
use activity_pub::ap_url; use activity_pub::{ap_url, ActivityStream, Id, IntoId};
use activity_pub::activity::{Create, Activity}; use activity_pub::actor::{ActorType, Actor as APActor};
use activity_pub::actor::{ActorType, Actor}; use activity_pub::inbox::{Inbox, WithInbox};
use activity_pub::inbox::Inbox;
use activity_pub::outbox::Outbox;
use activity_pub::sign::{Signer, gen_keypair}; use activity_pub::sign::{Signer, gen_keypair};
use activity_pub::webfinger::{Webfinger, resolve}; use activity_pub::webfinger::{Webfinger, resolve};
use db_conn::DbConn; use db_conn::DbConn;
@ -34,7 +38,7 @@ use schema::users;
pub const AUTH_COOKIE: &'static str = "user_id"; pub const AUTH_COOKIE: &'static str = "user_id";
#[derive(Queryable, Identifiable, Serialize, Clone)] #[derive(Queryable, Identifiable, Serialize, Deserialize, Clone)]
pub struct User { pub struct User {
pub id: i32, pub id: i32,
pub username: String, pub username: String,
@ -224,16 +228,23 @@ impl User {
} }
} }
pub fn outbox(&self, conn: &PgConnection) -> Outbox { pub fn outbox(&self, conn: &PgConnection) -> ActivityStream<OrderedCollection> {
Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) let acts = self.get_activities(conn);
let n_acts = acts.len();
let mut coll = OrderedCollection::default();
coll.collection_props.items = serde_json::to_value(acts).unwrap();
coll.collection_props.set_total_items_u64(n_acts as u64).unwrap();
ActivityStream::new(coll)
} }
fn get_activities(&self, conn: &PgConnection) -> Vec<Arc<Activity>> { fn get_activities(&self, conn: &PgConnection) -> Vec<serde_json::Value> {
use schema::posts; use schema::posts;
use schema::post_authors; use schema::post_authors;
let posts_by_self = PostAuthor::belonging_to(self).select(post_authors::post_id); let posts_by_self = PostAuthor::belonging_to(self).select(post_authors::post_id);
let posts = posts::table.filter(posts::id.eq(any(posts_by_self))).load::<Post>(conn).unwrap(); let posts = posts::table.filter(posts::id.eq(any(posts_by_self))).load::<Post>(conn).unwrap();
posts.into_iter().map(|p| Arc::new(Create::new(self, &p, conn)) as Arc<Activity>).collect::<Vec<Arc<Activity>>>() posts.into_iter().map(|p| {
serde_json::to_value(p.create_activity(conn)).unwrap()
}).collect::<Vec<serde_json::Value>>()
} }
pub fn get_fqn(&self, conn: &PgConnection) -> String { pub fn get_fqn(&self, conn: &PgConnection) -> String {
@ -270,6 +281,42 @@ impl User {
pub fn get_keypair(&self) -> PKey<Private> { pub fn get_keypair(&self) -> PKey<Private> {
PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.clone().unwrap().as_ref()).unwrap()).unwrap() 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> {
let mut actor = Person::default();
actor.object_props = ObjectProperties {
id: Some(serde_json::to_value(self.compute_id(conn)).unwrap()),
name: Some(serde_json::to_value(self.get_display_name()).unwrap()),
summary: Some(serde_json::to_value(self.get_summary()).unwrap()),
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()),
preferred_username: Some(serde_json::to_value(self.get_actor_id()).unwrap()),
endpoints: Some(json!({
"sharedInbox": ap_url(format!("{}/inbox", BASE_URL.as_str()))
}))
})
}
}
#[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 { impl<'a, 'r> FromRequest<'a, 'r> for User {
@ -285,7 +332,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
} }
} }
impl Actor for User { impl APActor for User {
fn get_box_prefix() -> &'static str { fn get_box_prefix() -> &'static str {
"@" "@"
} }
@ -350,9 +397,28 @@ impl Actor for User {
} }
} }
impl IntoId for User {
fn into_id(self) -> Id {
Id::new(self.ap_url.clone())
}
}
impl Object for User {}
impl Actor for User {}
impl WithInbox for User {
fn get_inbox_url(&self) -> String {
self.inbox_url.clone()
}
fn get_shared_inbox_url(&self) -> Option<String> {
self.shared_inbox_url.clone()
}
}
impl Inbox for User { impl Inbox for User {
fn received(&self, conn: &PgConnection, act: serde_json::Value) { fn received(&self, conn: &PgConnection, act: serde_json::Value) {
self.save(conn, act.clone()); self.save(conn, act.clone()).unwrap();
// Notifications // Notifications
match act["type"].as_str().unwrap() { match act["type"].as_str().unwrap() {

View file

@ -1,11 +1,11 @@
use activitystreams_types::collection::OrderedCollection;
use rocket::request::Form; use rocket::request::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;
use serde_json; use serde_json;
use activity_pub::ActivityPub; use activity_pub::{ActivityStream, ActivityPub};
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::outbox::Outbox;
use db_conn::DbConn; use db_conn::DbConn;
use models::blog_authors::*; use models::blog_authors::*;
use models::blogs::*; use models::blogs::*;
@ -78,7 +78,7 @@ fn create(conn: DbConn, data: Form<NewBlogForm>, user: User) -> Redirect {
} }
#[get("/~/<name>/outbox")] #[get("/~/<name>/outbox")]
fn outbox(name: String, conn: DbConn) -> Outbox { fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> {
let blog = Blog::find_local(&*conn, name).unwrap(); let blog = Blog::find_local(&*conn, name).unwrap();
blog.outbox(&*conn) blog.outbox(&*conn)
} }

View file

@ -2,8 +2,7 @@ use rocket::request::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;
use activity_pub::activity::Create; use activity_pub::broadcast;
use activity_pub::outbox::broadcast;
use db_conn::DbConn; use db_conn::DbConn;
use models::comments::*; use models::comments::*;
use models::posts::Post; use models::posts::Post;
@ -41,8 +40,8 @@ fn create(blog: String, slug: String, query: CommentQuery, data: Form<NewComment
sensitive: false, sensitive: false,
spoiler_text: "".to_string() spoiler_text: "".to_string()
}); });
let act = Create::new(&user, &comment, &*conn);
broadcast(&*conn, &user, act, user.get_followers(&*conn)); broadcast(&*conn, &user, comment.create_activity(&*conn), user.get_followers(&*conn));
Redirect::to(format!("/~/{}/{}/#comment-{}", blog, slug, comment.id).as_ref()) Redirect::to(format!("/~/{}/{}/#comment-{}", blog, slug, comment.id).as_ref())
} }

View file

@ -1,7 +1,6 @@
use rocket::response::Redirect; use rocket::response::Redirect;
use activity_pub::activity::{Like, Undo}; use activity_pub::broadcast;
use activity_pub::outbox::broadcast;
use db_conn::DbConn; use db_conn::DbConn;
use models::likes; use models::likes;
use models::posts::Post; use models::posts::Post;
@ -18,12 +17,12 @@ fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect {
ap_url: "".to_string() ap_url: "".to_string()
}); });
like.update_ap_url(&*conn); like.update_ap_url(&*conn);
let act = Like::new(&user, &post, &*conn);
broadcast(&*conn, &user, act, user.get_followers(&*conn)); broadcast(&*conn, &user, like.into_activity(&*conn), user.get_followers(&*conn));
} else { } else {
let like = likes::Like::find_by_user_on_post(&*conn, &user, &post).unwrap(); let like = likes::Like::find_by_user_on_post(&*conn, &user, &post).unwrap();
like.delete(&*conn); let delete_act = like.delete(&*conn);
broadcast(&*conn, &user, Undo::new(&user, &like, &*conn), user.get_followers(&*conn)); broadcast(&*conn, &user, delete_act, user.get_followers(&*conn));
} }
Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref()) Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref())

View file

@ -4,10 +4,8 @@ use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;
use serde_json; use serde_json;
use activity_pub::{context, activity_pub, ActivityPub}; use activity_pub::{broadcast, context, activity_pub, ActivityPub};
use activity_pub::activity::Create;
use activity_pub::object::Object; use activity_pub::object::Object;
use activity_pub::outbox::broadcast;
use db_conn::DbConn; use db_conn::DbConn;
use models::blogs::*; use models::blogs::*;
use models::comments::Comment; use models::comments::Comment;
@ -39,7 +37,7 @@ fn details(blog: String, slug: String, conn: DbConn, user: Option<User>) -> Temp
#[get("/~/<_blog>/<slug>", rank = 3, format = "application/activity+json")] #[get("/~/<_blog>/<slug>", rank = 3, format = "application/activity+json")]
fn activity_details(_blog: String, slug: String, conn: DbConn) -> ActivityPub { fn activity_details(_blog: String, slug: String, conn: DbConn) -> ActivityPub {
// TODO: posts in different blogs may have the same slug // FIXME: posts in different blogs may have the same slug
let post = Post::find_by_slug(&*conn, slug).unwrap(); let post = Post::find_by_slug(&*conn, slug).unwrap();
let mut act = post.serialize(&*conn); let mut act = post.serialize(&*conn);
@ -86,7 +84,7 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn)
author_id: user.id author_id: user.id
}); });
let act = Create::new(&user, &post, &*conn); let act = post.create_activity(&*conn);
broadcast(&*conn, &user, act, user.get_followers(&*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())

View file

@ -1,14 +1,17 @@
use activitystreams_types::{
activity::Follow,
collection::OrderedCollection
};
use rocket::request::Form; use rocket::request::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;
use serde_json; use serde_json;
use activity_pub::{activity, activity_pub, ActivityPub, context}; use activity_pub::{activity_pub, ActivityPub, ActivityStream, context, broadcast, Id, IntoId};
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::inbox::Inbox; use activity_pub::inbox::Inbox;
use activity_pub::outbox::{broadcast, Outbox};
use db_conn::DbConn; use db_conn::DbConn;
use models::follows::*; use models::follows;
use models::instance::Instance; use models::instance::Instance;
use models::posts::Post; use models::posts::Post;
use models::users::*; use models::users::*;
@ -49,11 +52,15 @@ fn details(name: String, conn: DbConn, account: Option<User>) -> Template {
#[get("/@/<name>/follow")] #[get("/@/<name>/follow")]
fn follow(name: String, conn: DbConn, user: User) -> Redirect { fn follow(name: String, conn: DbConn, user: User) -> Redirect {
let target = User::find_by_fqn(&*conn, name.clone()).unwrap(); let target = User::find_by_fqn(&*conn, name.clone()).unwrap();
Follow::insert(&*conn, NewFollow { follows::Follow::insert(&*conn, follows::NewFollow {
follower_id: user.id, follower_id: user.id,
following_id: target.id following_id: target.id
}); });
broadcast(&*conn, &user, activity::Follow::new(&user, &target, &*conn), vec![target]); 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.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())
} }
@ -152,7 +159,7 @@ fn create(conn: DbConn, data: Form<NewUserForm>) -> Result<Redirect, String> {
} }
#[get("/@/<name>/outbox")] #[get("/@/<name>/outbox")]
fn outbox(name: String, conn: DbConn) -> Outbox { fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> {
let user = User::find_local(&*conn, name).unwrap(); let user = User::find_local(&*conn, name).unwrap();
user.outbox(&*conn) user.outbox(&*conn)
} }