From ac280782b27b07e5282af03e329253503100ef99 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Sat, 7 Mar 2020 18:31:13 -0500 Subject: [PATCH] Iframely and pictshare backend mostly done. --- server/Cargo.lock | 166 ++++++++++++++- server/Cargo.toml | 4 +- .../down.sql | 112 ++++++++++ .../up.sql | 115 +++++++++++ server/src/api/mod.rs | 3 +- server/src/api/post.rs | 16 ++ server/src/api/user.rs | 4 + server/src/apub/mod.rs | 4 + server/src/db/comment.rs | 4 + server/src/db/comment_view.rs | 4 + server/src/db/moderator.rs | 4 + server/src/db/post.rs | 16 ++ server/src/db/post_view.rs | 101 +++++---- server/src/db/user_mention.rs | 4 + server/src/lib.rs | 89 ++++++++ server/src/schema.rs | 4 + ui/src/components/iframely-card.tsx | 24 +-- ui/src/components/inbox.tsx | 6 + ui/src/components/post-form.tsx | 2 +- ui/src/components/post-listing.tsx | 193 +++++++----------- ui/src/interfaces.ts | 19 +- ui/src/utils.ts | 23 ++- 22 files changed, 714 insertions(+), 203 deletions(-) create mode 100644 server/migrations/2020-03-06-202329_add_post_iframely_data/down.sql create mode 100644 server/migrations/2020-03-06-202329_add_post_iframely_data/up.sql diff --git a/server/Cargo.lock b/server/Cargo.lock index dc583d14d75..6f530ec226c 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -148,7 +148,7 @@ dependencies = [ "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -586,6 +586,15 @@ name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bytes" version = "0.5.3" @@ -628,6 +637,25 @@ dependencies = [ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "chttp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "curl 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)", + "curl-sys 0.4.28+curl-7.69.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "sluice 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -678,6 +706,14 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-channel" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-channel" version = "0.4.0" @@ -686,6 +722,15 @@ dependencies = [ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-utils" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-utils" version = "0.7.0" @@ -696,6 +741,35 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "curl" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "curl-sys 0.4.28+curl-7.69.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "curl-sys" +version = "0.4.28+curl-7.69.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libnghttp2-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "darling" version = "0.10.2" @@ -1038,11 +1112,24 @@ dependencies = [ "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-channel-preview" +version = "0.3.0-alpha.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "futures-core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures-core-preview" +version = "0.3.0-alpha.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures-executor" version = "0.3.1" @@ -1058,6 +1145,11 @@ name = "futures-io" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures-io-preview" +version = "0.3.0-alpha.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures-macro" version = "0.3.1" @@ -1097,6 +1189,18 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-util-preview" +version = "0.3.0-alpha.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -1125,7 +1229,7 @@ dependencies = [ [[package]] name = "h2" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1182,6 +1286,16 @@ name = "htmlescape" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "http" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "http" version = "0.2.0" @@ -1314,6 +1428,7 @@ dependencies = [ "actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "bcrypt 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "chttp 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "diesel 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1326,6 +1441,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "rss 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1383,6 +1499,26 @@ name = "libc" version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libnghttp2-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "linked-hash-map" version = "0.3.0" @@ -2233,6 +2369,16 @@ name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "sluice" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "smallvec" version = "1.1.0" @@ -2806,20 +2952,26 @@ dependencies = [ "checksum bumpalo 3.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5fb8038c1ddc0a5f73787b130f4cc75151e96ed33e417fde765eb5a81e3532f4" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38" "checksum bytestring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b24c107a4432e408d2caa58d3f5e763b219236221406ea58a4076b62343a039d" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum chttp 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ca699a61cc91c90af209d6bf546fc69d29e9d474c77fa86b9410eb3cf1e6eb31" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" "checksum copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" "checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" +"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" "checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +"checksum curl 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)" = "ecb534fed9060d04bccaa8b8e1e2d3d5a0d7a9ec6d9c667691c80a3c6b7d19ef" +"checksum curl-sys 0.4.28+curl-7.69.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2c6b7fa5d36aa192e410788b77af65f339af24c8786419e8b48173689a484bf" "checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" "checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" "checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" @@ -2859,22 +3011,27 @@ dependencies = [ "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" "checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" +"checksum futures-channel-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "21c71ed547606de08e9ae744bb3c6d80f5627527ef31ecf2a7210d0e67bc8fae" "checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" +"checksum futures-core-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4b141ccf9b7601ef987f36f1c0d9522f76df3bba1cf2e63bfacccc044c4558f5" "checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" "checksum futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" +"checksum futures-io-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "082e402605fcb8b1ae1e5ba7d7fdfd3e31ef510e2a8367dd92927bb41ae41b3a" "checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" "checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" "checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" "checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" +"checksum futures-util-preview 0.3.0-alpha.17 (registry+https://github.com/rust-lang/crates.io-index)" = "af8198c48b222f02326940ce2b3aa9e6e91a32886eeaad7ca3b8e4c70daa3f4e" "checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" -"checksum h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" +"checksum h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7" "checksum hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0849d73a64ec77d1c8354aff489cf31943c4b4d3716de1eabfba572c70fde530" "checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" "checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" +"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" "checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" @@ -2895,6 +3052,8 @@ dependencies = [ "checksum lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bbb68ca999042d965476e47bbdbacd52db0927348b6f8062c44dd04a3b1fd43b" "checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum libnghttp2-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b359f5ec8106bc297694c9a562ace312be2cfd17a5fc68dc12249845aa144b11" +"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586" @@ -2995,6 +3154,7 @@ dependencies = [ "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" "checksum simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum sluice 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ec70d7c3b17c262d4a18f7291c6ce62bf47170915f3b795434d3c5c49a4e59b7" "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" "checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" diff --git a/server/Cargo.toml b/server/Cargo.toml index 939d038ed59..e73143687fb 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Dessalines "] edition = "2018" [dependencies] -diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2"] } +diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-column-tables"] } diesel_migrations = "1.4.0" dotenv = "0.15.0" bcrypt = "0.6.1" @@ -33,3 +33,5 @@ rss = "1.9.0" htmlescape = "0.3.1" config = "0.10.1" hjson = "0.8.2" +percent-encoding = "2.1.0" +chttp = "0.5.5" diff --git a/server/migrations/2020-03-06-202329_add_post_iframely_data/down.sql b/server/migrations/2020-03-06-202329_add_post_iframely_data/down.sql new file mode 100644 index 00000000000..71dcc6bab10 --- /dev/null +++ b/server/migrations/2020-03-06-202329_add_post_iframely_data/down.sql @@ -0,0 +1,112 @@ +-- Adds a newest_activity_time for the post_views, in order to sort by newest comment +drop view post_view; +drop view post_mview; +drop materialized view post_aggregates_mview; +drop view post_aggregates_view; + +-- Drop the columns +alter table post drop column embed_title; +alter table post drop column embed_description; +alter table post drop column embed_html; +alter table post drop column thumbnail_url; + +-- regen post view +create view post_aggregates_view as +select +p.*, +(select u.banned from user_ u where p.creator_id = u.id) as banned, +(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, +(select name from user_ where p.creator_id = user_.id) as creator_name, +(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, +(select name from community where p.community_id = community.id) as community_name, +(select removed from community c where p.community_id = c.id) as community_removed, +(select deleted from community c where p.community_id = c.id) as community_deleted, +(select nsfw from community c where p.community_id = c.id) as community_nsfw, +(select count(*) from comment where comment.post_id = p.id) as number_of_comments, +coalesce(sum(pl.score), 0) as score, +count (case when pl.score = 1 then 1 else null end) as upvotes, +count (case when pl.score = -1 then 1 else null end) as downvotes, +hot_rank(coalesce(sum(pl.score) , 0), + ( + case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps + else greatest(c.recent_comment_time, p.published) + end + ) +) as hot_rank, +( + case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps + else greatest(c.recent_comment_time, p.published) + end +) as newest_activity_time +from post p +left join post_like pl on p.id = pl.post_id +left join ( + select post_id, + max(published) as recent_comment_time + from comment + group by 1 +) c on p.id = c.post_id +group by p.id, c.recent_comment_time; + +create materialized view post_aggregates_mview as select * from post_aggregates_view; + +create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); + +create view post_view as +with all_post as ( + select + pa.* + from post_aggregates_view pa +) +select +ap.*, +u.id as user_id, +coalesce(pl.score, 0) as my_vote, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; + +create view post_mview as +with all_post as ( + select + pa.* + from post_aggregates_mview pa +) +select +ap.*, +u.id as user_id, +coalesce(pl.score, 0) as my_vote, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; + diff --git a/server/migrations/2020-03-06-202329_add_post_iframely_data/up.sql b/server/migrations/2020-03-06-202329_add_post_iframely_data/up.sql new file mode 100644 index 00000000000..d431bb7fa2c --- /dev/null +++ b/server/migrations/2020-03-06-202329_add_post_iframely_data/up.sql @@ -0,0 +1,115 @@ +-- Add the columns +alter table post add column embed_title text; +alter table post add column embed_description text; +alter table post add column embed_html text; +alter table post add column thumbnail_url text; + +-- Regenerate the views + +-- Adds a newest_activity_time for the post_views, in order to sort by newest comment +drop view post_view; +drop view post_mview; +drop materialized view post_aggregates_mview; +drop view post_aggregates_view; + +-- regen post view +create view post_aggregates_view as +select +p.*, +(select u.banned from user_ u where p.creator_id = u.id) as banned, +(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, +(select name from user_ where p.creator_id = user_.id) as creator_name, +(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, +(select name from community where p.community_id = community.id) as community_name, +(select removed from community c where p.community_id = c.id) as community_removed, +(select deleted from community c where p.community_id = c.id) as community_deleted, +(select nsfw from community c where p.community_id = c.id) as community_nsfw, +(select count(*) from comment where comment.post_id = p.id) as number_of_comments, +coalesce(sum(pl.score), 0) as score, +count (case when pl.score = 1 then 1 else null end) as upvotes, +count (case when pl.score = -1 then 1 else null end) as downvotes, +hot_rank(coalesce(sum(pl.score) , 0), + ( + case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps + else greatest(c.recent_comment_time, p.published) + end + ) +) as hot_rank, +( + case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps + else greatest(c.recent_comment_time, p.published) + end +) as newest_activity_time +from post p +left join post_like pl on p.id = pl.post_id +left join ( + select post_id, + max(published) as recent_comment_time + from comment + group by 1 +) c on p.id = c.post_id +group by p.id, c.recent_comment_time; + +create materialized view post_aggregates_mview as select * from post_aggregates_view; + +create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); + +create view post_view as +with all_post as ( + select + pa.* + from post_aggregates_view pa +) +select +ap.*, +u.id as user_id, +coalesce(pl.score, 0) as my_vote, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; + +create view post_mview as +with all_post as ( + select + pa.* + from post_aggregates_mview pa +) +select +ap.*, +u.id as user_id, +coalesce(pl.score, 0) as my_vote, +(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, +(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, +(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved +from user_ u +cross join all_post ap +left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id + +union all + +select +ap.*, +null as user_id, +null as my_vote, +null as subscribed, +null as read, +null as saved +from all_post ap +; + + diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 155c706af05..e4fdfee6191 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -18,7 +18,8 @@ use crate::db::user_mention_view::*; use crate::db::user_view::*; use crate::db::*; use crate::{ - extract_usernames, naive_from_unix, naive_now, remove_slurs, slur_check, slurs_vec_to_str, + extract_usernames, fetch_iframely_and_pictshare_data, naive_from_unix, naive_now, remove_slurs, + slur_check, slurs_vec_to_str, }; use diesel::PgConnection; use failure::Error; diff --git a/server/src/api/post.rs b/server/src/api/post.rs index 00bf8e11480..fb022589195 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -110,6 +110,10 @@ impl Perform for Oper { return Err(APIError::err("site_ban").into()); } + // Fetch Iframely and Pictshare cached image + let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) = + fetch_iframely_and_pictshare_data(data.url.to_owned()); + let post_form = PostForm { name: data.name.to_owned(), url: data.url.to_owned(), @@ -122,6 +126,10 @@ impl Perform for Oper { locked: None, stickied: None, updated: None, + embed_title: iframely_title, + embed_description: iframely_description, + embed_html: iframely_html, + thumbnail_url: pictshare_thumbnail, }; let inserted_post = match Post::create(&conn, &post_form) { @@ -353,6 +361,10 @@ impl Perform for Oper { return Err(APIError::err("site_ban").into()); } + // Fetch Iframely and Pictshare cached image + let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) = + fetch_iframely_and_pictshare_data(data.url.to_owned()); + let post_form = PostForm { name: data.name.to_owned(), url: data.url.to_owned(), @@ -365,6 +377,10 @@ impl Perform for Oper { locked: data.locked.to_owned(), stickied: data.stickied.to_owned(), updated: Some(naive_now()), + embed_title: iframely_title, + embed_description: iframely_description, + embed_html: iframely_html, + thumbnail_url: pictshare_thumbnail, }; let _updated_post = match Post::update(&conn, data.edit_id, &post_form) { diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 1d332b906a4..f731389537a 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -883,6 +883,10 @@ impl Perform for Oper { locked: None, stickied: None, updated: Some(naive_now()), + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let _updated_post = match Post::update(&conn, post.id, &post_form) { diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index c5a0b2f0298..e224e259163 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -83,6 +83,10 @@ mod tests { nsfw: false, deleted: false, updated: None, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let page = post.as_page(); diff --git a/server/src/db/comment.rs b/server/src/db/comment.rs index efba07a5186..c9bfbac6c31 100644 --- a/server/src/db/comment.rs +++ b/server/src/db/comment.rs @@ -216,6 +216,10 @@ mod tests { stickied: None, updated: None, nsfw: false, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); diff --git a/server/src/db/comment_view.rs b/server/src/db/comment_view.rs index ff915d5e5cc..85b41d82640 100644 --- a/server/src/db/comment_view.rs +++ b/server/src/db/comment_view.rs @@ -480,6 +480,10 @@ mod tests { stickied: None, updated: None, nsfw: false, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); diff --git a/server/src/db/moderator.rs b/server/src/db/moderator.rs index 4fd532afdbe..a8c3df4f15b 100644 --- a/server/src/db/moderator.rs +++ b/server/src/db/moderator.rs @@ -506,6 +506,10 @@ mod tests { stickied: None, updated: None, nsfw: false, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); diff --git a/server/src/db/post.rs b/server/src/db/post.rs index 9e7a43410bd..ffde14d3d70 100644 --- a/server/src/db/post.rs +++ b/server/src/db/post.rs @@ -17,6 +17,10 @@ pub struct Post { pub deleted: bool, pub nsfw: bool, pub stickied: bool, + pub embed_title: Option, + pub embed_description: Option, + pub embed_html: Option, + pub thumbnail_url: Option, } #[derive(Insertable, AsChangeset, Clone)] @@ -33,6 +37,10 @@ pub struct PostForm { pub deleted: Option, pub nsfw: bool, pub stickied: Option, + pub embed_title: Option, + pub embed_description: Option, + pub embed_html: Option, + pub thumbnail_url: Option, } impl Crud for Post { @@ -229,6 +237,10 @@ mod tests { stickied: None, nsfw: false, updated: None, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); @@ -247,6 +259,10 @@ mod tests { nsfw: false, deleted: false, updated: None, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; // Post Like diff --git a/server/src/db/post_view.rs b/server/src/db/post_view.rs index 3f385077fab..f48f4f680cc 100644 --- a/server/src/db/post_view.rs +++ b/server/src/db/post_view.rs @@ -17,9 +17,54 @@ table! { updated -> Nullable, deleted -> Bool, nsfw -> Bool, + stickied -> Bool, + embed_title -> Nullable, + embed_description -> Nullable, + embed_html -> Nullable, + thumbnail_url -> Nullable, banned -> Bool, banned_from_community -> Bool, + creator_name -> Varchar, + creator_avatar -> Nullable, + community_name -> Varchar, + community_removed -> Bool, + community_deleted -> Bool, + community_nsfw -> Bool, + number_of_comments -> BigInt, + score -> BigInt, + upvotes -> BigInt, + downvotes -> BigInt, + hot_rank -> Int4, + newest_activity_time -> Timestamp, + user_id -> Nullable, + my_vote -> Nullable, + subscribed -> Nullable, + read -> Nullable, + saved -> Nullable, + } +} + +table! { + post_mview (id) { + id -> Int4, + name -> Varchar, + url -> Nullable, + body -> Nullable, + creator_id -> Int4, + community_id -> Int4, + removed -> Bool, + locked -> Bool, + published -> Timestamp, + updated -> Nullable, + deleted -> Bool, + nsfw -> Bool, stickied -> Bool, + embed_title -> Nullable, + embed_description -> Nullable, + embed_html -> Nullable, + thumbnail_url -> Nullable, + banned -> Bool, + banned_from_community -> Bool, creator_name -> Varchar, creator_avatar -> Nullable, community_name -> Varchar, @@ -57,9 +102,13 @@ pub struct PostView { pub updated: Option, pub deleted: bool, pub nsfw: bool, + pub stickied: bool, + pub embed_title: Option, + pub embed_description: Option, + pub embed_html: Option, + pub thumbnail_url: Option, pub banned: bool, pub banned_from_community: bool, - pub stickied: bool, pub creator_name: String, pub creator_avatar: Option, pub community_name: String, @@ -79,44 +128,6 @@ pub struct PostView { pub saved: Option, } -// The faked schema since diesel doesn't do views -table! { - post_mview (id) { - id -> Int4, - name -> Varchar, - url -> Nullable, - body -> Nullable, - creator_id -> Int4, - community_id -> Int4, - removed -> Bool, - locked -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - nsfw -> Bool, - banned -> Bool, - banned_from_community -> Bool, - stickied -> Bool, - creator_name -> Varchar, - creator_avatar -> Nullable, - community_name -> Varchar, - community_removed -> Bool, - community_deleted -> Bool, - community_nsfw -> Bool, - number_of_comments -> BigInt, - score -> BigInt, - upvotes -> BigInt, - downvotes -> BigInt, - hot_rank -> Int4, - newest_activity_time -> Timestamp, - user_id -> Nullable, - my_vote -> Nullable, - subscribed -> Nullable, - read -> Nullable, - saved -> Nullable, - } -} - pub struct PostQueryBuilder<'a> { conn: &'a PgConnection, query: BoxedQuery<'a, Pg>, @@ -394,6 +405,10 @@ mod tests { stickied: None, updated: None, nsfw: false, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); @@ -454,6 +469,10 @@ mod tests { read: None, saved: None, nsfw: false, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let expected_post_listing_with_user = PostView { @@ -489,6 +508,10 @@ mod tests { read: None, saved: None, nsfw: false, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let read_post_listings_with_user = PostQueryBuilder::create(&conn) diff --git a/server/src/db/user_mention.rs b/server/src/db/user_mention.rs index 3b10fd0ff42..0cf257955e7 100644 --- a/server/src/db/user_mention.rs +++ b/server/src/db/user_mention.rs @@ -132,6 +132,10 @@ mod tests { stickied: None, updated: None, nsfw: false, + embed_title: None, + embed_description: None, + embed_html: None, + thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 3e22585de7b..60addd27240 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -33,14 +33,17 @@ pub mod websocket; use crate::settings::Settings; use chrono::{DateTime, NaiveDateTime, Utc}; +use chttp::prelude::*; use lettre::smtp::authentication::{Credentials, Mechanism}; use lettre::smtp::extension::ClientId; use lettre::smtp::ConnectionReuseParameters; use lettre::{ClientSecurity, SmtpClient, Transport}; use lettre_email::Email; +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use regex::{Regex, RegexBuilder}; +use serde::Deserialize; pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime { DateTime::::from_utc(ndt, Utc) @@ -143,6 +146,77 @@ pub fn send_email( } } +#[derive(Deserialize, Debug)] +pub struct IframelyResponse { + title: Option, + description: Option, + thumbnail_url: Option, + html: Option, +} + +pub fn fetch_iframely(url: &str) -> Result { + let fetch_url = format!("http://lemmy_iframely:8061/oembed?url={}", url); + let text = chttp::get(&fetch_url)?.text()?; + let res: IframelyResponse = serde_json::from_str(&text)?; + Ok(res) +} + +#[derive(Deserialize, Debug)] +pub struct PictshareResponse { + status: String, + url: String, +} + +pub fn fetch_pictshare(image_url: &str) -> Result { + let fetch_url = format!( + "http://lemmy_pictshare/api/geturl.php?url={}", + utf8_percent_encode(image_url, NON_ALPHANUMERIC) + ); + let text = chttp::get(&fetch_url)?.text()?; + let res: PictshareResponse = serde_json::from_str(&text)?; + Ok(res) +} + +fn fetch_iframely_and_pictshare_data( + url: Option, +) -> ( + Option, + Option, + Option, + Option, +) { + // Fetch iframely data + let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = match url { + Some(url) => match fetch_iframely(&url) { + Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), + Err(e) => { + eprintln!("iframely err: {}", e); + (None, None, None, None) + } + }, + None => (None, None, None, None), + }; + + // Fetch pictshare thumbnail + let pictshare_thumbnail = match iframely_thumbnail_url { + Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) { + Ok(res) => Some(res.url), + Err(e) => { + eprintln!("pictshare err: {}", e); + None + } + }, + None => None, + }; + + ( + iframely_title, + iframely_description, + iframely_html, + pictshare_thumbnail, + ) +} + #[cfg(test)] mod tests { use crate::{extract_usernames, is_email_regex, remove_slurs, slur_check, slurs_vec_to_str}; @@ -188,6 +262,21 @@ mod tests { assert_eq!(usernames, expected); } + // These helped with testing + // #[test] + // fn test_iframely() { + // let res = fetch_iframely("https://www.redspark.nu/?p=15341"); + // assert!(res.is_ok()); + // } + + // #[test] + // fn test_pictshare() { + // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg"); + // assert!(res.is_ok()); + // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu"); + // assert!(res_other.is_err()); + // } + // #[test] // fn test_send_email() { // let result = send_email("not a subject", "test_email@gmail.com", "ur user", "

HI there

"); diff --git a/server/src/schema.rs b/server/src/schema.rs index 5330ed070de..d9449fef97a 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -207,6 +207,10 @@ table! { deleted -> Bool, nsfw -> Bool, stickied -> Bool, + embed_title -> Nullable, + embed_description -> Nullable, + embed_html -> Nullable, + thumbnail_url -> Nullable, } } diff --git a/ui/src/components/iframely-card.tsx b/ui/src/components/iframely-card.tsx index 31929eafe24..0d56a0b6bfd 100644 --- a/ui/src/components/iframely-card.tsx +++ b/ui/src/components/iframely-card.tsx @@ -1,10 +1,10 @@ import { Component, linkEvent } from 'inferno'; -import { FramelyData } from '../interfaces'; +import { Post } from '../interfaces'; import { mdToHtml } from '../utils'; import { i18n } from '../i18next'; interface FramelyCardProps { - iframely: FramelyData; + post: Post; } interface FramelyCardState { @@ -25,18 +25,18 @@ export class IFramelyCard extends Component< } render() { - let iframely = this.props.iframely; + let post = this.props.post; return ( <> - {iframely.title && !this.state.expanded && ( + {post.embed_title && !this.state.expanded && (
- - {iframely.title} + + {post.embed_title}
@@ -44,14 +44,14 @@ export class IFramelyCard extends Component< - {new URL(iframely.url).hostname} + {new URL(post.url).hostname} - {iframely.html && ( + {post.embed_html && ( )} - {iframely.description && ( + {post.embed_description && (
)}
@@ -75,7 +75,7 @@ export class IFramelyCard extends Component< {this.state.expanded && (
)} diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx index 0d07dca5132..8ced73f02fd 100644 --- a/ui/src/components/inbox.tsx +++ b/ui/src/components/inbox.tsx @@ -28,6 +28,7 @@ import { saveCommentRes, createCommentLikeRes, commentsToFlatNodes, + setupTippy, } from '../utils'; import { CommentNodes } from './comment-nodes'; import { PrivateMessage } from './private-message'; @@ -333,18 +334,21 @@ export class Inbox extends Component { this.sendUnreadCount(); window.scrollTo(0, 0); this.setState(this.state); + setupTippy(); } else if (res.op == UserOperation.GetUserMentions) { let data = res.data as GetUserMentionsResponse; this.state.mentions = data.mentions; this.sendUnreadCount(); window.scrollTo(0, 0); this.setState(this.state); + setupTippy(); } else if (res.op == UserOperation.GetPrivateMessages) { let data = res.data as PrivateMessagesResponse; this.state.messages = data.messages; this.sendUnreadCount(); window.scrollTo(0, 0); this.setState(this.state); + setupTippy(); } else if (res.op == UserOperation.EditPrivateMessage) { let data = res.data as PrivateMessageResponse; let found: PrivateMessageI = this.state.messages.find( @@ -387,6 +391,7 @@ export class Inbox extends Component { } this.sendUnreadCount(); this.setState(this.state); + setupTippy(); } else if (res.op == UserOperation.EditUserMention) { let data = res.data as UserMentionResponse; @@ -430,6 +435,7 @@ export class Inbox extends Component { let data = res.data as CommentResponse; saveCommentRes(data, this.state.replies); this.setState(this.state); + setupTippy(); } else if (res.op == UserOperation.CreateCommentLike) { let data = res.data as CommentResponse; createCommentLikeRes(data, this.state.replies); diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 5f05bd588ac..47920b9b4ec 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -497,7 +497,7 @@ export class PostForm extends Component { }) .then(res => res.json()) .then(res => { - let url = `${window.location.origin}/pictshare/${res.url}`; + let url = `${window.location.origin}/pictshare/${encodeURI(res.url)}`; if (res.filetype == 'mp4') { url += '/raw'; } diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index 2d09e90e58b..d087d14199d 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -15,7 +15,6 @@ import { AddAdminForm, TransferSiteForm, TransferCommunityForm, - FramelyData, } from '../interfaces'; import { MomentTime } from './moment-time'; import { PostForm } from './post-form'; @@ -29,7 +28,7 @@ import { getUnixTime, pictshareAvatarThumbnail, showAvatars, - imageThumbnailer, + pictshareImage, setupTippy, } from '../utils'; import { i18n } from '../i18next'; @@ -51,9 +50,6 @@ interface PostListingState { score: number; upvotes: number; downvotes: number; - url: string; - iframely: FramelyData; - thumbnail: string; } interface PostListingProps { @@ -82,9 +78,6 @@ export class PostListing extends Component { score: this.props.post.score, upvotes: this.props.post.upvotes, downvotes: this.props.post.downvotes, - url: this.props.post.url, - iframely: null, - thumbnail: null, }; constructor(props: any, context: any) { @@ -95,11 +88,6 @@ export class PostListing extends Component { this.handlePostDisLike = this.handlePostDisLike.bind(this); this.handleEditPost = this.handleEditPost.bind(this); this.handleEditCancel = this.handleEditCancel.bind(this); - - if (this.state.url) { - this.setThumbnail(); - this.fetchIframely(); - } } componentWillReceiveProps(nextProps: PostListingProps) { @@ -107,18 +95,6 @@ export class PostListing extends Component { this.state.upvotes = nextProps.post.upvotes; this.state.downvotes = nextProps.post.downvotes; this.state.score = nextProps.post.score; - - if (nextProps.post.url !== this.state.url) { - this.state.url = nextProps.post.url; - if (this.state.url) { - this.setThumbnail(); - this.fetchIframely(); - } else { - this.state.iframely = null; - this.state.thumbnail = null; - } - } - this.setState(this.state); } @@ -147,9 +123,11 @@ export class PostListing extends Component { return (
- {this.state.url && this.props.showBody && this.state.iframely && ( - - )} + {this.props.post.url && + this.props.showBody && + this.props.post.embed_title && ( + + )} {this.props.showBody && this.props.post.body && ( <> {this.state.viewSource ? ( @@ -167,61 +145,90 @@ export class PostListing extends Component { ); } - imgThumb() { + imgThumb(src: string) { let post = this.props.post; return ( ); } + getImage(thumbnail: boolean = false) { + let post = this.props.post; + if (isImage(post.url)) { + if (post.url.includes('pictshare')) { + return pictshareImage(post.url, thumbnail); + } else { + return post.url; + } + } else if (post.thumbnail_url) { + return pictshareImage(post.thumbnail_url, thumbnail); + } + } + thumbnail() { let post = this.props.post; - if (isImage(this.state.url)) { + if (isImage(post.url)) { return ( - {this.imgThumb()} + {this.imgThumb(this.getImage(true))} ); - } else if (this.state.thumbnail) { + } else if (post.thumbnail_url) { return ( - {this.imgThumb()} + {this.imgThumb(this.getImage(true))} ); - } else if (this.state.url && !this.state.thumbnail) { - return ( - - - - - - ); + } else if (post.url) { + if (isVideo(post.url)) { + return ( +
+ +
+ ); + } else { + return ( + + + + + + ); + } } else { return ( {
{this.thumbnail()}
)} - {this.state.url && isVideo(this.state.url) && ( - - )}
@@ -300,12 +294,12 @@ export class PostListing extends Component {
- {this.props.showBody && this.state.url ? ( + {this.props.showBody && post.url ? ( {post.name} @@ -319,25 +313,23 @@ export class PostListing extends Component { )}
- {this.state.url && - !( - new URL(this.state.url).hostname == window.location.hostname - ) && ( + {post.url && + !(new URL(post.url).hostname == window.location.hostname) && ( - {new URL(this.state.url).hostname} + {new URL(post.url).hostname} )} - {this.state.thumbnail && ( + {(isImage(post.url) || this.props.post.thumbnail_url) && ( <> {!this.state.imageExpanded ? ( { >
@@ -999,53 +991,6 @@ export class PostListing extends Component { ); } - fetchIframely() { - fetch(`/iframely/oembed?url=${this.state.url}`) - .then(res => res.json()) - .then(res => { - this.state.iframely = res; - this.setState(this.state); - - // Store and fetch the image in pictshare - if ( - this.state.iframely.thumbnail_url && - isImage(this.state.iframely.thumbnail_url) - ) { - fetch( - `/pictshare/api/geturl.php?url=${this.state.iframely.thumbnail_url}` - ) - .then(res => res.json()) - .then(res => { - if (res.status == 'ok') { - let url = `${window.location.origin}/pictshare/${res.url}`; - if (res.filetype == 'mp4') { - url += '/raw'; - } - this.state.thumbnail = url; - this.setState(this.state); - } else { - console.error( - `Couldn't cache pictshare url: ${this.state.iframely.thumbnail_url}` - ); - console.error(res); - } - }); - } - }) - .catch(error => { - console.error(`Iframely service not set up properly. ${error}`); - }); - } - - setThumbnail() { - let simpleImg = isImage(this.state.url); - if (simpleImg) { - this.state.thumbnail = this.state.url; - } else { - this.state.thumbnail = null; - } - } - handlePostLike(i: PostListing) { let new_vote = i.state.my_vote == 1 ? 0 : 1; @@ -1141,8 +1086,10 @@ export class PostListing extends Component { get crossPostParams(): string { let params = `?title=${this.props.post.name}`; - if (this.state.url) { - params += `&url=${this.state.url}`; + let post = this.props.post; + + if (post.url) { + params += `&url=${post.url}`; } if (this.props.post.body) { params += `&body=${this.props.post.body}`; diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index eb58ca11613..0eeeac06d88 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -156,6 +156,10 @@ export interface Post { deleted: boolean; locked: boolean; stickied: boolean; + embed_title?: string; + embed_description?: string; + embed_html?: string; + thumbnail_url?: string; nsfw: boolean; banned: boolean; banned_from_community: boolean; @@ -877,18 +881,3 @@ export interface WebSocketJsonResponse { error?: string; reconnect?: boolean; } - -export interface FramelyData { - url: string; - type: string; - version?: string; - title: string; - author?: string; - author_url?: string; - provider_name?: string; - thumbnail_url?: string; - thumbnail_width?: number; - thumbnail_height?: number; - description?: string; - html?: string; -} diff --git a/ui/src/utils.ts b/ui/src/utils.ts index ea09cf9639a..27dbfb500b1 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -404,15 +404,22 @@ export function showAvatars(): boolean { ); } -/// Converts to image thumbnail (only supports pictshare currently) -export function imageThumbnailer(url: string): string { - let split = url.split('pictshare'); - if (split.length > 1) { - let out = `${split[0]}pictshare/192${split[1]}`; - return out; - } else { - return url; +// Converts to image thumbnail +export function pictshareImage( + hash: string, + thumbnail: boolean = false +): string { + let root = `/pictshare`; + + // Necessary for other servers / domains + if (hash.includes('pictshare')) { + let split = hash.split('/pictshare/'); + root = `${split[0]}/pictshare`; + hash = split[1]; } + + let out = `${root}/${thumbnail ? '192/' : ''}${hash}`; + return out; } export function isCommentType(item: Comment | PrivateMessage): item is Comment {