From 81aa7410de2330f993336a65741427d2387076c7 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 19 Sep 2023 18:55:53 -0400 Subject: [PATCH] Import a questionably refactored version of file-service --- file-service/Cargo.lock | 1404 +++++++++++++++++ file-service/Cargo.toml | 25 + file-service/authdb.json | 1 + file-service/fixtures/.metadata/.placeholder | 0 .../fixtures/.thumbnails/.placeholder | 0 file-service/fixtures/rawr.png | Bin 0 -> 23777 bytes file-service/src/cookies.rs | 61 + file-service/src/lib/error.rs | 31 + file-service/src/lib/file.rs | 151 ++ file-service/src/lib/fileinfo.rs | 160 ++ file-service/src/lib/mod.rs | 56 + file-service/src/lib/thumbnail.rs | 82 + file-service/src/lib/utils.rs | 17 + file-service/src/main.rs | 341 ++++ file-service/src/middleware/authentication.rs | 51 + file-service/src/middleware/logging.rs | 16 + file-service/src/middleware/mod.rs | 6 + file-service/src/middleware/restform.rs | 34 + file-service/templates/file.html | 15 + file-service/templates/index.html | 54 + file-service/templates/script.js | 10 + file-service/templates/style.css | 103 ++ 22 files changed, 2618 insertions(+) create mode 100644 file-service/Cargo.lock create mode 100644 file-service/Cargo.toml create mode 100644 file-service/authdb.json create mode 100644 file-service/fixtures/.metadata/.placeholder create mode 100644 file-service/fixtures/.thumbnails/.placeholder create mode 100644 file-service/fixtures/rawr.png create mode 100644 file-service/src/cookies.rs create mode 100644 file-service/src/lib/error.rs create mode 100644 file-service/src/lib/file.rs create mode 100644 file-service/src/lib/fileinfo.rs create mode 100644 file-service/src/lib/mod.rs create mode 100644 file-service/src/lib/thumbnail.rs create mode 100644 file-service/src/lib/utils.rs create mode 100644 file-service/src/main.rs create mode 100644 file-service/src/middleware/authentication.rs create mode 100644 file-service/src/middleware/logging.rs create mode 100644 file-service/src/middleware/mod.rs create mode 100644 file-service/src/middleware/restform.rs create mode 100644 file-service/templates/file.html create mode 100644 file-service/templates/index.html create mode 100644 file-service/templates/script.js create mode 100644 file-service/templates/style.css diff --git a/file-service/Cargo.lock b/file-service/Cargo.lock new file mode 100644 index 0000000..2e99d09 --- /dev/null +++ b/file-service/Cargo.lock @@ -0,0 +1,1404 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem 0.3.3", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bodyparser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f023abfa58aad6f6bc4ae0630799e24d5ee0ab8bb2e49f651d9b1f9aa4f52f30" +dependencies = [ + "iron", + "persistent", + "plugin", + "serde 1.0.110", + "serde_json", +] + +[[package]] +name = "buf_redux" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9279646319ff816b05fb5897883ece50d7d854d12b59992683d4f8a71b0f949" +dependencies = [ + "memchr 1.0.2", + "safemem 0.2.0", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytemuck" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cc" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "serde 1.0.110", + "time", +] + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "color_quant" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg 1.0.0", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg 1.0.0", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "deflate" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "file-service" +version = "0.1.0" +dependencies = [ + "chrono", + "hex-string", + "image", + "iron", + "logger", + "mime 0.3.16", + "mime_guess 2.0.3", + "mustache", + "orizentic", + "params", + "router", + "serde 1.0.110", + "serde_json", + "sha2", + "thiserror", + "uuid 0.4.0", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "gif" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" +dependencies = [ + "color_quant", + "lzw", +] + +[[package]] +name = "hermit-abi" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +dependencies = [ + "libc", +] + +[[package]] +name = "hex-string" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848ec2dd093df965a34b434580d94852197fc83feac5b2c1962399bbf2cb4f0b" + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +dependencies = [ + "base64", + "httparse", + "language-tags", + "log 0.3.9", + "mime 0.2.6", + "num_cpus", + "time", + "traitobject", + "typeable", + "unicase 1.4.2", + "url", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d534e95ad8b9d5aa614322d02352b4f1bf962254adcf02ac6f2def8be18498e8" +dependencies = [ + "bytemuck", + "byteorder", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational 0.2.4", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "iron" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d308ca2d884650a8bf9ed2ff4cb13fbb2207b71f64cda11dc9b892067295e8" +dependencies = [ + "hyper", + "log 0.3.9", + "mime_guess 1.8.8", + "modifier", + "num_cpus", + "plugin", + "typemap", + "url", +] + +[[package]] +name = "itertools" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "jpeg-decoder" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704" +dependencies = [ + "byteorder", + "rayon", +] + +[[package]] +name = "jsonwebtoken" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d438ea707d465c230305963b67f8357a1d56fcfad9434797d7cb1c46c2e41df" +dependencies = [ + "base64", + "chrono", + "ring", + "serde 1.0.110", + "serde_derive", + "serde_json", + "untrusted", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" + +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.8", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c9172cb4c2f6c52117e25570983edcbb322f130b1031ae5d5d6b1abe7eeb493" +dependencies = [ + "iron", + "log 0.3.9", + "time", +] + +[[package]] +name = "lzw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memoffset" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "1.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" +dependencies = [ + "mime 0.2.6", + "phf", + "phf_codegen", + "unicase 1.4.2", +] + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime 0.3.16", + "unicase 2.6.0", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "modifier" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" + +[[package]] +name = "multipart" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92f54eb45230c3aa20864ccf0c277eeaeadcf5e437e91731db498dbf7fbe0ec6" +dependencies = [ + "buf_redux", + "httparse", + "log 0.3.9", + "mime 0.2.6", + "mime_guess 1.8.8", + "rand 0.3.23", + "safemem 0.2.0", + "tempdir", + "twoway", +] + +[[package]] +name = "mustache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51956ef1c5d20a1384524d91e616fb44dfc7d8f249bf696d49c97dd3289ecab5" +dependencies = [ + "log 0.3.9", + "serde 1.0.110", +] + +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.1.42", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.4.6", + "rustc-serialize", +] + +[[package]] +name = "num-complex" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" +dependencies = [ + "num-traits", + "rustc-serialize", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "rustc-serialize", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "orizentic" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f4af9d9002d3b129cdad6ebdb5b745b2b7d5e8c1c85c8addd7b447695af6a3" +dependencies = [ + "chrono", + "clap", + "itertools", + "jsonwebtoken", + "serde 1.0.110", + "serde_derive", + "serde_json", + "uuid 0.6.5", + "version_check 0.1.5", + "yaml-rust", +] + +[[package]] +name = "params" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c789fdad2cfdaa551ea0e3a9eadb74c5d634968a9fb3a8c767d89be470d21589" +dependencies = [ + "bodyparser", + "iron", + "multipart", + "num", + "plugin", + "serde_json", + "tempdir", + "urlencoded", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "persistent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8fa0009c4f3d350281309909c618abddf10bb7e3145f28410782f6a5ec74c5" +dependencies = [ + "iron", + "plugin", +] + +[[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +dependencies = [ + "phf_shared", + "rand 0.6.5", +] + +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher", + "unicase 1.4.2", +] + +[[package]] +name = "plugin" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" +dependencies = [ + "typemap", +] + +[[package]] +name = "png" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ccdd66f6fe4b2433b07e4728e9a013e43233120427046e93ceb709c3a439bf" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42934bc9c8ab0d3b273a16d8551c8f0fcff46be73276ca083ec2414c15c4ba5e" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rayon" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" +dependencies = [ + "autocfg 1.0.0", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4db68a2e35f3497146b7e4563df7d4773a2433230c5e4b448328e31740458a" +dependencies = [ + "cc", + "lazy_static", + "libc", + "untrusted", +] + +[[package]] +name = "route-recognizer" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea509065eb0b3c446acdd0102f0d46567dc30902dc0be91d6552035d92b0f4f8" + +[[package]] +name = "router" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc63b6f3b8895b0d04e816b2b1aa58fdba2d5acca3cbb8f0ab8e017347d57397" +dependencies = [ + "iron", + "route-recognizer", + "url", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "ryu" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" + +[[package]] +name = "safemem" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af" + +[[package]] +name = "serde" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.110", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" + +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" +dependencies = [ + "byteorder", + "lzw", + "miniz_oxide", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr 2.3.3", +] + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" +dependencies = [ + "unsafe-any", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +dependencies = [ + "version_check 0.1.5", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check 0.9.1", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +dependencies = [ + "smallvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "unsafe-any" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" +dependencies = [ + "traitobject", +] + +[[package]] +name = "untrusted" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "urlencoded" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a52f50139118b60ae91af08bf15ed158817d34b91b9d24c11ffbe21195d33e3" +dependencies = [ + "bodyparser", + "iron", + "plugin", + "url", +] + +[[package]] +name = "uuid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cfec50b0842181ba6e713151b72f4ec84a6a7e2c9c8a8a3ffc37bb1cd16b231" +dependencies = [ + "rand 0.3.23", + "serde 0.9.15", +] + +[[package]] +name = "uuid" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1436e58182935dcd9ce0add9ea0b558e8a87befe01c1a301e6020aeb0876363" +dependencies = [ + "cfg-if", + "rand 0.4.6", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +dependencies = [ + "linked-hash-map", +] diff --git a/file-service/Cargo.toml b/file-service/Cargo.toml new file mode 100644 index 0000000..fa1be72 --- /dev/null +++ b/file-service/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "file-service" +version = "0.1.0" +authors = ["savanni@luminescent-dreams.com"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4", features = ["serde"] } +hex-string = "0.1.0" +iron = "0.6.1" +logger = "*" +mime = "0.3.16" +mime_guess = "2.0.3" +mustache = "0.9.0" +orizentic = "1.0.0" +params = "*" +router = "*" +serde_json = "*" +serde = { version = "1.0", features = ["derive"] } +sha2 = "0.8.2" +uuid = { version = "0.4", features = ["serde", "v4"] } +thiserror = "1.0.20" +image = "0.23.5" \ No newline at end of file diff --git a/file-service/authdb.json b/file-service/authdb.json new file mode 100644 index 0000000..3bd3ffb --- /dev/null +++ b/file-service/authdb.json @@ -0,0 +1 @@ +[{"jti":"ac3a46c6-3fa1-4d0a-af12-e7d3fefdc878","aud":"savanni","exp":1621351436,"iss":"savanni","iat":1589729036,"sub":"https://savanni.luminescent-dreams.com/file-service/","perms":["admin"]}] \ No newline at end of file diff --git a/file-service/fixtures/.metadata/.placeholder b/file-service/fixtures/.metadata/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/file-service/fixtures/.thumbnails/.placeholder b/file-service/fixtures/.thumbnails/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/file-service/fixtures/rawr.png b/file-service/fixtures/rawr.png new file mode 100644 index 0000000000000000000000000000000000000000..edaf3239e74ac0e4f61e6856ba4333063c3fbcba GIT binary patch literal 23777 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

Z7$1qzu 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 WJpW!ZQ0$Tb0000 for Cookie { + fn from(s: &str) -> Cookie { + let parts: Vec<&str> = s.split("=").collect(); + Cookie { + name: String::from(parts[0]), + value: String::from(parts[1]), + } + } +} + +impl From<&String> for Cookie { + fn from(s: &String) -> Cookie { + Cookie::from(s.as_str()) + } +} + +impl From for Cookie { + fn from(s: String) -> Cookie { + Cookie::from(s.as_str()) + } +} + +#[derive(Debug)] +pub struct CookieJar(HashMap); + +impl CookieJar { + pub fn new() -> CookieJar { + CookieJar(HashMap::new()) + } + + pub fn add_cookie(&mut self, name: String, value: Cookie) { + self.0.insert(name, value); + } + + pub fn lookup(&self, name: &str) -> Option<&Cookie> { + self.0.get(name) + } +} + +// Some(Cookie(["auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhYzNhNDZjNi0zZmExLTRkMGEtYWYxMi1lN2QzZmVmZGM4NzgiLCJhdWQiOiJzYXZhbm5pIiwiZXhwIjoxNjIxMzUxNDM2LCJpc3MiOiJzYXZhbm5pIiwiaWF0IjoxNTg5NzI5MDM2LCJzdWIiOiJodHRwczovL3NhdmFubmkubHVtaW5lc2NlbnQtZHJlYW1zLmNvbS9maWxlLXNlcnZpY2UvIiwicGVybXMiOlsiYWRtaW4iXX0.8zjAbZ7Ut0d6EcDeyik39GKhXvH4qkMDdaiQVNKWiuM"])) +impl From<&headers::Cookie> for CookieJar { + fn from(c: &headers::Cookie) -> CookieJar { + let jar = CookieJar::new(); + + let headers::Cookie(cs) = c; + cs.iter().fold(jar, |mut jar, c_| { + let cookie = Cookie::from(c_); + jar.add_cookie(cookie.name.clone(), cookie); + jar + }) + } +} diff --git a/file-service/src/lib/error.rs b/file-service/src/lib/error.rs new file mode 100644 index 0000000..53dc55f --- /dev/null +++ b/file-service/src/lib/error.rs @@ -0,0 +1,31 @@ +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("not implemented")] + NotImplemented, + + #[error("file not found: `{0}`")] + FileNotFound(PathBuf), + + #[error("file is not an image: `{0}`")] + NotAnImage(PathBuf), + + #[error("path is not a file: `{0}`")] + NotAFile(PathBuf), + + #[error("Image loading error")] + ImageError(#[from] image::ImageError), + + #[error("IO error")] + IOError(#[from] std::io::Error), + + #[error("JSON error")] + JSONError(#[from] serde_json::error::Error), + + #[error("UTF8 Error")] + UTF8Error(#[from] std::str::Utf8Error), +} + +pub type Result = std::result::Result; diff --git a/file-service/src/lib/file.rs b/file-service/src/lib/file.rs new file mode 100644 index 0000000..3a085fd --- /dev/null +++ b/file-service/src/lib/file.rs @@ -0,0 +1,151 @@ +use super::error::{Error, Result}; +use super::fileinfo::FileInfo; +use super::thumbnail::Thumbnail; +use std::fs::{copy, read_dir, remove_file}; +use std::path::{Path, PathBuf}; + +#[derive(Debug)] +pub struct File { + info: FileInfo, + tn: Thumbnail, + root: PathBuf, +} + +impl File { + pub fn new( + id: &str, + root: &Path, + temp_path: &PathBuf, + filename: &Option, + ) -> Result { + let mut dest_path = PathBuf::from(root); + dest_path.push(id); + match filename { + Some(fname) => match fname.extension() { + Some(ext) => { + dest_path.set_extension(ext); + () + } + None => (), + }, + None => (), + }; + copy(temp_path, dest_path.clone())?; + let info = FileInfo::from_path(&dest_path)?; + let tn = Thumbnail::from_path(&dest_path)?; + Ok(File { + info, + tn, + root: PathBuf::from(root), + }) + } + + pub fn open(id: &str, root: &Path) -> Result { + let mut file_path = PathBuf::from(root); + file_path.push(id.clone()); + + if !file_path.exists() { + return Err(Error::FileNotFound(file_path)); + } + if !file_path.is_file() { + return Err(Error::NotAFile(file_path)); + } + + let info = match FileInfo::open(id, root) { + Ok(i) => Ok(i), + Err(Error::FileNotFound(_)) => { + let info = FileInfo::from_path(&file_path)?; + info.save(&root)?; + Ok(info) + } + Err(err) => Err(err), + }?; + + let tn = Thumbnail::open(id, root)?; + + Ok(File { + info, + tn, + root: PathBuf::from(root), + }) + } + + pub fn list(root: &Path) -> Vec> { + let dir_iter = read_dir(&root).unwrap(); + dir_iter + .filter(|entry| { + let entry_ = entry.as_ref().unwrap(); + let filename = entry_.file_name(); + !(filename.to_string_lossy().starts_with(".")) + }) + .map(|entry| { + let entry_ = entry.unwrap(); + let id = entry_.file_name().into_string().unwrap(); + File::open(&id, root) + }) + .collect() + } + + pub fn info(&self) -> FileInfo { + self.info.clone() + } + + pub fn thumbnail(&self) -> Thumbnail { + self.tn.clone() + } + + pub fn stream(&self) -> Result { + let mut path = self.root.clone(); + path.push(self.info.id.clone()); + std::fs::File::open(path).map_err(Error::from) + } + + pub fn delete(&self) -> Result<()> { + let mut path = self.root.clone(); + path.push(self.info.id.clone()); + remove_file(path)?; + self.tn.delete()?; + self.info.delete() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::lib::utils::FileCleanup; + use std::path::{Path, PathBuf}; + + #[test] + fn it_opens_a_file() { + let _md = FileCleanup(PathBuf::from("fixtures/.metadata/rawr.png.json")); + let _tn = FileCleanup(PathBuf::from("fixtures/.thumbnails/rawr.png")); + + File::open("rawr.png", Path::new("fixtures/")).expect("to succeed"); + } + + #[test] + fn it_can_return_a_thumbnail() { + let f = File::open("rawr.png", Path::new("fixtures/")).expect("to succeed"); + assert_eq!( + f.thumbnail(), + Thumbnail { + id: String::from("rawr.png"), + root: PathBuf::from("fixtures/"), + }, + ); + } + + #[test] + fn it_can_return_a_file_stream() { + let f = File::open("rawr.png", Path::new("fixtures/")).expect("to succeed"); + f.stream().expect("to succeed"); + } + + #[test] + fn it_raises_an_error_when_file_not_found() { + match File::open("garbage", Path::new("fixtures/")) { + Err(Error::FileNotFound(_)) => assert!(true), + _ => assert!(false), + } + } +} diff --git a/file-service/src/lib/fileinfo.rs b/file-service/src/lib/fileinfo.rs new file mode 100644 index 0000000..ee8350e --- /dev/null +++ b/file-service/src/lib/fileinfo.rs @@ -0,0 +1,160 @@ +use chrono::prelude::*; +use hex_string::HexString; +use serde::{Deserialize, Serialize}; +use serde_json; +use sha2::{Digest, Sha256}; +use std::fs::remove_file; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +use super::error::{Error, Result}; +use super::utils::append_extension; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FileInfo { + pub id: String, + pub size: u64, + pub created: DateTime, + pub file_type: String, + pub hash: String, + + #[serde(skip)] + root: PathBuf, +} + +impl FileInfo { + pub fn save(&self, root: &Path) -> Result<()> { + let ser = serde_json::to_string(self).unwrap(); + std::fs::File::create(FileInfo::metadata_path(&self.id, root)) + .and_then(|mut stream| stream.write(ser.as_bytes()).map(|_| (()))) + .map_err(Error::from) + } + + pub fn open(id: &str, root: &Path) -> Result { + let mut buf = Vec::new(); + + let md_path = FileInfo::metadata_path(id, root); + std::fs::File::open(md_path.clone()) + .and_then(|mut stream| stream.read_to_end(&mut buf)) + .map_err(move |err| match err.kind() { + std::io::ErrorKind::NotFound => Error::FileNotFound(md_path), + _ => Error::IOError(err), + })?; + + let str_repr = std::str::from_utf8(&buf)?; + + serde_json::from_str(&str_repr).map_err(Error::from) + } + + pub fn from_path(path: &Path) -> Result { + match (path.is_file(), path.is_dir()) { + (false, false) => Err(Error::FileNotFound(PathBuf::from(path))), + (false, true) => Err(Error::NotAFile(PathBuf::from(path))), + (true, _) => Ok(()), + }?; + + let metadata = path.metadata().map_err(Error::IOError)?; + let id = path + .file_name() + .map(|s| String::from(s.to_string_lossy())) + .ok_or(Error::NotAFile(PathBuf::from(path)))?; + let created = metadata + .created() + .map(|m| DateTime::from(m)) + .map_err(|err| Error::IOError(err))?; + let file_type = String::from( + mime_guess::from_path(path) + .first_or_octet_stream() + .essence_str(), + ); + let hash = FileInfo::hash_file(path)?; + Ok(FileInfo { + id, + size: metadata.len(), + created: created, + file_type, + hash: hash.as_string(), + root: PathBuf::from(path.parent().unwrap()), + }) + } + + fn hash_file(path: &Path) -> Result { + let mut buf = Vec::new(); + let mut file = std::fs::File::open(path).map_err(Error::from)?; + + file.read_to_end(&mut buf).map_err(Error::from)?; + let mut vec = Vec::new(); + vec.extend_from_slice(Sha256::digest(&buf).as_slice()); + Ok(HexString::from_bytes(&vec)) + } + + fn metadata_path(id: &str, root: &Path) -> PathBuf { + let mut path = PathBuf::from(root); + path.push(".metadata"); + path.push(id.clone()); + append_extension(&path, "json") + } + + pub fn delete(&self) -> Result<()> { + let path = FileInfo::metadata_path(&self.id, &self.root); + remove_file(path).map_err(Error::from) + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::lib::utils::FileCleanup; + + #[test] + fn it_generates_information_from_file_path() { + let path = Path::new("fixtures/rawr.png"); + match FileInfo::from_path(&path) { + Ok(FileInfo { + id, + size, + file_type, + hash, + .. + }) => { + assert_eq!(id, "rawr.png"); + assert_eq!(size, 23777); + assert_eq!(file_type, "image/png"); + assert_eq!( + hash, + "b6cd35e113b95d62f53d9cbd27ccefef47d3e324aef01a2db6c0c6d3a43c89ee" + ); + } + Err(err) => { + println!("error loading file path: {}", err); + assert!(false); + } + } + } + + #[test] + fn it_saves_and_loads_metadata() { + let path = Path::new("fixtures/rawr.png"); + let _ = FileCleanup(append_extension(path, "json")); + let info = FileInfo::from_path(&path).unwrap(); + info.save(Path::new("fixtures")).unwrap(); + + assert!(Path::new("fixtures/.metadata/rawr.png.json").is_file()); + + let info_ = FileInfo::open("rawr.png", Path::new("fixtures")).unwrap(); + assert_eq!(info_.id, "rawr.png"); + assert_eq!(info_.size, 23777); + assert_eq!(info_.created, info.created); + assert_eq!(info_.file_type, "image/png"); + assert_eq!(info_.hash, info.hash); + } + + #[test] + fn it_extends_a_file_extension() { + assert_eq!( + append_extension(Path::new("fixtures/rawr.png"), "json"), + Path::new("fixtures/rawr.png.json") + ); + } +} diff --git a/file-service/src/lib/mod.rs b/file-service/src/lib/mod.rs new file mode 100644 index 0000000..40e80c2 --- /dev/null +++ b/file-service/src/lib/mod.rs @@ -0,0 +1,56 @@ +use std::path::{Path, PathBuf}; +use uuid::Uuid; + +mod error; +mod file; +mod fileinfo; +mod thumbnail; +mod utils; + +pub use error::{Error, Result}; +pub use file::File; +pub use fileinfo::FileInfo; +pub use thumbnail::Thumbnail; + +pub struct App { + files_root: PathBuf, +} + +impl App { + pub fn new(files_root: &Path) -> App { + App { + files_root: PathBuf::from(files_root), + } + } + + pub fn list_files(&self) -> Vec> { + File::list(&self.files_root) + } + + pub fn add_file(&mut self, temp_path: &PathBuf, filename: &Option) -> Result { + let id = Uuid::new_v4().hyphenated().to_string(); + File::new(&id, &self.files_root, temp_path, filename) + } + + pub fn delete_file(&mut self, id: String) -> Result<()> { + let f = File::open(&id, &self.files_root)?; + f.delete() + } + + pub fn get_metadata(&self, id: String) -> Result { + FileInfo::open(&id, &self.files_root) + } + + pub fn get_file(&self, id: String) -> Result<(FileInfo, std::fs::File)> { + let f = File::open(&id, &self.files_root)?; + let info = f.info(); + let stream = f.stream()?; + Ok((info, stream)) + } + + pub fn get_thumbnail(&self, id: &str) -> Result<(FileInfo, std::fs::File)> { + let f = File::open(id, &self.files_root)?; + let stream = f.thumbnail().stream()?; + Ok((f.info(), stream)) + } +} diff --git a/file-service/src/lib/thumbnail.rs b/file-service/src/lib/thumbnail.rs new file mode 100644 index 0000000..738b4aa --- /dev/null +++ b/file-service/src/lib/thumbnail.rs @@ -0,0 +1,82 @@ +use image::imageops::FilterType; +use std::fs::remove_file; +use std::path::{Path, PathBuf}; + +use super::error::{Error, Result}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Thumbnail { + pub id: String, + pub root: PathBuf, +} + +impl Thumbnail { + pub fn open(id: &str, root: &Path) -> Result { + let mut source_path = PathBuf::from(root); + source_path.push(id); + + let self_ = Thumbnail { + id: String::from(id), + root: PathBuf::from(root), + }; + + let thumbnail_path = Thumbnail::thumbnail_path(id, root); + if !thumbnail_path.exists() { + let img = image::open(source_path)?; + let tn = img.resize(640, 640, FilterType::Nearest); + tn.save(thumbnail_path)?; + } + + Ok(self_) + } + + pub fn from_path(path: &Path) -> Result { + let id = path + .file_name() + .map(|s| String::from(s.to_string_lossy())) + .ok_or(Error::NotAnImage(PathBuf::from(path)))?; + + let root = path + .parent() + .ok_or(Error::FileNotFound(PathBuf::from(path)))?; + + Thumbnail::open(&id, root) + } + + fn thumbnail_path(id: &str, root: &Path) -> PathBuf { + let mut path = PathBuf::from(root); + path.push(".thumbnails"); + path.push(id.clone()); + path + } + + pub fn stream(&self) -> Result { + let thumbnail_path = Thumbnail::thumbnail_path(&self.id, &self.root); + std::fs::File::open(thumbnail_path.clone()).map_err(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + Error::FileNotFound(thumbnail_path) + } else { + Error::from(err) + } + }) + } + + pub fn delete(&self) -> Result<()> { + let path = Thumbnail::thumbnail_path(&self.id, &self.root); + remove_file(path).map_err(Error::from) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::lib::utils::FileCleanup; + + #[test] + fn it_creates_a_thumbnail_if_one_does_not_exist() { + let _ = FileCleanup(PathBuf::from("fixtures/.thumbnails/rawr.png")); + let _ = + Thumbnail::open("rawr.png", Path::new("fixtures")).expect("thumbnail open must work"); + assert!(Path::new("fixtures/.thumbnails/rawr.png").is_file()); + } +} diff --git a/file-service/src/lib/utils.rs b/file-service/src/lib/utils.rs new file mode 100644 index 0000000..9427daf --- /dev/null +++ b/file-service/src/lib/utils.rs @@ -0,0 +1,17 @@ +use std::path::{Path, PathBuf}; + +pub struct FileCleanup(pub PathBuf); + +impl Drop for FileCleanup { + fn drop(&mut self) { + let _ = std::fs::remove_file(&self.0); + } +} + +pub fn append_extension(path: &Path, extra_ext: &str) -> PathBuf { + let ext_ = match path.extension() { + None => String::from(extra_ext), + Some(ext) => [ext.to_string_lossy(), std::borrow::Cow::from(extra_ext)].join("."), + }; + path.with_extension(ext_) +} diff --git a/file-service/src/main.rs b/file-service/src/main.rs new file mode 100644 index 0000000..67f1401 --- /dev/null +++ b/file-service/src/main.rs @@ -0,0 +1,341 @@ +use iron::headers; +use iron::middleware::Handler; +use iron::modifiers::{Header, Redirect}; +use iron::prelude::*; +use iron::response::BodyReader; +use iron::status; +use mustache::{compile_path, Template}; +use orizentic::{Permissions, ResourceName, Secret}; +use params::{Params, Value}; +use router::Router; +use serde::Serialize; +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; + +mod cookies; +mod lib; +mod middleware; + +use lib::{App, FileInfo}; +use middleware::{Authentication, RestForm}; + +fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool { + let Permissions(perms) = permissions; + ResourceName(String::from( + "https://savanni.luminescent-dreams.com/file-service/", + )) == *resource + && perms.contains(&String::from("admin")) +} + +pub fn compare_etags(info: FileInfo, etag_list: &headers::IfNoneMatch) -> bool { + let current_etag = headers::EntityTag::new(false, info.hash); + match etag_list { + headers::IfNoneMatch::Any => false, + headers::IfNoneMatch::Items(lst) => lst.iter().any(|etag| etag.weak_eq(¤t_etag)), + } +} + +mod files { + use super::*; + + pub struct IndexHandler { + pub app: Arc>, + pub template: Template, + } + + #[derive(Serialize)] + pub enum TemplateFile { + #[serde(rename = "error")] + Error { error: String }, + #[serde(rename = "file")] + File { + id: String, + size: u64, + date: String, + type_: String, + }, + } + + #[derive(Serialize)] + pub struct IndexTemplateParams { + files: Vec, + } + + impl Handler for IndexHandler { + fn handle(&self, req: &mut Request) -> IronResult { + let app = self.app.read().unwrap(); + let m_token = req.extensions.get::(); + match m_token { + Some(token) => { + if token.check_authorizations(is_admin) { + let files: Vec = app + .list_files() + .into_iter() + .map(|entry| match entry { + Ok(file) => TemplateFile::File { + id: file.info().id, + size: file.info().size, + date: format!( + "{}", + file.info().created.format("%Y-%m-%d %H:%M:%S") + ), + type_: file.info().file_type, + }, + Err(err) => TemplateFile::Error { + error: format!("{}", err), + }, + }) + .collect(); + Ok(Response::with(( + status::Ok, + Header(headers::ContentType::html()), + Header(headers::SetCookie(vec![format!("auth={}", token.text)])), + self.template + .render_to_string(&IndexTemplateParams { files }) + .expect("the template to render"), + ))) + } else { + Ok(Response::with(status::Forbidden)) + } + } + None => Ok(Response::with(status::Forbidden)), + } + } + } + + pub struct GetHandler { + pub app: Arc>, + } + + impl Handler for GetHandler { + fn handle(&self, req: &mut Request) -> IronResult { + let app = self.app.read().unwrap(); + let capture = req.extensions.get::().unwrap().clone(); + let old_etags = req.headers.get::(); + match capture.find("id") { + Some(id) => { + let info = app.get_metadata(String::from(id)); + match (info, old_etags) { + (Ok(info_), Some(if_none_match)) => { + if compare_etags(info_, if_none_match) { + return Ok(Response::with(status::NotModified)); + } + } + _ => (), + } + match app.get_file(String::from(id)) { + Ok((info, stream)) => Ok(Response::with(( + status::Ok, + Header(headers::ContentType( + info.file_type.parse::().unwrap(), + )), + Header(headers::ETag(headers::EntityTag::new(false, info.hash))), + BodyReader(stream), + ))), + Err(_err) => Ok(Response::with(status::NotFound)), + } + } + _ => Ok(Response::with(status::BadRequest)), + } + } + } + + pub struct GetThumbnailHandler { + pub app: Arc>, + } + + impl Handler for GetThumbnailHandler { + fn handle(&self, req: &mut Request) -> IronResult { + let app = self.app.read().unwrap(); + let capture = req.extensions.get::().unwrap().clone(); + let old_etags = req.headers.get::(); + match capture.find("id") { + Some(id) => { + let info = app.get_metadata(String::from(id)); + match (info, old_etags) { + (Ok(info_), Some(if_none_match)) => { + if compare_etags(info_, if_none_match) { + return Ok(Response::with(status::NotModified)); + } + } + _ => (), + } + match app.get_thumbnail(id) { + Ok((info, stream)) => Ok(Response::with(( + status::Ok, + Header(headers::ContentType( + info.file_type.parse::().unwrap(), + )), + Header(headers::ETag(headers::EntityTag::new(false, info.hash))), + BodyReader(stream), + ))), + Err(_err) => Ok(Response::with(status::NotFound)), + } + } + _ => Ok(Response::with(status::BadRequest)), + } + } + } + pub struct PostHandler { + pub app: Arc>, + } + + impl Handler for PostHandler { + fn handle(&self, req: &mut Request) -> IronResult { + let mut app = self.app.write().unwrap(); + let m_token = req.extensions.get::(); + match m_token { + Some(token) => { + if token.check_authorizations(is_admin) { + let params = req.get_ref::().unwrap(); + if let Value::File(f_info) = params.get("file").unwrap() { + match app.add_file( + &f_info.path, + &f_info.filename.clone().map(|fname| PathBuf::from(fname)), + ) { + Ok(_) => Ok(Response::with(( + status::MovedPermanently, + Redirect(router::url_for(req, "index", HashMap::new())), + ))), + Err(_) => Ok(Response::with(status::InternalServerError)), + } + } else { + Ok(Response::with(status::BadRequest)) + } + } else { + Ok(Response::with(status::Forbidden)) + } + } + None => Ok(Response::with(status::Forbidden)), + } + } + } + + pub struct DeleteHandler { + pub app: Arc>, + } + + impl Handler for DeleteHandler { + fn handle(&self, req: &mut Request) -> IronResult { + let mut app = self.app.write().unwrap(); + let capture = req.extensions.get::().unwrap().clone(); + let m_token = req.extensions.get::(); + match m_token { + Some(token) => { + if token.check_authorizations(is_admin) { + match capture.find("id") { + Some(id) => match app.delete_file(String::from(id)) { + Ok(()) => Ok(Response::with(( + status::MovedPermanently, + Redirect(router::url_for(req, "index", HashMap::new())), + ))), + Err(_) => Ok(Response::with(status::InternalServerError)), + }, + None => Ok(Response::with(status::BadRequest)), + } + } else { + Ok(Response::with(status::Forbidden)) + } + } + None => Ok(Response::with(status::Forbidden)), + } + } + } +} + +fn css(_: &mut Request) -> IronResult { + let mut css: String = String::from(""); + File::open("templates/style.css") + .unwrap() + .read_to_string(&mut css) + .unwrap(); + Ok(Response::with(( + status::Ok, + Header(headers::ContentType(iron::mime::Mime( + iron::mime::TopLevel::Text, + iron::mime::SubLevel::Css, + vec![], + ))), + css, + ))) +} + +fn script(_: &mut Request) -> IronResult { + let mut js: String = String::from(""); + File::open("templates/script.js") + .unwrap() + .read_to_string(&mut js) + .unwrap(); + Ok(Response::with(( + status::Ok, + Header(headers::ContentType(iron::mime::Mime( + iron::mime::TopLevel::Text, + iron::mime::SubLevel::Javascript, + vec![], + ))), + js, + ))) +} + +fn main() { + let auth_db_path = std::env::var("ORIZENTIC_DB").unwrap(); + let secret = Secret(Vec::from( + std::env::var("ORIZENTIC_SECRET").unwrap().as_bytes(), + )); + let auth_middleware = Authentication::new(secret, auth_db_path); + + let app = Arc::new(RwLock::new(App::new(Path::new( + &std::env::var("FILE_SHARE_DIR").unwrap(), + )))); + + let mut router = Router::new(); + router.get( + "/", + files::IndexHandler { + app: app.clone(), + template: compile_path("templates/index.html").expect("the template to compile"), + }, + "index", + ); + + router.get( + "/:id", + files::GetFileHandler { + app: app.clone(), + template: compile_path("templates/file.html").expect("the template to compile"), + }, + "get-file-page", + ); + + router.get( + "/:id/raw", + files::GetHandler { app: app.clone() }, + "get-file", + ); + + router.get( + "/:id/tn", + files::GetThumbnailHandler { app: app.clone() }, + "get-thumbnail", + ); + + router.post("/", files::PostHandler { app: app.clone() }, "upload-file"); + + router.delete( + "/:id", + files::DeleteHandler { app: app.clone() }, + "delete-file", + ); + router.get("/css", css, "styles"); + router.get("/script", script, "script"); + + let mut chain = Chain::new(router); + chain.link_before(auth_middleware); + chain.link_before(RestForm {}); + + Iron::new(chain).http("0.0.0.0:3000").unwrap(); +} diff --git a/file-service/src/middleware/authentication.rs b/file-service/src/middleware/authentication.rs new file mode 100644 index 0000000..87c1445 --- /dev/null +++ b/file-service/src/middleware/authentication.rs @@ -0,0 +1,51 @@ +use iron::headers; +use iron::middleware::BeforeMiddleware; +use iron::prelude::*; +use iron::typemap::Key; +use orizentic::{filedb, OrizenticCtx, Secret}; +use params::{FromValue, Params}; + +use crate::cookies::{Cookie, CookieJar}; + +pub struct Authentication { + pub auth: OrizenticCtx, +} + +impl Key for Authentication { + type Value = orizentic::VerifiedToken; +} + +impl Authentication { + pub fn new(secret: Secret, auth_db_path: String) -> Authentication { + let claims = filedb::load_claims_from_file(&auth_db_path).expect("claims did not load"); + let orizentic = OrizenticCtx::new(secret, claims); + Authentication { auth: orizentic } + } + + fn authenticate_user( + &self, + token_str: String, + ) -> Result { + self.auth.decode_and_validate_text(&token_str) + } +} + +impl BeforeMiddleware for Authentication { + fn before(&self, req: &mut Request) -> IronResult<()> { + let params = req.get_ref::().unwrap(); + let token = match params.get("auth").and_then(|v| String::from_value(v)) { + Some(token_str) => self.authenticate_user(token_str).ok(), + None => { + let m_jar = req + .headers + .get::() + .map(|cookies| CookieJar::from(cookies)); + m_jar + .and_then(|jar| jar.lookup("auth").cloned()) + .and_then(|Cookie { value, .. }| self.authenticate_user(value.clone()).ok()) + } + }; + token.map(|t| req.extensions.insert::(t)); + Ok(()) + } +} diff --git a/file-service/src/middleware/logging.rs b/file-service/src/middleware/logging.rs new file mode 100644 index 0000000..2aceb1d --- /dev/null +++ b/file-service/src/middleware/logging.rs @@ -0,0 +1,16 @@ +use iron::middleware::{AfterMiddleware, BeforeMiddleware}; +use iron::prelude::*; + +pub struct Logging {} + +impl BeforeMiddleware for Logging { + fn before(&self, _: &mut Request) -> IronResult<()> { + Ok(()) + } +} + +impl AfterMiddleware for Logging { + fn after(&self, _: &mut Request, res: Response) -> IronResult { + Ok(res) + } +} diff --git a/file-service/src/middleware/mod.rs b/file-service/src/middleware/mod.rs new file mode 100644 index 0000000..1bab46b --- /dev/null +++ b/file-service/src/middleware/mod.rs @@ -0,0 +1,6 @@ +mod authentication; +mod logging; +mod restform; + +pub use authentication::Authentication; +pub use restform::RestForm; diff --git a/file-service/src/middleware/restform.rs b/file-service/src/middleware/restform.rs new file mode 100644 index 0000000..5cfadd9 --- /dev/null +++ b/file-service/src/middleware/restform.rs @@ -0,0 +1,34 @@ +use iron::method::Method; +use iron::middleware::BeforeMiddleware; +use iron::prelude::*; +use params::{Params, Value}; + +pub struct RestForm {} + +impl RestForm { + fn method(&self, v: &Value) -> Option { + match v { + Value::String(method_str) => match method_str.as_str() { + "delete" => Some(Method::Delete), + _ => None, + }, + _ => None, + } + } +} + +impl BeforeMiddleware for RestForm { + fn before(&self, req: &mut Request) -> IronResult<()> { + if req.method == Method::Post { + let method = { + let params = req.get_ref::().unwrap(); + params + .get("_method") + .and_then(|m| self.method(m)) + .unwrap_or(Method::Post) + }; + req.method = method; + } + Ok(()) + } +} diff --git a/file-service/templates/file.html b/file-service/templates/file.html new file mode 100644 index 0000000..45002cc --- /dev/null +++ b/file-service/templates/file.html @@ -0,0 +1,15 @@ + + + + {{title}} + + + + + + + + + + + \ No newline at end of file diff --git a/file-service/templates/index.html b/file-service/templates/index.html new file mode 100644 index 0000000..6c44b20 --- /dev/null +++ b/file-service/templates/index.html @@ -0,0 +1,54 @@ + + + + Admin list of files + + + + + +

Admin list of files

+ +
+
+
+ + +
+ +
+
+ +
+ {{#files}} +
+ {{#error}} +
+

{{error}}

+
+ {{/error}} + + {{#file}} +
+ +
+
+
    +
  • {{date}}
  • +
  • {{type_}}
  • +
  • {{size}}
  • +
+
+
+ + +
+
+
+ {{/file}} +
+ {{/files}} +
+ + + \ No newline at end of file diff --git a/file-service/templates/script.js b/file-service/templates/script.js new file mode 100644 index 0000000..9a820db --- /dev/null +++ b/file-service/templates/script.js @@ -0,0 +1,10 @@ +const selectFile = (selectorId) => { + console.log("wide arrow functions work: " + selectorId); + const input = document.querySelector("#" + selectorId + " input[type='file']") + const label = document.querySelector("#" + selectorId + " label") + input.addEventListener("change", (e) => { + if (input.files.length > 0) { + label.innerHTML = input.files[0].name + } + }) +} diff --git a/file-service/templates/style.css b/file-service/templates/style.css new file mode 100644 index 0000000..eeb6f8b --- /dev/null +++ b/file-service/templates/style.css @@ -0,0 +1,103 @@ +body { + font-family: 'Ariel', sans-serif; +} + +.files { + display: flex; + flex-wrap: wrap; +} + +.file { + display: flex; + margin: 1em; + border: 1px solid #449dfc; + border-radius: 5px; + padding: 1em; +} + +.thumbnail { + max-width: 320px; + margin: 1em; +} + +img { + max-width: 100%; +} + +[type="submit"] { + border-radius: 1em; + margin: 1em; + padding: 1em; +} + +.uploadform { + display: flex; + margin: 1em; + background-color: #e5f0fc; + border: 1px solid #449dfc; + border-radius: 5px; + padding: 1em; +} + +/* https://benmarshall.me/styling-file-inputs/ */ +[type="file"] { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + overflow: hidden; + padding: 0; + position: absolute !important; + white-space: nowrap; + width: 1px; +} + +[type="file"] + label { + background-color: rgb(0, 86, 112); + border-radius: 1em; + color: #fff; + cursor: pointer; + display: inline-block; + padding: 1em; + margin: 1em; + transition: background-color 0.3s; +} + +[type="file"]:focus + label, +[type="file"] + label:hover { + background-color: #67b0ff; +} + +[type="file"]:focus + label { + outline: 1px dotted #000; + outline: -webkit-focus-ring-color auto 5px; +} + +@media screen and (max-width: 980px) { /* This is the screen width of a OnePlus 5t */ + body { + font-size: xx-large; + } + + [type="submit"] { + font-size: xx-large; + width: 100%; + } + + .uploadform { + display: flex; + } + + [type="file"] + label { + width: 100%; + } + + .thumbnail { + max-width: 100%; + margin: 1em; + } + + .file { + display: flex; + flex-direction: column; + width: 100%; + } +}