From e5c8ef9e8d62f9da654c54b53323fdb0e17c0c2c Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Tue, 4 Oct 2022 17:38:49 +1300 Subject: [PATCH 1/8] Remove redundant files --- .git-rewrite/backup-refs | 31 --- .git-rewrite/commit | 6 - .git-rewrite/heads | 1 - .../00d254d7c41be358cc0a99d388e796703c3ee5b9 | 1 - .../07141d3a213a443b830fb05dae87f181a6b213cc | 1 - .../0799a6d67cfd65d0228014478632f85780ed5517 | 1 - .../0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 | 1 - .../154b2342056d3adb1be716e16f8919319d3c6dba | 1 - .../19966fad81e043ef9e2a2d062411e3990dad449d | 1 - .../1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 | 1 - .../2944c2a32f6e1e3cbb7eb9603188b339f6d74700 | 1 - .../2e5752f69385f8d3f75ab1446e46d3f2208a543f | 1 - .../3103b50f0857d18c48ec60ee622fabf4938c938f | 1 - .../324de3d99f22bbce918e0246daa6caf90a0fc734 | 1 - .../335b0f3876e66da7bb06e6c39afb016068da1ac9 | 1 - .../38da162cd90445a64e6586f72fed0a3c5e7dfce2 | 1 - .../3bbc12286966b69455025aa4de3e88ecda439811 | 1 - .../41c7c2a93af86acb48e5dd8b3d9406820c547b13 | 1 - .../48db1437b3a9ea21bc987513c01bc4800b06f0f9 | 1 - .../493e8a09aa10d8fe48896c429ea0c1260b2c0709 | 1 - .../4b707537b9334641c37fb1d1aab96fe708e507ec | 1 - .../4e4fc22cb53abe2979d751e63671c771718147e5 | 1 - .../50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 | 1 - .../55fd56a4a3e40acbfa4720749018d13873d8af62 | 1 - .../56449dd30e9f26312ddb2117faf319a514f73f1f | 1 - .../6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 | 1 - .../6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 | 1 - .../72709acb903556967dd5d1cead1f5b052151bb25 | 1 - .../76c6c881d54b82d5ca4bc427c2968fbc15c7a090 | 1 - .../7a9b11a9e5e63b987fde0e7d8536f9fa76585097 | 1 - .../7cbe3a04ef7b95789287cf8fa7d654e18df11084 | 1 - .../7fd73a6fdb49843d29aefa2bb8bb5de900e53811 | 1 - .../8019d3e0e2a8a01f729911730128de72a2a1b6ba | 1 - .../802f6f5672200fff036a72abd84f39e78483d61f | 1 - .../8159694b935775e4072e1902124a8cb4bb6efacd | 1 - .../83f289eb40b34c07ad6b5df1099a7b6427e9be40 | 1 - .../86d73f9118eee40fd842fc25898490a2923eaecd | 1 - .../8866720631a717b8df2df6fc206eb6db44f8a005 | 1 - .../8d308a6776436ce1189e45268e4eceb8b5a1011a | 1 - .../8d6d48c59e9ca78d00a12c715494de99eb40a5ad | 1 - .../8e819aca56db7ab982243eddda0ab89525db1b23 | 1 - .../8f474bc31303882270112cdfafeaedc908938707 | 1 - .../927638f2d500dbfd29371b2b4a3f01b987749899 | 1 - .../9a27f330791c08ee87dbb886c6d511bb6d3bffa0 | 1 - .../a3b34ec32effa03acfa4aa99cbe38aee81f01e56 | 1 - .../a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 | 1 - .../a810bdae244404a91d17c6293f3096b055dd74cf | 1 - .../a85a74bb9a03dafeaaaf3e2211cb796f195773e3 | 1 - .../ad1037c02b654b7e582c2c644841ea7477fcf9b4 | 1 - .../b7dbda0db65e24c8ad2549639cd22388db837e1f | 1 - .../bca7bec867cd6aff1b1692ed5508c18e62aa430b | 1 - .../bd87dcabf6b370316eac39b863ee5d84ad5851e2 | 1 - .../c6f1c8213b0bb6489072bc379ae8b158e6a552d9 | 1 - .../ce23e0616e4a29a266bbfc9c398c57c2976dc71a | 1 - .../cf12b3968b2d2a59fb1fa12ef83065b1d7090681 | 1 - .../cf5c4297c9b975dff541a7384da6838dd7fab930 | 1 - .../d15b3eb05e53326a81eab3f6839186e0840cb686 | 1 - .../d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 | 1 - .../e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 | 1 - .../e363ece5a05eeb55264176e689ffdbf4e79b718f | 1 - .../eb7049163bbfa1bc73af5859dff1803901cf43b3 | 1 - .../f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 | 1 - .../f966b6a756f736f3f106a186faac183ce0eb0686 | 1 - .git-rewrite/message | 1 - .git-rewrite/parse | 0 .git-rewrite/raw-refs | 1 - .git-rewrite/revs | 212 ------------------ 67 files changed, 312 deletions(-) delete mode 100644 .git-rewrite/backup-refs delete mode 100644 .git-rewrite/commit delete mode 100644 .git-rewrite/heads delete mode 100644 .git-rewrite/map/00d254d7c41be358cc0a99d388e796703c3ee5b9 delete mode 100644 .git-rewrite/map/07141d3a213a443b830fb05dae87f181a6b213cc delete mode 100644 .git-rewrite/map/0799a6d67cfd65d0228014478632f85780ed5517 delete mode 100644 .git-rewrite/map/0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 delete mode 100644 .git-rewrite/map/154b2342056d3adb1be716e16f8919319d3c6dba delete mode 100644 .git-rewrite/map/19966fad81e043ef9e2a2d062411e3990dad449d delete mode 100644 .git-rewrite/map/1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 delete mode 100644 .git-rewrite/map/2944c2a32f6e1e3cbb7eb9603188b339f6d74700 delete mode 100644 .git-rewrite/map/2e5752f69385f8d3f75ab1446e46d3f2208a543f delete mode 100644 .git-rewrite/map/3103b50f0857d18c48ec60ee622fabf4938c938f delete mode 100644 .git-rewrite/map/324de3d99f22bbce918e0246daa6caf90a0fc734 delete mode 100644 .git-rewrite/map/335b0f3876e66da7bb06e6c39afb016068da1ac9 delete mode 100644 .git-rewrite/map/38da162cd90445a64e6586f72fed0a3c5e7dfce2 delete mode 100644 .git-rewrite/map/3bbc12286966b69455025aa4de3e88ecda439811 delete mode 100644 .git-rewrite/map/41c7c2a93af86acb48e5dd8b3d9406820c547b13 delete mode 100644 .git-rewrite/map/48db1437b3a9ea21bc987513c01bc4800b06f0f9 delete mode 100644 .git-rewrite/map/493e8a09aa10d8fe48896c429ea0c1260b2c0709 delete mode 100644 .git-rewrite/map/4b707537b9334641c37fb1d1aab96fe708e507ec delete mode 100644 .git-rewrite/map/4e4fc22cb53abe2979d751e63671c771718147e5 delete mode 100644 .git-rewrite/map/50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 delete mode 100644 .git-rewrite/map/55fd56a4a3e40acbfa4720749018d13873d8af62 delete mode 100644 .git-rewrite/map/56449dd30e9f26312ddb2117faf319a514f73f1f delete mode 100644 .git-rewrite/map/6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 delete mode 100644 .git-rewrite/map/6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 delete mode 100644 .git-rewrite/map/72709acb903556967dd5d1cead1f5b052151bb25 delete mode 100644 .git-rewrite/map/76c6c881d54b82d5ca4bc427c2968fbc15c7a090 delete mode 100644 .git-rewrite/map/7a9b11a9e5e63b987fde0e7d8536f9fa76585097 delete mode 100644 .git-rewrite/map/7cbe3a04ef7b95789287cf8fa7d654e18df11084 delete mode 100644 .git-rewrite/map/7fd73a6fdb49843d29aefa2bb8bb5de900e53811 delete mode 100644 .git-rewrite/map/8019d3e0e2a8a01f729911730128de72a2a1b6ba delete mode 100644 .git-rewrite/map/802f6f5672200fff036a72abd84f39e78483d61f delete mode 100644 .git-rewrite/map/8159694b935775e4072e1902124a8cb4bb6efacd delete mode 100644 .git-rewrite/map/83f289eb40b34c07ad6b5df1099a7b6427e9be40 delete mode 100644 .git-rewrite/map/86d73f9118eee40fd842fc25898490a2923eaecd delete mode 100644 .git-rewrite/map/8866720631a717b8df2df6fc206eb6db44f8a005 delete mode 100644 .git-rewrite/map/8d308a6776436ce1189e45268e4eceb8b5a1011a delete mode 100644 .git-rewrite/map/8d6d48c59e9ca78d00a12c715494de99eb40a5ad delete mode 100644 .git-rewrite/map/8e819aca56db7ab982243eddda0ab89525db1b23 delete mode 100644 .git-rewrite/map/8f474bc31303882270112cdfafeaedc908938707 delete mode 100644 .git-rewrite/map/927638f2d500dbfd29371b2b4a3f01b987749899 delete mode 100644 .git-rewrite/map/9a27f330791c08ee87dbb886c6d511bb6d3bffa0 delete mode 100644 .git-rewrite/map/a3b34ec32effa03acfa4aa99cbe38aee81f01e56 delete mode 100644 .git-rewrite/map/a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 delete mode 100644 .git-rewrite/map/a810bdae244404a91d17c6293f3096b055dd74cf delete mode 100644 .git-rewrite/map/a85a74bb9a03dafeaaaf3e2211cb796f195773e3 delete mode 100644 .git-rewrite/map/ad1037c02b654b7e582c2c644841ea7477fcf9b4 delete mode 100644 .git-rewrite/map/b7dbda0db65e24c8ad2549639cd22388db837e1f delete mode 100644 .git-rewrite/map/bca7bec867cd6aff1b1692ed5508c18e62aa430b delete mode 100644 .git-rewrite/map/bd87dcabf6b370316eac39b863ee5d84ad5851e2 delete mode 100644 .git-rewrite/map/c6f1c8213b0bb6489072bc379ae8b158e6a552d9 delete mode 100644 .git-rewrite/map/ce23e0616e4a29a266bbfc9c398c57c2976dc71a delete mode 100644 .git-rewrite/map/cf12b3968b2d2a59fb1fa12ef83065b1d7090681 delete mode 100644 .git-rewrite/map/cf5c4297c9b975dff541a7384da6838dd7fab930 delete mode 100644 .git-rewrite/map/d15b3eb05e53326a81eab3f6839186e0840cb686 delete mode 100644 .git-rewrite/map/d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 delete mode 100644 .git-rewrite/map/e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 delete mode 100644 .git-rewrite/map/e363ece5a05eeb55264176e689ffdbf4e79b718f delete mode 100644 .git-rewrite/map/eb7049163bbfa1bc73af5859dff1803901cf43b3 delete mode 100644 .git-rewrite/map/f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 delete mode 100644 .git-rewrite/map/f966b6a756f736f3f106a186faac183ce0eb0686 delete mode 100644 .git-rewrite/message delete mode 100644 .git-rewrite/parse delete mode 100644 .git-rewrite/raw-refs delete mode 100644 .git-rewrite/revs diff --git a/.git-rewrite/backup-refs b/.git-rewrite/backup-refs deleted file mode 100644 index 6086354..0000000 --- a/.git-rewrite/backup-refs +++ /dev/null @@ -1,31 +0,0 @@ -7c73d1883ba2ee2a9fe39d7c1d90e19d9606b0a1 commit refs/heads/develop -d6fb872c795a8d57f6813da55d6cf3b453a96cc7 commit refs/heads/feature/version -89e6fb4c42d70fa5a44075a2023cc6b520c17d8c commit refs/heads/master -7c73d1883ba2ee2a9fe39d7c1d90e19d9606b0a1 commit refs/remotes/origin/develop -89e6fb4c42d70fa5a44075a2023cc6b520c17d8c commit refs/remotes/origin/master -438504fa46b2aa6847096d45be682c92bcfa00a2 commit refs/stash -f0c66b466e49942d7ff47a4a788ba41d6028ae8b tag refs/tags/0.0.1-beta -6c2be2ac88682884897fc440805c03679c2ced84 tag refs/tags/0.0.2 -1f39040d1d6e5c7804a090ae5cd09f2e0fdaca09 tag refs/tags/0.0.3 -3b781c77672a03600b170426298c0b30c0433542 tag refs/tags/0.0.4 -82f78fbf5a75d5dd3dae03e7c7ecedce99fc4642 tag refs/tags/0.0.5 -df9b109f382ff148c4254a4c98b0511c09f6687b tag refs/tags/0.0.6 -52859e22f355fafb6e1babf3d83e6e84e2aa380d tag refs/tags/0.0.7 -5e3e91a22c3557b3af1f787bd5b8a1ee462f5604 tag refs/tags/0.0.8 -0eb4066a4ce7839b81b6333be0e9660c8811b913 tag refs/tags/0.0.9 -13d94ea2541ad13d2f12825c604467b11f3e4a8a tag refs/tags/0.1.0 -e591d5c093ee3ba06a0c1ee891f961e287faeeb3 tag refs/tags/0.1.1 -4f14cdea00bf0fd4299e557cf3e895b548a0f982 tag refs/tags/0.1.2 -2be1a43f3990adb865c9e1a713c168cba9d01af7 tag refs/tags/0.1.3 -625b72b1eead52df071716d95d49aed28ae59d64 tag refs/tags/0.1.4 -39e6757bea81d8c8b52d3765cb198c4d70a200bb tag refs/tags/0.1.5 -ddf198cb39f4ed254558819ece53af6ec2f0d568 tag refs/tags/1.0.0 -60079cf37f9e53c93853bfe99094fde764feec5c tag refs/tags/1.0.0-beta1 -15371a9cd3671238811c7467f86decc3c00b7119 tag refs/tags/1.1.0 -88361be8a0fe56b147b44ea5fa5e754d2461e530 tag refs/tags/1.1.1 -9c9bc84baa24de70658bf402ebdae32c861dd3b1 tag refs/tags/1.1.2 -ee82b104198a75f3d837b7ab678c2a427e399a47 tag refs/tags/1.1.3 -28cde017eb7f904b77c00265cde6f3fe319af983 tag refs/tags/1.1.4 -6a008ae9c9fd101721b399e75f562dc9240f4372 tag refs/tags/1.1.5 -f3811c3be7c7aad71b03ece17fe4412890bc0ea1 tag refs/tags/1.1.6 -4da0e28ac47593b415c3e35aa13ff9cfb2be904d tag refs/tags/1.1.7 diff --git a/.git-rewrite/commit b/.git-rewrite/commit deleted file mode 100644 index 09854d1..0000000 --- a/.git-rewrite/commit +++ /dev/null @@ -1,6 +0,0 @@ -tree 1f6b4426212043bf6b473fcbac898e9b871c84f6 -parent 802f6f5672200fff036a72abd84f39e78483d61f -author Ralph Slooten 1659602885 +1200 -committer Ralph Slooten 1659602885 +1200 - -Release 0.0.6 diff --git a/.git-rewrite/heads b/.git-rewrite/heads deleted file mode 100644 index b182939..0000000 --- a/.git-rewrite/heads +++ /dev/null @@ -1 +0,0 @@ -refs/heads/develop diff --git a/.git-rewrite/map/00d254d7c41be358cc0a99d388e796703c3ee5b9 b/.git-rewrite/map/00d254d7c41be358cc0a99d388e796703c3ee5b9 deleted file mode 100644 index fb7a3a0..0000000 --- a/.git-rewrite/map/00d254d7c41be358cc0a99d388e796703c3ee5b9 +++ /dev/null @@ -1 +0,0 @@ -00d254d7c41be358cc0a99d388e796703c3ee5b9 diff --git a/.git-rewrite/map/07141d3a213a443b830fb05dae87f181a6b213cc b/.git-rewrite/map/07141d3a213a443b830fb05dae87f181a6b213cc deleted file mode 100644 index 5568503..0000000 --- a/.git-rewrite/map/07141d3a213a443b830fb05dae87f181a6b213cc +++ /dev/null @@ -1 +0,0 @@ -07141d3a213a443b830fb05dae87f181a6b213cc diff --git a/.git-rewrite/map/0799a6d67cfd65d0228014478632f85780ed5517 b/.git-rewrite/map/0799a6d67cfd65d0228014478632f85780ed5517 deleted file mode 100644 index b9fc570..0000000 --- a/.git-rewrite/map/0799a6d67cfd65d0228014478632f85780ed5517 +++ /dev/null @@ -1 +0,0 @@ -0799a6d67cfd65d0228014478632f85780ed5517 diff --git a/.git-rewrite/map/0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 b/.git-rewrite/map/0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 deleted file mode 100644 index 0b8cb17..0000000 --- a/.git-rewrite/map/0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 +++ /dev/null @@ -1 +0,0 @@ -0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 diff --git a/.git-rewrite/map/154b2342056d3adb1be716e16f8919319d3c6dba b/.git-rewrite/map/154b2342056d3adb1be716e16f8919319d3c6dba deleted file mode 100644 index 5420e43..0000000 --- a/.git-rewrite/map/154b2342056d3adb1be716e16f8919319d3c6dba +++ /dev/null @@ -1 +0,0 @@ -154b2342056d3adb1be716e16f8919319d3c6dba diff --git a/.git-rewrite/map/19966fad81e043ef9e2a2d062411e3990dad449d b/.git-rewrite/map/19966fad81e043ef9e2a2d062411e3990dad449d deleted file mode 100644 index 1171deb..0000000 --- a/.git-rewrite/map/19966fad81e043ef9e2a2d062411e3990dad449d +++ /dev/null @@ -1 +0,0 @@ -19966fad81e043ef9e2a2d062411e3990dad449d diff --git a/.git-rewrite/map/1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 b/.git-rewrite/map/1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 deleted file mode 100644 index 91cb42a..0000000 --- a/.git-rewrite/map/1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 +++ /dev/null @@ -1 +0,0 @@ -1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 diff --git a/.git-rewrite/map/2944c2a32f6e1e3cbb7eb9603188b339f6d74700 b/.git-rewrite/map/2944c2a32f6e1e3cbb7eb9603188b339f6d74700 deleted file mode 100644 index de6d55a..0000000 --- a/.git-rewrite/map/2944c2a32f6e1e3cbb7eb9603188b339f6d74700 +++ /dev/null @@ -1 +0,0 @@ -2944c2a32f6e1e3cbb7eb9603188b339f6d74700 diff --git a/.git-rewrite/map/2e5752f69385f8d3f75ab1446e46d3f2208a543f b/.git-rewrite/map/2e5752f69385f8d3f75ab1446e46d3f2208a543f deleted file mode 100644 index 9220d48..0000000 --- a/.git-rewrite/map/2e5752f69385f8d3f75ab1446e46d3f2208a543f +++ /dev/null @@ -1 +0,0 @@ -2e5752f69385f8d3f75ab1446e46d3f2208a543f diff --git a/.git-rewrite/map/3103b50f0857d18c48ec60ee622fabf4938c938f b/.git-rewrite/map/3103b50f0857d18c48ec60ee622fabf4938c938f deleted file mode 100644 index 81b471c..0000000 --- a/.git-rewrite/map/3103b50f0857d18c48ec60ee622fabf4938c938f +++ /dev/null @@ -1 +0,0 @@ -3103b50f0857d18c48ec60ee622fabf4938c938f diff --git a/.git-rewrite/map/324de3d99f22bbce918e0246daa6caf90a0fc734 b/.git-rewrite/map/324de3d99f22bbce918e0246daa6caf90a0fc734 deleted file mode 100644 index 9b4014f..0000000 --- a/.git-rewrite/map/324de3d99f22bbce918e0246daa6caf90a0fc734 +++ /dev/null @@ -1 +0,0 @@ -324de3d99f22bbce918e0246daa6caf90a0fc734 diff --git a/.git-rewrite/map/335b0f3876e66da7bb06e6c39afb016068da1ac9 b/.git-rewrite/map/335b0f3876e66da7bb06e6c39afb016068da1ac9 deleted file mode 100644 index f301596..0000000 --- a/.git-rewrite/map/335b0f3876e66da7bb06e6c39afb016068da1ac9 +++ /dev/null @@ -1 +0,0 @@ -335b0f3876e66da7bb06e6c39afb016068da1ac9 diff --git a/.git-rewrite/map/38da162cd90445a64e6586f72fed0a3c5e7dfce2 b/.git-rewrite/map/38da162cd90445a64e6586f72fed0a3c5e7dfce2 deleted file mode 100644 index 398d7c1..0000000 --- a/.git-rewrite/map/38da162cd90445a64e6586f72fed0a3c5e7dfce2 +++ /dev/null @@ -1 +0,0 @@ -38da162cd90445a64e6586f72fed0a3c5e7dfce2 diff --git a/.git-rewrite/map/3bbc12286966b69455025aa4de3e88ecda439811 b/.git-rewrite/map/3bbc12286966b69455025aa4de3e88ecda439811 deleted file mode 100644 index 73f42c2..0000000 --- a/.git-rewrite/map/3bbc12286966b69455025aa4de3e88ecda439811 +++ /dev/null @@ -1 +0,0 @@ -3bbc12286966b69455025aa4de3e88ecda439811 diff --git a/.git-rewrite/map/41c7c2a93af86acb48e5dd8b3d9406820c547b13 b/.git-rewrite/map/41c7c2a93af86acb48e5dd8b3d9406820c547b13 deleted file mode 100644 index 61c228c..0000000 --- a/.git-rewrite/map/41c7c2a93af86acb48e5dd8b3d9406820c547b13 +++ /dev/null @@ -1 +0,0 @@ -41c7c2a93af86acb48e5dd8b3d9406820c547b13 diff --git a/.git-rewrite/map/48db1437b3a9ea21bc987513c01bc4800b06f0f9 b/.git-rewrite/map/48db1437b3a9ea21bc987513c01bc4800b06f0f9 deleted file mode 100644 index a477c8b..0000000 --- a/.git-rewrite/map/48db1437b3a9ea21bc987513c01bc4800b06f0f9 +++ /dev/null @@ -1 +0,0 @@ -48db1437b3a9ea21bc987513c01bc4800b06f0f9 diff --git a/.git-rewrite/map/493e8a09aa10d8fe48896c429ea0c1260b2c0709 b/.git-rewrite/map/493e8a09aa10d8fe48896c429ea0c1260b2c0709 deleted file mode 100644 index 6b9cdc8..0000000 --- a/.git-rewrite/map/493e8a09aa10d8fe48896c429ea0c1260b2c0709 +++ /dev/null @@ -1 +0,0 @@ -493e8a09aa10d8fe48896c429ea0c1260b2c0709 diff --git a/.git-rewrite/map/4b707537b9334641c37fb1d1aab96fe708e507ec b/.git-rewrite/map/4b707537b9334641c37fb1d1aab96fe708e507ec deleted file mode 100644 index 3db505c..0000000 --- a/.git-rewrite/map/4b707537b9334641c37fb1d1aab96fe708e507ec +++ /dev/null @@ -1 +0,0 @@ -4b707537b9334641c37fb1d1aab96fe708e507ec diff --git a/.git-rewrite/map/4e4fc22cb53abe2979d751e63671c771718147e5 b/.git-rewrite/map/4e4fc22cb53abe2979d751e63671c771718147e5 deleted file mode 100644 index e29ca11..0000000 --- a/.git-rewrite/map/4e4fc22cb53abe2979d751e63671c771718147e5 +++ /dev/null @@ -1 +0,0 @@ -4e4fc22cb53abe2979d751e63671c771718147e5 diff --git a/.git-rewrite/map/50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 b/.git-rewrite/map/50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 deleted file mode 100644 index 496072f..0000000 --- a/.git-rewrite/map/50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 +++ /dev/null @@ -1 +0,0 @@ -50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 diff --git a/.git-rewrite/map/55fd56a4a3e40acbfa4720749018d13873d8af62 b/.git-rewrite/map/55fd56a4a3e40acbfa4720749018d13873d8af62 deleted file mode 100644 index 438c713..0000000 --- a/.git-rewrite/map/55fd56a4a3e40acbfa4720749018d13873d8af62 +++ /dev/null @@ -1 +0,0 @@ -55fd56a4a3e40acbfa4720749018d13873d8af62 diff --git a/.git-rewrite/map/56449dd30e9f26312ddb2117faf319a514f73f1f b/.git-rewrite/map/56449dd30e9f26312ddb2117faf319a514f73f1f deleted file mode 100644 index 4e78ad2..0000000 --- a/.git-rewrite/map/56449dd30e9f26312ddb2117faf319a514f73f1f +++ /dev/null @@ -1 +0,0 @@ -56449dd30e9f26312ddb2117faf319a514f73f1f diff --git a/.git-rewrite/map/6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 b/.git-rewrite/map/6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 deleted file mode 100644 index 0dd1651..0000000 --- a/.git-rewrite/map/6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 +++ /dev/null @@ -1 +0,0 @@ -6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 diff --git a/.git-rewrite/map/6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 b/.git-rewrite/map/6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 deleted file mode 100644 index cbf2cf0..0000000 --- a/.git-rewrite/map/6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 +++ /dev/null @@ -1 +0,0 @@ -6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 diff --git a/.git-rewrite/map/72709acb903556967dd5d1cead1f5b052151bb25 b/.git-rewrite/map/72709acb903556967dd5d1cead1f5b052151bb25 deleted file mode 100644 index bd8c731..0000000 --- a/.git-rewrite/map/72709acb903556967dd5d1cead1f5b052151bb25 +++ /dev/null @@ -1 +0,0 @@ -72709acb903556967dd5d1cead1f5b052151bb25 diff --git a/.git-rewrite/map/76c6c881d54b82d5ca4bc427c2968fbc15c7a090 b/.git-rewrite/map/76c6c881d54b82d5ca4bc427c2968fbc15c7a090 deleted file mode 100644 index 8008614..0000000 --- a/.git-rewrite/map/76c6c881d54b82d5ca4bc427c2968fbc15c7a090 +++ /dev/null @@ -1 +0,0 @@ -76c6c881d54b82d5ca4bc427c2968fbc15c7a090 diff --git a/.git-rewrite/map/7a9b11a9e5e63b987fde0e7d8536f9fa76585097 b/.git-rewrite/map/7a9b11a9e5e63b987fde0e7d8536f9fa76585097 deleted file mode 100644 index 0095e09..0000000 --- a/.git-rewrite/map/7a9b11a9e5e63b987fde0e7d8536f9fa76585097 +++ /dev/null @@ -1 +0,0 @@ -7a9b11a9e5e63b987fde0e7d8536f9fa76585097 diff --git a/.git-rewrite/map/7cbe3a04ef7b95789287cf8fa7d654e18df11084 b/.git-rewrite/map/7cbe3a04ef7b95789287cf8fa7d654e18df11084 deleted file mode 100644 index c30a562..0000000 --- a/.git-rewrite/map/7cbe3a04ef7b95789287cf8fa7d654e18df11084 +++ /dev/null @@ -1 +0,0 @@ -7cbe3a04ef7b95789287cf8fa7d654e18df11084 diff --git a/.git-rewrite/map/7fd73a6fdb49843d29aefa2bb8bb5de900e53811 b/.git-rewrite/map/7fd73a6fdb49843d29aefa2bb8bb5de900e53811 deleted file mode 100644 index 87ed8db..0000000 --- a/.git-rewrite/map/7fd73a6fdb49843d29aefa2bb8bb5de900e53811 +++ /dev/null @@ -1 +0,0 @@ -7fd73a6fdb49843d29aefa2bb8bb5de900e53811 diff --git a/.git-rewrite/map/8019d3e0e2a8a01f729911730128de72a2a1b6ba b/.git-rewrite/map/8019d3e0e2a8a01f729911730128de72a2a1b6ba deleted file mode 100644 index 76d7114..0000000 --- a/.git-rewrite/map/8019d3e0e2a8a01f729911730128de72a2a1b6ba +++ /dev/null @@ -1 +0,0 @@ -8019d3e0e2a8a01f729911730128de72a2a1b6ba diff --git a/.git-rewrite/map/802f6f5672200fff036a72abd84f39e78483d61f b/.git-rewrite/map/802f6f5672200fff036a72abd84f39e78483d61f deleted file mode 100644 index fae0e00..0000000 --- a/.git-rewrite/map/802f6f5672200fff036a72abd84f39e78483d61f +++ /dev/null @@ -1 +0,0 @@ -802f6f5672200fff036a72abd84f39e78483d61f diff --git a/.git-rewrite/map/8159694b935775e4072e1902124a8cb4bb6efacd b/.git-rewrite/map/8159694b935775e4072e1902124a8cb4bb6efacd deleted file mode 100644 index d910505..0000000 --- a/.git-rewrite/map/8159694b935775e4072e1902124a8cb4bb6efacd +++ /dev/null @@ -1 +0,0 @@ -8159694b935775e4072e1902124a8cb4bb6efacd diff --git a/.git-rewrite/map/83f289eb40b34c07ad6b5df1099a7b6427e9be40 b/.git-rewrite/map/83f289eb40b34c07ad6b5df1099a7b6427e9be40 deleted file mode 100644 index 03e32d0..0000000 --- a/.git-rewrite/map/83f289eb40b34c07ad6b5df1099a7b6427e9be40 +++ /dev/null @@ -1 +0,0 @@ -83f289eb40b34c07ad6b5df1099a7b6427e9be40 diff --git a/.git-rewrite/map/86d73f9118eee40fd842fc25898490a2923eaecd b/.git-rewrite/map/86d73f9118eee40fd842fc25898490a2923eaecd deleted file mode 100644 index 7e0f2e2..0000000 --- a/.git-rewrite/map/86d73f9118eee40fd842fc25898490a2923eaecd +++ /dev/null @@ -1 +0,0 @@ -86d73f9118eee40fd842fc25898490a2923eaecd diff --git a/.git-rewrite/map/8866720631a717b8df2df6fc206eb6db44f8a005 b/.git-rewrite/map/8866720631a717b8df2df6fc206eb6db44f8a005 deleted file mode 100644 index 454cb24..0000000 --- a/.git-rewrite/map/8866720631a717b8df2df6fc206eb6db44f8a005 +++ /dev/null @@ -1 +0,0 @@ -8866720631a717b8df2df6fc206eb6db44f8a005 diff --git a/.git-rewrite/map/8d308a6776436ce1189e45268e4eceb8b5a1011a b/.git-rewrite/map/8d308a6776436ce1189e45268e4eceb8b5a1011a deleted file mode 100644 index 129a983..0000000 --- a/.git-rewrite/map/8d308a6776436ce1189e45268e4eceb8b5a1011a +++ /dev/null @@ -1 +0,0 @@ -8d308a6776436ce1189e45268e4eceb8b5a1011a diff --git a/.git-rewrite/map/8d6d48c59e9ca78d00a12c715494de99eb40a5ad b/.git-rewrite/map/8d6d48c59e9ca78d00a12c715494de99eb40a5ad deleted file mode 100644 index b7b3ef9..0000000 --- a/.git-rewrite/map/8d6d48c59e9ca78d00a12c715494de99eb40a5ad +++ /dev/null @@ -1 +0,0 @@ -8d6d48c59e9ca78d00a12c715494de99eb40a5ad diff --git a/.git-rewrite/map/8e819aca56db7ab982243eddda0ab89525db1b23 b/.git-rewrite/map/8e819aca56db7ab982243eddda0ab89525db1b23 deleted file mode 100644 index 0ee0081..0000000 --- a/.git-rewrite/map/8e819aca56db7ab982243eddda0ab89525db1b23 +++ /dev/null @@ -1 +0,0 @@ -8e819aca56db7ab982243eddda0ab89525db1b23 diff --git a/.git-rewrite/map/8f474bc31303882270112cdfafeaedc908938707 b/.git-rewrite/map/8f474bc31303882270112cdfafeaedc908938707 deleted file mode 100644 index 5887453..0000000 --- a/.git-rewrite/map/8f474bc31303882270112cdfafeaedc908938707 +++ /dev/null @@ -1 +0,0 @@ -8f474bc31303882270112cdfafeaedc908938707 diff --git a/.git-rewrite/map/927638f2d500dbfd29371b2b4a3f01b987749899 b/.git-rewrite/map/927638f2d500dbfd29371b2b4a3f01b987749899 deleted file mode 100644 index 1034e33..0000000 --- a/.git-rewrite/map/927638f2d500dbfd29371b2b4a3f01b987749899 +++ /dev/null @@ -1 +0,0 @@ -927638f2d500dbfd29371b2b4a3f01b987749899 diff --git a/.git-rewrite/map/9a27f330791c08ee87dbb886c6d511bb6d3bffa0 b/.git-rewrite/map/9a27f330791c08ee87dbb886c6d511bb6d3bffa0 deleted file mode 100644 index cd188a9..0000000 --- a/.git-rewrite/map/9a27f330791c08ee87dbb886c6d511bb6d3bffa0 +++ /dev/null @@ -1 +0,0 @@ -9a27f330791c08ee87dbb886c6d511bb6d3bffa0 diff --git a/.git-rewrite/map/a3b34ec32effa03acfa4aa99cbe38aee81f01e56 b/.git-rewrite/map/a3b34ec32effa03acfa4aa99cbe38aee81f01e56 deleted file mode 100644 index 614b04c..0000000 --- a/.git-rewrite/map/a3b34ec32effa03acfa4aa99cbe38aee81f01e56 +++ /dev/null @@ -1 +0,0 @@ -a3b34ec32effa03acfa4aa99cbe38aee81f01e56 diff --git a/.git-rewrite/map/a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 b/.git-rewrite/map/a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 deleted file mode 100644 index 8d9e65c..0000000 --- a/.git-rewrite/map/a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 +++ /dev/null @@ -1 +0,0 @@ -a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 diff --git a/.git-rewrite/map/a810bdae244404a91d17c6293f3096b055dd74cf b/.git-rewrite/map/a810bdae244404a91d17c6293f3096b055dd74cf deleted file mode 100644 index c32eaf7..0000000 --- a/.git-rewrite/map/a810bdae244404a91d17c6293f3096b055dd74cf +++ /dev/null @@ -1 +0,0 @@ -a810bdae244404a91d17c6293f3096b055dd74cf diff --git a/.git-rewrite/map/a85a74bb9a03dafeaaaf3e2211cb796f195773e3 b/.git-rewrite/map/a85a74bb9a03dafeaaaf3e2211cb796f195773e3 deleted file mode 100644 index b31690e..0000000 --- a/.git-rewrite/map/a85a74bb9a03dafeaaaf3e2211cb796f195773e3 +++ /dev/null @@ -1 +0,0 @@ -a85a74bb9a03dafeaaaf3e2211cb796f195773e3 diff --git a/.git-rewrite/map/ad1037c02b654b7e582c2c644841ea7477fcf9b4 b/.git-rewrite/map/ad1037c02b654b7e582c2c644841ea7477fcf9b4 deleted file mode 100644 index 03600c8..0000000 --- a/.git-rewrite/map/ad1037c02b654b7e582c2c644841ea7477fcf9b4 +++ /dev/null @@ -1 +0,0 @@ -ad1037c02b654b7e582c2c644841ea7477fcf9b4 diff --git a/.git-rewrite/map/b7dbda0db65e24c8ad2549639cd22388db837e1f b/.git-rewrite/map/b7dbda0db65e24c8ad2549639cd22388db837e1f deleted file mode 100644 index 09b33ed..0000000 --- a/.git-rewrite/map/b7dbda0db65e24c8ad2549639cd22388db837e1f +++ /dev/null @@ -1 +0,0 @@ -b7dbda0db65e24c8ad2549639cd22388db837e1f diff --git a/.git-rewrite/map/bca7bec867cd6aff1b1692ed5508c18e62aa430b b/.git-rewrite/map/bca7bec867cd6aff1b1692ed5508c18e62aa430b deleted file mode 100644 index baea241..0000000 --- a/.git-rewrite/map/bca7bec867cd6aff1b1692ed5508c18e62aa430b +++ /dev/null @@ -1 +0,0 @@ -bca7bec867cd6aff1b1692ed5508c18e62aa430b diff --git a/.git-rewrite/map/bd87dcabf6b370316eac39b863ee5d84ad5851e2 b/.git-rewrite/map/bd87dcabf6b370316eac39b863ee5d84ad5851e2 deleted file mode 100644 index 0eeb04b..0000000 --- a/.git-rewrite/map/bd87dcabf6b370316eac39b863ee5d84ad5851e2 +++ /dev/null @@ -1 +0,0 @@ -bd87dcabf6b370316eac39b863ee5d84ad5851e2 diff --git a/.git-rewrite/map/c6f1c8213b0bb6489072bc379ae8b158e6a552d9 b/.git-rewrite/map/c6f1c8213b0bb6489072bc379ae8b158e6a552d9 deleted file mode 100644 index 66aacd9..0000000 --- a/.git-rewrite/map/c6f1c8213b0bb6489072bc379ae8b158e6a552d9 +++ /dev/null @@ -1 +0,0 @@ -c6f1c8213b0bb6489072bc379ae8b158e6a552d9 diff --git a/.git-rewrite/map/ce23e0616e4a29a266bbfc9c398c57c2976dc71a b/.git-rewrite/map/ce23e0616e4a29a266bbfc9c398c57c2976dc71a deleted file mode 100644 index 4297f52..0000000 --- a/.git-rewrite/map/ce23e0616e4a29a266bbfc9c398c57c2976dc71a +++ /dev/null @@ -1 +0,0 @@ -ce23e0616e4a29a266bbfc9c398c57c2976dc71a diff --git a/.git-rewrite/map/cf12b3968b2d2a59fb1fa12ef83065b1d7090681 b/.git-rewrite/map/cf12b3968b2d2a59fb1fa12ef83065b1d7090681 deleted file mode 100644 index a4b6772..0000000 --- a/.git-rewrite/map/cf12b3968b2d2a59fb1fa12ef83065b1d7090681 +++ /dev/null @@ -1 +0,0 @@ -cf12b3968b2d2a59fb1fa12ef83065b1d7090681 diff --git a/.git-rewrite/map/cf5c4297c9b975dff541a7384da6838dd7fab930 b/.git-rewrite/map/cf5c4297c9b975dff541a7384da6838dd7fab930 deleted file mode 100644 index c4ffda8..0000000 --- a/.git-rewrite/map/cf5c4297c9b975dff541a7384da6838dd7fab930 +++ /dev/null @@ -1 +0,0 @@ -cf5c4297c9b975dff541a7384da6838dd7fab930 diff --git a/.git-rewrite/map/d15b3eb05e53326a81eab3f6839186e0840cb686 b/.git-rewrite/map/d15b3eb05e53326a81eab3f6839186e0840cb686 deleted file mode 100644 index 5414bfc..0000000 --- a/.git-rewrite/map/d15b3eb05e53326a81eab3f6839186e0840cb686 +++ /dev/null @@ -1 +0,0 @@ -d15b3eb05e53326a81eab3f6839186e0840cb686 diff --git a/.git-rewrite/map/d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 b/.git-rewrite/map/d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 deleted file mode 100644 index 40d8a75..0000000 --- a/.git-rewrite/map/d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 +++ /dev/null @@ -1 +0,0 @@ -d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 diff --git a/.git-rewrite/map/e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 b/.git-rewrite/map/e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 deleted file mode 100644 index e9a3715..0000000 --- a/.git-rewrite/map/e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 +++ /dev/null @@ -1 +0,0 @@ -e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 diff --git a/.git-rewrite/map/e363ece5a05eeb55264176e689ffdbf4e79b718f b/.git-rewrite/map/e363ece5a05eeb55264176e689ffdbf4e79b718f deleted file mode 100644 index fc12ccd..0000000 --- a/.git-rewrite/map/e363ece5a05eeb55264176e689ffdbf4e79b718f +++ /dev/null @@ -1 +0,0 @@ -e363ece5a05eeb55264176e689ffdbf4e79b718f diff --git a/.git-rewrite/map/eb7049163bbfa1bc73af5859dff1803901cf43b3 b/.git-rewrite/map/eb7049163bbfa1bc73af5859dff1803901cf43b3 deleted file mode 100644 index 13768aa..0000000 --- a/.git-rewrite/map/eb7049163bbfa1bc73af5859dff1803901cf43b3 +++ /dev/null @@ -1 +0,0 @@ -eb7049163bbfa1bc73af5859dff1803901cf43b3 diff --git a/.git-rewrite/map/f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 b/.git-rewrite/map/f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 deleted file mode 100644 index 42efe5f..0000000 --- a/.git-rewrite/map/f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 +++ /dev/null @@ -1 +0,0 @@ -f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 diff --git a/.git-rewrite/map/f966b6a756f736f3f106a186faac183ce0eb0686 b/.git-rewrite/map/f966b6a756f736f3f106a186faac183ce0eb0686 deleted file mode 100644 index d8641e8..0000000 --- a/.git-rewrite/map/f966b6a756f736f3f106a186faac183ce0eb0686 +++ /dev/null @@ -1 +0,0 @@ -f966b6a756f736f3f106a186faac183ce0eb0686 diff --git a/.git-rewrite/message b/.git-rewrite/message deleted file mode 100644 index 73d874d..0000000 --- a/.git-rewrite/message +++ /dev/null @@ -1 +0,0 @@ -Release 0.0.6 diff --git a/.git-rewrite/parse b/.git-rewrite/parse deleted file mode 100644 index e69de29..0000000 diff --git a/.git-rewrite/raw-refs b/.git-rewrite/raw-refs deleted file mode 100644 index b182939..0000000 --- a/.git-rewrite/raw-refs +++ /dev/null @@ -1 +0,0 @@ -refs/heads/develop diff --git a/.git-rewrite/revs b/.git-rewrite/revs deleted file mode 100644 index 9a1d882..0000000 --- a/.git-rewrite/revs +++ /dev/null @@ -1,212 +0,0 @@ -7a9b11a9e5e63b987fde0e7d8536f9fa76585097 -6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 7a9b11a9e5e63b987fde0e7d8536f9fa76585097 -f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 6d426c2a0bcd5c191c0e95163cf15b69a207fdd5 -76c6c881d54b82d5ca4bc427c2968fbc15c7a090 7a9b11a9e5e63b987fde0e7d8536f9fa76585097 f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 -8159694b935775e4072e1902124a8cb4bb6efacd f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2 76c6c881d54b82d5ca4bc427c2968fbc15c7a090 -07141d3a213a443b830fb05dae87f181a6b213cc 8159694b935775e4072e1902124a8cb4bb6efacd -cf5c4297c9b975dff541a7384da6838dd7fab930 76c6c881d54b82d5ca4bc427c2968fbc15c7a090 07141d3a213a443b830fb05dae87f181a6b213cc -eb7049163bbfa1bc73af5859dff1803901cf43b3 07141d3a213a443b830fb05dae87f181a6b213cc cf5c4297c9b975dff541a7384da6838dd7fab930 -927638f2d500dbfd29371b2b4a3f01b987749899 eb7049163bbfa1bc73af5859dff1803901cf43b3 -50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 cf5c4297c9b975dff541a7384da6838dd7fab930 927638f2d500dbfd29371b2b4a3f01b987749899 -e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 927638f2d500dbfd29371b2b4a3f01b987749899 50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 -a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76 -a3b34ec32effa03acfa4aa99cbe38aee81f01e56 50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2 a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 -d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 a56c9616adbf6f5c387a2ffc472cd42b6b9ba377 a3b34ec32effa03acfa4aa99cbe38aee81f01e56 -b7dbda0db65e24c8ad2549639cd22388db837e1f d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89 -0799a6d67cfd65d0228014478632f85780ed5517 b7dbda0db65e24c8ad2549639cd22388db837e1f -493e8a09aa10d8fe48896c429ea0c1260b2c0709 a3b34ec32effa03acfa4aa99cbe38aee81f01e56 0799a6d67cfd65d0228014478632f85780ed5517 -0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 0799a6d67cfd65d0228014478632f85780ed5517 493e8a09aa10d8fe48896c429ea0c1260b2c0709 -cf12b3968b2d2a59fb1fa12ef83065b1d7090681 0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5 -2e5752f69385f8d3f75ab1446e46d3f2208a543f cf12b3968b2d2a59fb1fa12ef83065b1d7090681 -4e4fc22cb53abe2979d751e63671c771718147e5 2e5752f69385f8d3f75ab1446e46d3f2208a543f -8e819aca56db7ab982243eddda0ab89525db1b23 4e4fc22cb53abe2979d751e63671c771718147e5 -8d6d48c59e9ca78d00a12c715494de99eb40a5ad 8e819aca56db7ab982243eddda0ab89525db1b23 -324de3d99f22bbce918e0246daa6caf90a0fc734 8d6d48c59e9ca78d00a12c715494de99eb40a5ad -335b0f3876e66da7bb06e6c39afb016068da1ac9 324de3d99f22bbce918e0246daa6caf90a0fc734 -a85a74bb9a03dafeaaaf3e2211cb796f195773e3 335b0f3876e66da7bb06e6c39afb016068da1ac9 -ce23e0616e4a29a266bbfc9c398c57c2976dc71a 8d6d48c59e9ca78d00a12c715494de99eb40a5ad a85a74bb9a03dafeaaaf3e2211cb796f195773e3 -7cbe3a04ef7b95789287cf8fa7d654e18df11084 ce23e0616e4a29a266bbfc9c398c57c2976dc71a -f966b6a756f736f3f106a186faac183ce0eb0686 493e8a09aa10d8fe48896c429ea0c1260b2c0709 7cbe3a04ef7b95789287cf8fa7d654e18df11084 -a810bdae244404a91d17c6293f3096b055dd74cf 7cbe3a04ef7b95789287cf8fa7d654e18df11084 f966b6a756f736f3f106a186faac183ce0eb0686 -56449dd30e9f26312ddb2117faf319a514f73f1f a810bdae244404a91d17c6293f3096b055dd74cf -38da162cd90445a64e6586f72fed0a3c5e7dfce2 56449dd30e9f26312ddb2117faf319a514f73f1f -c6f1c8213b0bb6489072bc379ae8b158e6a552d9 f966b6a756f736f3f106a186faac183ce0eb0686 38da162cd90445a64e6586f72fed0a3c5e7dfce2 -55fd56a4a3e40acbfa4720749018d13873d8af62 38da162cd90445a64e6586f72fed0a3c5e7dfce2 c6f1c8213b0bb6489072bc379ae8b158e6a552d9 -3bbc12286966b69455025aa4de3e88ecda439811 55fd56a4a3e40acbfa4720749018d13873d8af62 -7fd73a6fdb49843d29aefa2bb8bb5de900e53811 3bbc12286966b69455025aa4de3e88ecda439811 -83f289eb40b34c07ad6b5df1099a7b6427e9be40 7fd73a6fdb49843d29aefa2bb8bb5de900e53811 -72709acb903556967dd5d1cead1f5b052151bb25 83f289eb40b34c07ad6b5df1099a7b6427e9be40 -d15b3eb05e53326a81eab3f6839186e0840cb686 72709acb903556967dd5d1cead1f5b052151bb25 -bca7bec867cd6aff1b1692ed5508c18e62aa430b d15b3eb05e53326a81eab3f6839186e0840cb686 -4b707537b9334641c37fb1d1aab96fe708e507ec 7cbe3a04ef7b95789287cf8fa7d654e18df11084 -ad1037c02b654b7e582c2c644841ea7477fcf9b4 4b707537b9334641c37fb1d1aab96fe708e507ec -154b2342056d3adb1be716e16f8919319d3c6dba ad1037c02b654b7e582c2c644841ea7477fcf9b4 -41c7c2a93af86acb48e5dd8b3d9406820c547b13 154b2342056d3adb1be716e16f8919319d3c6dba -2944c2a32f6e1e3cbb7eb9603188b339f6d74700 41c7c2a93af86acb48e5dd8b3d9406820c547b13 -00d254d7c41be358cc0a99d388e796703c3ee5b9 2944c2a32f6e1e3cbb7eb9603188b339f6d74700 -8d308a6776436ce1189e45268e4eceb8b5a1011a 00d254d7c41be358cc0a99d388e796703c3ee5b9 -3103b50f0857d18c48ec60ee622fabf4938c938f 8d308a6776436ce1189e45268e4eceb8b5a1011a -8f474bc31303882270112cdfafeaedc908938707 3103b50f0857d18c48ec60ee622fabf4938c938f -8866720631a717b8df2df6fc206eb6db44f8a005 bca7bec867cd6aff1b1692ed5508c18e62aa430b 8f474bc31303882270112cdfafeaedc908938707 -8019d3e0e2a8a01f729911730128de72a2a1b6ba 8866720631a717b8df2df6fc206eb6db44f8a005 -bd87dcabf6b370316eac39b863ee5d84ad5851e2 8019d3e0e2a8a01f729911730128de72a2a1b6ba -86d73f9118eee40fd842fc25898490a2923eaecd bd87dcabf6b370316eac39b863ee5d84ad5851e2 -e363ece5a05eeb55264176e689ffdbf4e79b718f c6f1c8213b0bb6489072bc379ae8b158e6a552d9 86d73f9118eee40fd842fc25898490a2923eaecd -9a27f330791c08ee87dbb886c6d511bb6d3bffa0 bd87dcabf6b370316eac39b863ee5d84ad5851e2 e363ece5a05eeb55264176e689ffdbf4e79b718f -6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 9a27f330791c08ee87dbb886c6d511bb6d3bffa0 -1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 -48db1437b3a9ea21bc987513c01bc4800b06f0f9 e363ece5a05eeb55264176e689ffdbf4e79b718f 1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0 -19966fad81e043ef9e2a2d062411e3990dad449d 6fe1bdb579f0924b10b28f1fc9587ac49fc44f24 48db1437b3a9ea21bc987513c01bc4800b06f0f9 -802f6f5672200fff036a72abd84f39e78483d61f 19966fad81e043ef9e2a2d062411e3990dad449d -f74bb70499424355b8ca1a0e1338131b0ecabd34 802f6f5672200fff036a72abd84f39e78483d61f -9d257dd3c03a3d07323d5f74f6d94bc7f02f4de7 48db1437b3a9ea21bc987513c01bc4800b06f0f9 f74bb70499424355b8ca1a0e1338131b0ecabd34 -f807c166f7265440c9a190d05f67904d99812f65 802f6f5672200fff036a72abd84f39e78483d61f 9d257dd3c03a3d07323d5f74f6d94bc7f02f4de7 -9fed08245a87d631d1ee4f9d75a60dc1d8940a3e f807c166f7265440c9a190d05f67904d99812f65 -123b0f19dbcd0542a73df6f487372ace03bfc5ff 19966fad81e043ef9e2a2d062411e3990dad449d -4b9b60f2476275e3cf4dab64d61d8512318c4605 9fed08245a87d631d1ee4f9d75a60dc1d8940a3e 123b0f19dbcd0542a73df6f487372ace03bfc5ff -47376d4db98f6b303e05240bbd154085442cb913 4b9b60f2476275e3cf4dab64d61d8512318c4605 -74fe6d55b4ed096bb3b3a3a497389312e6f3adcc 47376d4db98f6b303e05240bbd154085442cb913 -fc8148bfb33835495499227d9b808d0572c0b7ee 9d257dd3c03a3d07323d5f74f6d94bc7f02f4de7 74fe6d55b4ed096bb3b3a3a497389312e6f3adcc -e0f7d88d6138e849aee9aa454b32206c0f147ae3 47376d4db98f6b303e05240bbd154085442cb913 fc8148bfb33835495499227d9b808d0572c0b7ee -f7502b1c143672d92b32f24974fc95f2ae4111b9 e0f7d88d6138e849aee9aa454b32206c0f147ae3 -970a534d77b0ef4c9ea3ff201f5e6281b1bacef2 f7502b1c143672d92b32f24974fc95f2ae4111b9 -3b65a8852e2dfd64130e1bf4b4e625d55813c62c 970a534d77b0ef4c9ea3ff201f5e6281b1bacef2 -cbe61e3f2e68d4c3b607ff8ad2cfb8dabbb11a3a 3b65a8852e2dfd64130e1bf4b4e625d55813c62c -54d3f6e3adee9d4c4cb8f668d2cd1dfa5e28029e cbe61e3f2e68d4c3b607ff8ad2cfb8dabbb11a3a -22a476ded520ce7052e3960e546ac1136fada0e8 54d3f6e3adee9d4c4cb8f668d2cd1dfa5e28029e -9fc7202552ced5424f01fb2815e47d8ae98131d2 fc8148bfb33835495499227d9b808d0572c0b7ee 22a476ded520ce7052e3960e546ac1136fada0e8 -4f266cd3f3d9a0592928496f37d7c4793ab0ecd1 54d3f6e3adee9d4c4cb8f668d2cd1dfa5e28029e 9fc7202552ced5424f01fb2815e47d8ae98131d2 -2d221a6b67c39c0f96f2f864e9edc5f91fed3616 4f266cd3f3d9a0592928496f37d7c4793ab0ecd1 -ad49bf28982b20db3d541313180ef22337574d57 2d221a6b67c39c0f96f2f864e9edc5f91fed3616 -58601710028dd8d17eb837df915f1b38826b1fca ad49bf28982b20db3d541313180ef22337574d57 -b9043b6c39124ffc60859515f594bd3c23d5f726 58601710028dd8d17eb837df915f1b38826b1fca -b57e340389c1ab3ec99cc1bd04e3f0fba0b51351 9fc7202552ced5424f01fb2815e47d8ae98131d2 b9043b6c39124ffc60859515f594bd3c23d5f726 -9bc8d005fb6693dc7349ac2f9fee6594c6c662d5 58601710028dd8d17eb837df915f1b38826b1fca b57e340389c1ab3ec99cc1bd04e3f0fba0b51351 -25090aeb2a86baecc692a916120e6e169b97bcc5 9bc8d005fb6693dc7349ac2f9fee6594c6c662d5 -56fdaa1224fbdb768758889fd8e18fb3bfada309 25090aeb2a86baecc692a916120e6e169b97bcc5 -73d2b1ba930a48b379132e081ee2baed319b26f5 56fdaa1224fbdb768758889fd8e18fb3bfada309 -ec5267f5a5e2e3a06da379aa49fa8232a1afe732 b57e340389c1ab3ec99cc1bd04e3f0fba0b51351 73d2b1ba930a48b379132e081ee2baed319b26f5 -ba8c4cd2aa58c2add42ed1e007506a48c765768a 56fdaa1224fbdb768758889fd8e18fb3bfada309 ec5267f5a5e2e3a06da379aa49fa8232a1afe732 -a3b92711a971d123fd2f2053ca558dd62d20a083 ec5267f5a5e2e3a06da379aa49fa8232a1afe732 -00d6463de1cb5217e11dbb52c9a78b871f3020cd ec5267f5a5e2e3a06da379aa49fa8232a1afe732 a3b92711a971d123fd2f2053ca558dd62d20a083 -a77b5323289ef0e72fdbc1660c32d302c963d665 ba8c4cd2aa58c2add42ed1e007506a48c765768a 00d6463de1cb5217e11dbb52c9a78b871f3020cd -37eec298d7c990b34dfe630f905c0f2d6144b275 a77b5323289ef0e72fdbc1660c32d302c963d665 -056bef7d5eb75d51b812f50b02df22fab39a8df8 37eec298d7c990b34dfe630f905c0f2d6144b275 -11554437854646c5ee26d3056c2b9e2d3f89de33 056bef7d5eb75d51b812f50b02df22fab39a8df8 -f6ae6bbdbbd3ff69e431b690c4da1bc31a46396d 37eec298d7c990b34dfe630f905c0f2d6144b275 11554437854646c5ee26d3056c2b9e2d3f89de33 -788e390e01efdda0513b617eceae64f424c97f11 f6ae6bbdbbd3ff69e431b690c4da1bc31a46396d -544f0175d9968151df8fcd537f6399ea3fcb05fa 788e390e01efdda0513b617eceae64f424c97f11 -642487742c8ba27f1a24c68098bfaa81cff0677a 544f0175d9968151df8fcd537f6399ea3fcb05fa -39132723dbc0bf3da3e5b022ad06866deafc44c7 642487742c8ba27f1a24c68098bfaa81cff0677a -cf8994ceaf370677336e1d8c6d34f113f9e780e6 39132723dbc0bf3da3e5b022ad06866deafc44c7 -8affa0f3757cf92c1b5604499d6912101cf47cff 00d6463de1cb5217e11dbb52c9a78b871f3020cd cf8994ceaf370677336e1d8c6d34f113f9e780e6 -9fc5318e8644b6ada6fe127c97cfd2590ff97789 39132723dbc0bf3da3e5b022ad06866deafc44c7 8affa0f3757cf92c1b5604499d6912101cf47cff -a14cdce07f9e39bd428a22f6c40b3849e0aadfc2 9fc5318e8644b6ada6fe127c97cfd2590ff97789 -09b704bcd73f4158b63218053c82f0af7dc3f14c 9fc5318e8644b6ada6fe127c97cfd2590ff97789 -d9f1f88107ee7b447a3088a3a6d76181f5fbb000 a14cdce07f9e39bd428a22f6c40b3849e0aadfc2 09b704bcd73f4158b63218053c82f0af7dc3f14c -f260495495bf90c93dd0d958b19d925b57b62e3e d9f1f88107ee7b447a3088a3a6d76181f5fbb000 -d4cf95363f00b56453bd9ae58a20e4c9a39dd715 f260495495bf90c93dd0d958b19d925b57b62e3e -e03618570d2da623a0a4e8823e4a8fe9a4bbca38 d4cf95363f00b56453bd9ae58a20e4c9a39dd715 -61e15e415519f6cae4cc94b3c3558d310b087f7b e03618570d2da623a0a4e8823e4a8fe9a4bbca38 -29c7295d16e99969ef39f8d338b88e143820745b d9f1f88107ee7b447a3088a3a6d76181f5fbb000 61e15e415519f6cae4cc94b3c3558d310b087f7b -c9c910ab7cb5ad637ee7d01abf9e1a832ca088ff 29c7295d16e99969ef39f8d338b88e143820745b -aba3c46eb1a985014ed56ca2d93853e97b36b8b4 c9c910ab7cb5ad637ee7d01abf9e1a832ca088ff -94feb2ccaa55aacc21f2538b269ef514f92b3f81 aba3c46eb1a985014ed56ca2d93853e97b36b8b4 -18b0f5b790ef622350b7020bae71ff73b3e23166 94feb2ccaa55aacc21f2538b269ef514f92b3f81 -97bf9c257cbd52fbb0c519c93a141d894daf94df 8affa0f3757cf92c1b5604499d6912101cf47cff 18b0f5b790ef622350b7020bae71ff73b3e23166 -93d5289d25dd4785ecaf54ca19f921c0de03c60b 94feb2ccaa55aacc21f2538b269ef514f92b3f81 97bf9c257cbd52fbb0c519c93a141d894daf94df -18b5ce8c1813c232467bc74e10b259799e068ccb 93d5289d25dd4785ecaf54ca19f921c0de03c60b -9ab28d606af35cdace55be4921105516360db310 18b5ce8c1813c232467bc74e10b259799e068ccb -486388a7988427a0140a6f89d1d14c4253202f06 9ab28d606af35cdace55be4921105516360db310 -15859f7be9f6fef815e6ad38f76634618e1dc6e0 486388a7988427a0140a6f89d1d14c4253202f06 -444b65d3713bc274c6ee27551d3ce0ef1f3837c8 15859f7be9f6fef815e6ad38f76634618e1dc6e0 -49bc62f0aa2872347d77e6503c5d412393bde03f 444b65d3713bc274c6ee27551d3ce0ef1f3837c8 -cc15ada3043b71986a2d48df8027b0e41ba7df5f 49bc62f0aa2872347d77e6503c5d412393bde03f -86cc237c78778c27950906111dc797a87319ec01 cc15ada3043b71986a2d48df8027b0e41ba7df5f -799987ecb19aa16cc31cbff5d7d96a99ef0fd3ee 86cc237c78778c27950906111dc797a87319ec01 -79b68923200dbe52bdf2b87ceffe41aec0323552 97bf9c257cbd52fbb0c519c93a141d894daf94df 799987ecb19aa16cc31cbff5d7d96a99ef0fd3ee -f33cbce63fc3ca197477d8e42162feee0f1745a5 86cc237c78778c27950906111dc797a87319ec01 79b68923200dbe52bdf2b87ceffe41aec0323552 -2d57839b3ebf21e5bb92b9089c0241f8a774cc49 86cc237c78778c27950906111dc797a87319ec01 -1f7dd0287a4873810daace1c814cc464da6c60e2 f33cbce63fc3ca197477d8e42162feee0f1745a5 2d57839b3ebf21e5bb92b9089c0241f8a774cc49 -b6a87b9410f6a185f672147c283ea298f2b22c20 1f7dd0287a4873810daace1c814cc464da6c60e2 -2ae51c3f64e000c2d04b58e0b5c41f93be61c5b9 79b68923200dbe52bdf2b87ceffe41aec0323552 b6a87b9410f6a185f672147c283ea298f2b22c20 -bc30b012cf25ed3aa725191be5916f3f1e3026eb 1f7dd0287a4873810daace1c814cc464da6c60e2 2ae51c3f64e000c2d04b58e0b5c41f93be61c5b9 -ed28a4cc0d36d2e25096f1ed6863e2624b81dfff bc30b012cf25ed3aa725191be5916f3f1e3026eb -133b36c34c4fe9ec3eb0b6bdb8d0f1f1a547d065 ed28a4cc0d36d2e25096f1ed6863e2624b81dfff -1aa58eeaafd961fe987c8bd12be1b9207c4f6309 133b36c34c4fe9ec3eb0b6bdb8d0f1f1a547d065 -a6693481fa0fa92f2c1d7c768a2bb4c78e1024f9 1aa58eeaafd961fe987c8bd12be1b9207c4f6309 -53e199b20ff53845562a520da4bfaecf0189fad2 a6693481fa0fa92f2c1d7c768a2bb4c78e1024f9 -a8945bd3032c60ef3522b7aeb89f69b13d292931 53e199b20ff53845562a520da4bfaecf0189fad2 -8b6b6640d5c9066e34cc5bdaf9a89695afe37be0 2ae51c3f64e000c2d04b58e0b5c41f93be61c5b9 a8945bd3032c60ef3522b7aeb89f69b13d292931 -40cb76810e4daabd4d265e29226f20b93fd0e791 53e199b20ff53845562a520da4bfaecf0189fad2 8b6b6640d5c9066e34cc5bdaf9a89695afe37be0 -3054dfe79e2912a86c17ee61273728bad3a28a4b 40cb76810e4daabd4d265e29226f20b93fd0e791 -5a9fd0686eb6a1a89524449ab8711257c37b77dc 3054dfe79e2912a86c17ee61273728bad3a28a4b -77e6b88c5dddfa18327a299cf39a61d1f7c2b0bb 5a9fd0686eb6a1a89524449ab8711257c37b77dc -9f5d329105d30d0a48c47413fcf23dc8328ad526 77e6b88c5dddfa18327a299cf39a61d1f7c2b0bb -eff483c1c47cda01da415f58b101f0348c61c200 9f5d329105d30d0a48c47413fcf23dc8328ad526 -54ba59872e27d4abd8e2ef6dd07e0805a4fce505 eff483c1c47cda01da415f58b101f0348c61c200 -eb796924b16fd84a073eab388c5ee23842d029f2 5a9fd0686eb6a1a89524449ab8711257c37b77dc 54ba59872e27d4abd8e2ef6dd07e0805a4fce505 -b6940eccffdafcfc213d6a7c4ba33ac29f7352ba 8b6b6640d5c9066e34cc5bdaf9a89695afe37be0 eb796924b16fd84a073eab388c5ee23842d029f2 -23e47c567a018f4f613f617aa2dc0711670b74a8 eb796924b16fd84a073eab388c5ee23842d029f2 b6940eccffdafcfc213d6a7c4ba33ac29f7352ba -12c54f4bb3020f26177332c110e438abb0d46dae 23e47c567a018f4f613f617aa2dc0711670b74a8 -5d530edfabd1bce8d692ca5573464e2807bcd9f9 12c54f4bb3020f26177332c110e438abb0d46dae -f87242452610692e4346ef632d4286ee6ee75311 5d530edfabd1bce8d692ca5573464e2807bcd9f9 -f64f3771997e87bf5aed8f1b348e4af39d7e2b7e 12c54f4bb3020f26177332c110e438abb0d46dae -6233cb1e07633d75a9164bd247b7585c408a0a79 f64f3771997e87bf5aed8f1b348e4af39d7e2b7e -9501b460c5c55c64a8af5714660ace9f2af88a88 6233cb1e07633d75a9164bd247b7585c408a0a79 -3c81e152e66b5ca7f63c71d5c9fab85a0e3a1247 9501b460c5c55c64a8af5714660ace9f2af88a88 -6dbdbf16379c0311c197bbac5c468f146e908fed 3c81e152e66b5ca7f63c71d5c9fab85a0e3a1247 -43403bc6f724c52cda644d91cbf1a8b581d691e1 6dbdbf16379c0311c197bbac5c468f146e908fed -695270e515ea1ce8b2b4dd9758dfefe7f0a02770 f87242452610692e4346ef632d4286ee6ee75311 43403bc6f724c52cda644d91cbf1a8b581d691e1 -ecd3a97853b0f7a32648a35313c5ee2571452c71 695270e515ea1ce8b2b4dd9758dfefe7f0a02770 -98026e0685984a709dd9d9c659cb718b562df5ea b6940eccffdafcfc213d6a7c4ba33ac29f7352ba ecd3a97853b0f7a32648a35313c5ee2571452c71 -93c3dec66edb51a049c55f94ee432c5228cd24f3 695270e515ea1ce8b2b4dd9758dfefe7f0a02770 98026e0685984a709dd9d9c659cb718b562df5ea -bf4d5fbc6b49f68afd9d49b3077f98da5c3336ff 93c3dec66edb51a049c55f94ee432c5228cd24f3 -e6a5fceeddefb8a750b38003a90c8ee1d58bdaec bf4d5fbc6b49f68afd9d49b3077f98da5c3336ff -e4a7212f89cf6ffba37aa61162758fde6c8bdf15 e6a5fceeddefb8a750b38003a90c8ee1d58bdaec -d4e520772e265544dbf8975d5307e43bf6e2f849 e4a7212f89cf6ffba37aa61162758fde6c8bdf15 -fea733a43ec0cdc9796cd86cdf19faac8f4be846 d4e520772e265544dbf8975d5307e43bf6e2f849 -5cd0a6e2f39828149dea886ee10f0c937b0e83de fea733a43ec0cdc9796cd86cdf19faac8f4be846 -3ee91eb6c84599bedf195bef5c01c2ff785d7e26 5cd0a6e2f39828149dea886ee10f0c937b0e83de -0e83a5a98535b43d9ead28cfb0be472bf0ef3767 98026e0685984a709dd9d9c659cb718b562df5ea 3ee91eb6c84599bedf195bef5c01c2ff785d7e26 -faf8bd4a08db065bb85b0b642cc8114e8bf94725 5cd0a6e2f39828149dea886ee10f0c937b0e83de 0e83a5a98535b43d9ead28cfb0be472bf0ef3767 -088b772de59cc9638dd44ffe468d9e7650db62ff faf8bd4a08db065bb85b0b642cc8114e8bf94725 -8e100ff21bc0a90ac8033b3d2aecea79734ab605 088b772de59cc9638dd44ffe468d9e7650db62ff -c1d4a73440a77f2993f4607ae4e849c3a31767d0 0e83a5a98535b43d9ead28cfb0be472bf0ef3767 8e100ff21bc0a90ac8033b3d2aecea79734ab605 -8202c94a438d9909d97219136f4d92b4e729938a 088b772de59cc9638dd44ffe468d9e7650db62ff c1d4a73440a77f2993f4607ae4e849c3a31767d0 -812c9b99d10013dd349146577519c5019185fdd3 8202c94a438d9909d97219136f4d92b4e729938a -6b2e5b2e416abe260eeeea208454770d67ac5f9f 812c9b99d10013dd349146577519c5019185fdd3 -33dcd489ebe126c89f6fa92b6f55185111e17551 6b2e5b2e416abe260eeeea208454770d67ac5f9f -efe1ac732eee4bb280a51bfcc3f809e27adcbf6c 33dcd489ebe126c89f6fa92b6f55185111e17551 -66aead387ebcbbbfa59d590dae96a5d255d43c25 c1d4a73440a77f2993f4607ae4e849c3a31767d0 efe1ac732eee4bb280a51bfcc3f809e27adcbf6c -edab9e1b6b7089adf5447b76e2b082076d87c6b8 33dcd489ebe126c89f6fa92b6f55185111e17551 66aead387ebcbbbfa59d590dae96a5d255d43c25 -0da89d91dde7a7f77a4cf69e1dbd2ec3af619835 edab9e1b6b7089adf5447b76e2b082076d87c6b8 -d70f2fd196875282278d5455819dcfa238948f59 0da89d91dde7a7f77a4cf69e1dbd2ec3af619835 -b228c9477ee56f89a09cce3adf2340b28817150c 66aead387ebcbbbfa59d590dae96a5d255d43c25 d70f2fd196875282278d5455819dcfa238948f59 -a426f64795667bd144151c0ebec4ec57cfcb85f1 d70f2fd196875282278d5455819dcfa238948f59 -6aeebb9824b93261ba8270db34ebf8eac76b28b0 a426f64795667bd144151c0ebec4ec57cfcb85f1 -4e2e59ec87d6cad9ee9084b683323e094fbfa1ad 6aeebb9824b93261ba8270db34ebf8eac76b28b0 -f6a8de32150407469750a153ba07b3aaf0699a3c 4e2e59ec87d6cad9ee9084b683323e094fbfa1ad -d29a7d6218772ee19d2fe1396fc5cf6e611f0ff4 f6a8de32150407469750a153ba07b3aaf0699a3c -51e458ad571807c6edb5d4a1399eed6a031b537b d29a7d6218772ee19d2fe1396fc5cf6e611f0ff4 -867dbf41d5cc8ca3fde4dd539afb488a401f357d 51e458ad571807c6edb5d4a1399eed6a031b537b -86abc7ea68faad8bca54fafcaeaf1f84a6478caf 867dbf41d5cc8ca3fde4dd539afb488a401f357d -9219b2d411a40573e04bf69db37891720786e494 b228c9477ee56f89a09cce3adf2340b28817150c 86abc7ea68faad8bca54fafcaeaf1f84a6478caf -5c362c14301b9273ed1dae7edcf373d50b7f9f24 867dbf41d5cc8ca3fde4dd539afb488a401f357d 9219b2d411a40573e04bf69db37891720786e494 -997e041042c1f9bdef18c0cfd1ffbf7a4a28695b 5c362c14301b9273ed1dae7edcf373d50b7f9f24 -5d6aa7c48a8116840ce35691cf08df885919a9ab 997e041042c1f9bdef18c0cfd1ffbf7a4a28695b -2bc2660ad5cfe4d917e909663b065faef48b3fc5 5d6aa7c48a8116840ce35691cf08df885919a9ab -a372e8150eafd25b90bd24df12c4942af56d6ccd 2bc2660ad5cfe4d917e909663b065faef48b3fc5 -8bdd0cc635765134d88d08f80c8d70c6b36235e2 a372e8150eafd25b90bd24df12c4942af56d6ccd -8f05b979478b076b77e9e16023b7040ad07c5862 9219b2d411a40573e04bf69db37891720786e494 8bdd0cc635765134d88d08f80c8d70c6b36235e2 -583df9ee1f5d27a43c6fa121c81f974a4e9184da a372e8150eafd25b90bd24df12c4942af56d6ccd 8f05b979478b076b77e9e16023b7040ad07c5862 -388bea740bfde42852f8a4aee6c495c6d81c7251 583df9ee1f5d27a43c6fa121c81f974a4e9184da -fd1346c5f402bae694090b409b4e38968ada7793 388bea740bfde42852f8a4aee6c495c6d81c7251 -d918fdb1373d326c4ce494168f5fa2ccc36a7dba fd1346c5f402bae694090b409b4e38968ada7793 -fced6719b10e7f26f96ed4e6d550347d032d7f04 8f05b979478b076b77e9e16023b7040ad07c5862 d918fdb1373d326c4ce494168f5fa2ccc36a7dba -2d132913c37a42dd79086e760c14431837bc520b fd1346c5f402bae694090b409b4e38968ada7793 fced6719b10e7f26f96ed4e6d550347d032d7f04 -d98e67ac0cc9ceb4c51dcc96f80247c0bdafb1a2 2d132913c37a42dd79086e760c14431837bc520b -2274d02be6b11cfa50d78e5689aa092f63d605f1 d98e67ac0cc9ceb4c51dcc96f80247c0bdafb1a2 -3cd97481ece8b0f0cf160819fe28187bace1c61c 2274d02be6b11cfa50d78e5689aa092f63d605f1 -89e6fb4c42d70fa5a44075a2023cc6b520c17d8c fced6719b10e7f26f96ed4e6d550347d032d7f04 3cd97481ece8b0f0cf160819fe28187bace1c61c -7c73d1883ba2ee2a9fe39d7c1d90e19d9606b0a1 2274d02be6b11cfa50d78e5689aa092f63d605f1 89e6fb4c42d70fa5a44075a2023cc6b520c17d8c From b1dc121cdd9cff8a0eda92b8f1e3696f25050099 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Tue, 4 Oct 2022 17:41:25 +1300 Subject: [PATCH 2/8] UI: Hide delete all / mark all read in message view --- server/ui-src/App.vue | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/server/ui-src/App.vue b/server/ui-src/App.vue index cf1d014..1bb0ba1 100644 --- a/server/ui-src/App.vue +++ b/server/ui-src/App.vue @@ -305,6 +305,32 @@ export default { }); }, + // test of any selected emails are unread + selectedHasUnread: function() { + if (!this.selected.length) { + return false; + } + for (let i in this.items) { + if (this.isSelected(this.items[i].ID) && !this.items[i].Read) { + return true; + } + } + return false; + }, + + // test of any selected emails are read + selectedHasRead: function() { + if (!this.selected.length) { + return false; + } + for (let i in this.items) { + if (this.isSelected(this.items[i].ID) && this.items[i].Read) { + return true; + } + } + return false; + }, + // websocket connect connect: function () { let wsproto = location.protocol == 'https:' ? 'wss' : 'ws'; @@ -556,13 +582,13 @@ export default { -
  • +
  • Mark all read
  • -
  • +
  • Delete all @@ -573,20 +599,20 @@ export default { Selected {{selected.length}}
  • -
  • +
  • Mark read
  • -
  • +
  • Mark unread
  • - + Delete From 4a92b99a53f9f3dc88dfc5f474bd1e4ecb595857 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Fri, 7 Oct 2022 19:25:26 +1300 Subject: [PATCH 3/8] Optimise Mailpit SVG logo --- server/ui/mailpit.svg | 98 +------------------------------------------ 1 file changed, 1 insertion(+), 97 deletions(-) diff --git a/server/ui/mailpit.svg b/server/ui/mailpit.svg index 94499f5..9c6cc4c 100644 --- a/server/ui/mailpit.svg +++ b/server/ui/mailpit.svg @@ -1,97 +1 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - + \ No newline at end of file From 34da0e50426bf7b5f182ab27f2b3e77a59a973c9 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Fri, 7 Oct 2022 19:46:39 +1300 Subject: [PATCH 4/8] Feature: Add REST API Requested feature for integration, see #15 --- README.md | 1 + config/config.go | 3 + data/message.go | 1 - docs/apiv1/Message.md | 80 +++++++++ docs/apiv1/Messages.md | 166 ++++++++++++++++++ docs/apiv1/README.md | 11 ++ docs/apiv1/Search.md | 67 +++++++ server/api.go | 279 ----------------------------- server/apiv1/api.go | 289 +++++++++++++++++++++++++++++++ server/{ => apiv1}/thumbnails.go | 6 +- server/server.go | 82 +++------ storage/database.go | 62 +++++-- 12 files changed, 695 insertions(+), 352 deletions(-) create mode 100644 docs/apiv1/Message.md create mode 100644 docs/apiv1/Messages.md create mode 100644 docs/apiv1/README.md create mode 100644 docs/apiv1/Search.md delete mode 100644 server/api.go create mode 100644 server/apiv1/api.go rename server/{ => apiv1}/thumbnails.go (94%) diff --git a/README.md b/README.md index 2f2b608..43b9d05 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster. - Optional SMTP with STARTTLS & SMTP authentication ([see wiki](https://github.com/axllent/mailpit/wiki/SMTP-with-STARTTLS-and-authentication)) - Optional HTTPS for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/HTTPS)) - Optional basic authentication for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/Basic-authentication)) +- A simple REST API allowing ([see docs](docs/apiv1/README.md)) - Multi-architecture [Docker images](https://github.com/axllent/mailpit/wiki/Docker-images) diff --git a/config/config.go b/config/config.go index ed199d3..5fc906f 100644 --- a/config/config.go +++ b/config/config.go @@ -55,6 +55,9 @@ var ( // SMTPAuth used for euthentication SMTPAuth *htpasswd.File + + // ContentSecurityPolicy for HTTP server + ContentSecurityPolicy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'self'; img-src * data: blob:; font-src 'self' data:; media-src 'self'; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'self';" ) // VerifyConfig wil do some basic checking diff --git a/data/message.go b/data/message.go index da6d6d1..a6cf788 100644 --- a/data/message.go +++ b/data/message.go @@ -20,7 +20,6 @@ type Message struct { Date time.Time Text string HTML string - HTMLSource string Size int Inline []Attachment Attachments []Attachment diff --git a/docs/apiv1/Message.md b/docs/apiv1/Message.md new file mode 100644 index 0000000..064e4ad --- /dev/null +++ b/docs/apiv1/Message.md @@ -0,0 +1,80 @@ +# Message + +Returns a summary of the message and attachments. + +**URL** : `api/v1/message/` + +**Method** : `GET` + +## Response + +**Status** : `200` + +```json +{ + "ID": "d7a5543b-96dd-478b-9b60-2b465c9884de", + "Read": true, + "From": { + "Name": "John Doe", + "Address": "john@example.com" + }, + "To": [ + { + "Name": "Jane Smith", + "Address": "jane@example.com" + } + ], + "Cc": null, + "Bcc": null, + "Subject": "Message subject", + "Date": "2016-09-07T16:46:00+13:00", + "Text": "Plain text MIME part of the email", + "HTML": "HTML MIME part (if exists)", + "Size": 79499, + "Inline": [ + { + "PartID": "1.2", + "FileName": "filename.gif", + "ContentType": "image/gif", + "ContentID": "919564503@07092006-1525", + "Size": 7760 + } + ], + "Attachments": [ + { + "PartID": "2", + "FileName": "filename.doc", + "ContentType": "application/msword", + "ContentID": "", + "Size": 43520 + } + ] +} +``` +### Notes + +- `Read` - always true (message marked read on open) +- `From` - Name & Address, or null +- `To`, `CC`, `BCC` - Array of Names & Address, or null +- `Date` - Parsed email local date & time from headers +- `Size` - Total size of raw email +- `Inline`, `Attachments` - Array of attachments and inline images. + + +--- +## Attachments + +**URL** : `api/v1/message//part/` + +**Method** : `GET` + +Returns the attachment using the MIME type provided by the attachment `ContentType`. + +--- +## Raw (source) email + +**URL** : `api/v1/message//raw` + +**Method** : `GET` + +Returns the original email source including headers and attachments. diff --git a/docs/apiv1/Messages.md b/docs/apiv1/Messages.md new file mode 100644 index 0000000..a4d5aea --- /dev/null +++ b/docs/apiv1/Messages.md @@ -0,0 +1,166 @@ +# Messages + +List & delete messages. + + +--- +## List + +List messages in the mailbox. Messages are returned in the order of latest received to oldest. + +**URL** : `api/v1/messages` + +**Method** : `GET` + + +### Query parameters + +| Parameter | Type | Required | Description | +|-----------|---------|----------|----------------------------| +| limit | integer | false | Limit results (default 50) | +| start | integer | false | Pagination offset | + + +### Response + +**Status** : `200` + +```json +{ + "total": 500, + "unread": 500, + "count": 50, + "start": 0, + "messages": [ + { + "ID": "1c575821-70ba-466f-8cee-2e1cf0fcdd0f", + "Read": false, + "From": { + "Name": "John Doe", + "Address": "john@example.com" + }, + "To": [ + { + "Name": "Jane Smith", + "Address": "jane@example.com" + } + ], + "Cc": [ + { + "Name": "Accounts", + "Address": "accounts@example.com" + } + ], + "Bcc": null, + "Subject": "Message subject", + "Created": "2022-10-03T21:35:32.228605299+13:00", + "Size": 6144, + "Attachments": 0 + }, + ... + ] +} +``` + +### Notes + +- `total` - Total messages in mailbox +- `unread` - Total unread messages in mailbox +- `count` - Number of messages returned in request +- `start` - The offset (default `0`) for pagination +- `Read` - The read/unread status of the message +- `From` - Name & Address, or null if none +- `To`, `CC`, `BCC` - Array of Names & Address, or null if none +- `Created` - Local date & time the message was received +- `Size` - Total size of raw email in bytes + + +--- +## Delete individual messages + +Delete one or more messages by ID. + +**URL** : `api/v1/messages` + +**Method** : `DELETE` + +### Request + +```json +{ + "ids": ["",""...] +} +``` + +### Response + +**Status** : `200` + + +--- +## Delete all messages + +Delete all messages (same as deleting individual messages, but with the "ids" either empty or omitted entirely). + +**URL** : `api/v1/messages` + +**Method** : `DELETE` + +### Request + +```json +{ + "ids": [] +} +``` + +### Response + +**Status** : `200` + + +--- +## Update individual read statuses + +Set the read status of one or more messages. +The `read` status can be `true` or `false`. + +**URL** : `api/v1/messages` + +**Method** : `PUT` + +### Request + +```json +{ + "ids": ["",""...], + "read": false +} +``` + +### Response + +**Status** : `200` + +--- +## Update all messages read status + +Set the read status of all messages. +The `read` status can be `true` or `false`. + +**URL** : `api/v1/messages` + +**Method** : `PUT` + +### Request + +```json +{ + "ids": [], + "read": false +} +``` + +### Response + +**Status** : `200` diff --git a/docs/apiv1/README.md b/docs/apiv1/README.md new file mode 100644 index 0000000..3b6a133 --- /dev/null +++ b/docs/apiv1/README.md @@ -0,0 +1,11 @@ +# API v1 + +Mailpit provides a simple REST API to access and delete stored messages. + +If the Mailpit server is set to use Basic Authentication, then API requests must use Basic Authentication too. + +The API is split into three main parts: + +- [Messages](Messages.md) - Listing, deleting & marking messages as read/unread. +- [Message](Message.md) - Return message data & attachments +- [Search](Search.md) - Searching messages diff --git a/docs/apiv1/Search.md b/docs/apiv1/Search.md new file mode 100644 index 0000000..58cb5cf --- /dev/null +++ b/docs/apiv1/Search.md @@ -0,0 +1,67 @@ +# Search + +**URL** : `api/v1/search?query=` + +**Method** : `GET` + +The search returns up to 200 of the most recent matches, and does not support pagination or limits. +Matching messages are returned in the order of latest received to oldest. + + +## Query parameters + +| Parameter | Type | Required | Description | +|-----------|--------|----------|--------------| +| query | string | true | Search query | + + +## Response + +**Status** : `200` + +```json +{ + "total": 500, + "unread": 500, + "count": 25, + "start": 0, + "messages": [ + { + "ID": "1c575821-70ba-466f-8cee-2e1cf0fcdd0f", + "Read": false, + "From": { + "Name": "John Doe", + "Address": "john@example.com" + }, + "To": [ + { + "Name": "Jane Smith", + "Address": "jane@example.com" + } + ], + "Cc": [ + { + "Name": "Accounts", + "Address": "accounts@example.com" + } + ], + "Bcc": null, + "Subject": "Test email", + "Created": "2022-10-03T21:35:32.228605299+13:00", + "Size": 6144, + "Attachments": 0 + }, + ... + ] +} +``` + +### Notes + +- `total` - Total messages in mailbox (all messages, not search) +- `unread` - Total unread messages in mailbox (all messages, not search) +- `count` - Number of messages returned in request (up to 200 for search) +- `start` - Always 0 (offset in search is unsupported) +- `From` - Singular Name & Address, or null if none +- `To`, `CC`, `BCC` - Array of Name & Address, or null if none +- `Size` - Total size of raw email in bytes diff --git a/server/api.go b/server/api.go deleted file mode 100644 index 09717a4..0000000 --- a/server/api.go +++ /dev/null @@ -1,279 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" - "strings" - - "github.com/axllent/mailpit/data" - "github.com/axllent/mailpit/server/websockets" - "github.com/axllent/mailpit/storage" - "github.com/gorilla/mux" -) - -type messagesResult struct { - Total int `json:"total"` - Unread int `json:"unread"` - Count int `json:"count"` - Start int `json:"start"` - Items []data.Summary `json:"items"` -} - -// Return a list of available mailboxes -func apiMailboxStats(w http.ResponseWriter, _ *http.Request) { - res := storage.StatsGet() - - bytes, _ := json.Marshal(res) - w.Header().Add("Content-Type", "application/json") - _, _ = w.Write(bytes) -} - -// List messages -func apiListMessages(w http.ResponseWriter, r *http.Request) { - start, limit := getStartLimit(r) - - messages, err := storage.List(start, limit) - if err != nil { - httpError(w, err.Error()) - return - } - - stats := storage.StatsGet() - - var res messagesResult - - res.Start = start - res.Items = messages - res.Count = len(res.Items) - res.Total = stats.Total - res.Unread = stats.Unread - - bytes, _ := json.Marshal(res) - w.Header().Add("Content-Type", "application/json") - _, _ = w.Write(bytes) -} - -// Search all messages -func apiSearchMessages(w http.ResponseWriter, r *http.Request) { - search := strings.TrimSpace(r.URL.Query().Get("query")) - if search == "" { - fourOFour(w) - return - } - - messages, err := storage.Search(search) - if err != nil { - httpError(w, err.Error()) - return - } - - stats := storage.StatsGet() - - var res messagesResult - - res.Start = 0 - res.Items = messages - res.Count = len(messages) - res.Total = stats.Total - res.Unread = stats.Unread - - bytes, _ := json.Marshal(res) - w.Header().Add("Content-Type", "application/json") - _, _ = w.Write(bytes) -} - -// Open a message -func apiOpenMessage(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - id := vars["id"] - - msg, err := storage.GetMessage(id) - if err != nil { - httpError(w, "Message not found") - return - } - - bytes, _ := json.Marshal(msg) - w.Header().Add("Content-Type", "application/json") - _, _ = w.Write(bytes) -} - -// Download/view an attachment -func apiDownloadAttachment(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - id := vars["id"] - partID := vars["partID"] - - a, err := storage.GetAttachmentPart(id, partID) - if err != nil { - httpError(w, err.Error()) - return - } - fileName := a.FileName - if fileName == "" { - fileName = a.ContentID - } - - w.Header().Add("Content-Type", a.ContentType) - w.Header().Set("Content-Disposition", "filename=\""+fileName+"\"") - _, _ = w.Write(a.Content) -} - -// Download the full email source as plain text -func apiDownloadRaw(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - id := vars["id"] - - dl := r.FormValue("dl") - - data, err := storage.GetMessageRaw(id) - if err != nil { - httpError(w, err.Error()) - return - } - - w.Header().Set("Content-Type", "text/plain") - if dl == "1" { - w.Header().Set("Content-Disposition", "attachment; filename=\""+id+".eml\"") - } - _, _ = w.Write(data) -} - -// Delete all messages -func apiDeleteAll(w http.ResponseWriter, r *http.Request) { - err := storage.DeleteAllMessages() - if err != nil { - httpError(w, err.Error()) - return - } - - w.Header().Add("Content-Type", "text/plain") - _, _ = w.Write([]byte("ok")) -} - -// Delete all selected messages -func apiDeleteSelected(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) - - var data struct { - IDs []string - } - err := decoder.Decode(&data) - if err != nil { - panic(err) - } - - ids := data.IDs - - for _, id := range ids { - if err := storage.DeleteOneMessage(id); err != nil { - httpError(w, err.Error()) - return - } - } - - w.Header().Add("Content-Type", "text/plain") - _, _ = w.Write([]byte("ok")) -} - -// Delete a single message -func apiDeleteOne(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - id := vars["id"] - - err := storage.DeleteOneMessage(id) - if err != nil { - httpError(w, err.Error()) - return - } - - w.Header().Add("Content-Type", "text/plain") - _, _ = w.Write([]byte("ok")) -} - -// Mark single message as unread -func apiUnreadOne(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - id := vars["id"] - - err := storage.MarkUnread(id) - if err != nil { - httpError(w, err.Error()) - return - } - - w.Header().Add("Content-Type", "text/plain") - _, _ = w.Write([]byte("ok")) -} - -// Mark all messages as read -func apiMarkAllRead(w http.ResponseWriter, r *http.Request) { - err := storage.MarkAllRead() - if err != nil { - httpError(w, err.Error()) - return - } - - w.Header().Add("Content-Type", "text/plain") - _, _ = w.Write([]byte("ok")) -} - -// Mark selected message as read -func apiMarkSelectedRead(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) - - var data struct { - IDs []string - } - err := decoder.Decode(&data) - if err != nil { - panic(err) - } - - ids := data.IDs - - for _, id := range ids { - if err := storage.MarkRead(id); err != nil { - httpError(w, err.Error()) - return - } - } - - w.Header().Add("Content-Type", "text/plain") - _, _ = w.Write([]byte("ok")) -} - -// Mark selected message as unread -func apiMarkSelectedUnread(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) - - var data struct { - IDs []string - } - err := decoder.Decode(&data) - if err != nil { - panic(err) - } - - ids := data.IDs - - for _, id := range ids { - if err := storage.MarkUnread(id); err != nil { - httpError(w, err.Error()) - return - } - } - - w.Header().Add("Content-Type", "text/plain") - _, _ = w.Write([]byte("ok")) -} - -// Websocket to broadcast changes -func apiWebsocket(w http.ResponseWriter, r *http.Request) { - websockets.ServeWs(websockets.MessageHub, w, r) -} diff --git a/server/apiv1/api.go b/server/apiv1/api.go new file mode 100644 index 0000000..044b7b1 --- /dev/null +++ b/server/apiv1/api.go @@ -0,0 +1,289 @@ +package apiv1 + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/axllent/mailpit/config" + "github.com/axllent/mailpit/data" + "github.com/axllent/mailpit/storage" + "github.com/gorilla/mux" +) + +// MessagesResult struct +type MessagesResult struct { + Total int `json:"total"` + Unread int `json:"unread"` + Count int `json:"count"` + Start int `json:"start"` + Messages []data.Summary `json:"messages"` +} + +// // Mailbox returns an message overview (stats) +// func Mailbox(w http.ResponseWriter, _ *http.Request) { +// res := storage.StatsGet() + +// bytes, _ := json.Marshal(res) +// w.Header().Add("Content-Type", "application/json") +// _, _ = w.Write(bytes) +// } + +// Messages returns a paginated list of messages +func Messages(w http.ResponseWriter, r *http.Request) { + start, limit := getStartLimit(r) + + messages, err := storage.List(start, limit) + if err != nil { + httpError(w, err.Error()) + return + } + + stats := storage.StatsGet() + + var res MessagesResult + + res.Start = start + res.Messages = messages + res.Count = len(messages) + res.Total = stats.Total + res.Unread = stats.Unread + + bytes, _ := json.Marshal(res) + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(bytes) +} + +// Search returns a max of 200 of the latest messages +func Search(w http.ResponseWriter, r *http.Request) { + search := strings.TrimSpace(r.URL.Query().Get("query")) + if search == "" { + fourOFour(w) + return + } + + messages, err := storage.Search(search) + if err != nil { + httpError(w, err.Error()) + return + } + + stats := storage.StatsGet() + + var res MessagesResult + + res.Start = 0 + res.Messages = messages + res.Count = len(messages) + res.Total = stats.Total + res.Unread = stats.Unread + + bytes, _ := json.Marshal(res) + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(bytes) +} + +// Message (method: GET) returns a *data.Message +func Message(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + id := vars["id"] + + msg, err := storage.GetMessage(id) + if err != nil { + httpError(w, "Message not found") + return + } + + bytes, _ := json.Marshal(msg) + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(bytes) +} + +// DownloadAttachment (method: GET) returns the attachment data +func DownloadAttachment(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + id := vars["id"] + partID := vars["partID"] + + a, err := storage.GetAttachmentPart(id, partID) + if err != nil { + httpError(w, err.Error()) + return + } + fileName := a.FileName + if fileName == "" { + fileName = a.ContentID + } + + w.Header().Add("Content-Type", a.ContentType) + w.Header().Set("Content-Disposition", "filename=\""+fileName+"\"") + _, _ = w.Write(a.Content) +} + +// DownloadRaw (method: GET) returns the full email source as plain text +func DownloadRaw(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + id := vars["id"] + + dl := r.FormValue("dl") + + data, err := storage.GetMessageRaw(id) + if err != nil { + httpError(w, err.Error()) + return + } + + w.Header().Set("Content-Type", "text/plain") + if dl == "1" { + w.Header().Set("Content-Disposition", "attachment; filename=\""+id+".eml\"") + } + _, _ = w.Write(data) +} + +// DeleteMessages (method: DELETE) deletes all messages matching IDS. +// If no IDs are provided then all messages are deleted. +func DeleteMessages(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) + var data struct { + IDs []string + } + err := decoder.Decode(&data) + if err != nil || len(data.IDs) == 0 { + if err := storage.DeleteAllMessages(); err != nil { + httpError(w, err.Error()) + return + } + } else { + for _, id := range data.IDs { + if err := storage.DeleteOneMessage(id); err != nil { + httpError(w, err.Error()) + return + } + } + } + + w.Header().Add("Content-Type", "text/plain") + _, _ = w.Write([]byte("ok")) +} + +// // DeleteMessage (method: DELETE) deletes a single message +// func DeleteMessage(w http.ResponseWriter, r *http.Request) { +// vars := mux.Vars(r) + +// id := vars["id"] + +// err := storage.DeleteOneMessage(id) +// if err != nil { +// httpError(w, err.Error()) +// return +// } + +// w.Header().Add("Content-Type", "text/plain") +// _, _ = w.Write([]byte("ok")) +// } + +// SetAllRead (GET) will update all messages as read +// func SetAllRead(w http.ResponseWriter, r *http.Request) { +// err := storage.MarkAllRead() +// if err != nil { +// httpError(w, err.Error()) +// return +// } + +// w.Header().Add("Content-Type", "text/plain") +// _, _ = w.Write([]byte("ok")) +// } + +// SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs +func SetReadStatus(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) + + var data struct { + Read bool + IDs []string + } + + err := decoder.Decode(&data) + if err != nil { + httpError(w, err.Error()) + return + } + + ids := data.IDs + + if len(ids) == 0 { + if data.Read { + err := storage.MarkAllRead() + if err != nil { + httpError(w, err.Error()) + return + } + } else { + err := storage.MarkAllUnread() + if err != nil { + httpError(w, err.Error()) + return + } + } + } else { + if data.Read { + for _, id := range ids { + if err := storage.MarkRead(id); err != nil { + httpError(w, err.Error()) + return + } + } + } else { + for _, id := range ids { + if err := storage.MarkUnread(id); err != nil { + httpError(w, err.Error()) + return + } + } + } + } + + w.Header().Add("Content-Type", "text/plain") + _, _ = w.Write([]byte("ok")) +} + +// FourOFour returns a basic 404 message +func fourOFour(w http.ResponseWriter) { + w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) + w.WriteHeader(http.StatusNotFound) + w.Header().Set("Content-Type", "text/plain") + fmt.Fprint(w, "404 page not found") +} + +// HTTPError returns a basic error message (400 response) +func httpError(w http.ResponseWriter, msg string) { + w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "text/plain") + fmt.Fprint(w, msg) +} + +// Get the start and limit based on query params. Defaults to 0, 50 +func getStartLimit(req *http.Request) (start int, limit int) { + start = 0 + limit = 50 + + s := req.URL.Query().Get("start") + if n, err := strconv.Atoi(s); err == nil && n > 0 { + start = n + } + + l := req.URL.Query().Get("limit") + if n, err := strconv.Atoi(l); err == nil && n > 0 { + limit = n + } + + return start, limit +} diff --git a/server/thumbnails.go b/server/apiv1/thumbnails.go similarity index 94% rename from server/thumbnails.go rename to server/apiv1/thumbnails.go index 741f366..d77dec7 100644 --- a/server/thumbnails.go +++ b/server/apiv1/thumbnails.go @@ -1,4 +1,4 @@ -package server +package apiv1 import ( "bufio" @@ -22,8 +22,8 @@ var ( thumbHeight = 120 ) -// Attachment thumbnail (images only) -func apiAttachmentThumbnail(w http.ResponseWriter, r *http.Request) { +// Thumbnail returns a thumbnail image for an attachment (images only) +func Thumbnail(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] diff --git a/server/server.go b/server/server.go index 93be26f..192160d 100644 --- a/server/server.go +++ b/server/server.go @@ -3,17 +3,16 @@ package server import ( "compress/gzip" "embed" - "fmt" "io" "io/fs" "log" "net/http" "os" - "strconv" "strings" "github.com/axllent/mailpit/config" "github.com/axllent/mailpit/logger" + "github.com/axllent/mailpit/server/apiv1" "github.com/axllent/mailpit/server/websockets" "github.com/gorilla/mux" ) @@ -21,8 +20,6 @@ import ( //go:embed ui var embeddedFS embed.FS -var contentSecurityPolicy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'self'; img-src * data: blob:; font-src 'self' data:; media-src 'self'; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'self';" - // Listen will start the httpd func Listen() { serverRoot, err := fs.Sub(embeddedFS, "ui") @@ -35,22 +32,12 @@ func Listen() { go websockets.MessageHub.Run() - r := mux.NewRouter() - r.HandleFunc("/api/stats", middleWareFunc(apiMailboxStats)).Methods("GET") - r.HandleFunc("/api/messages", middleWareFunc(apiListMessages)).Methods("GET") - r.HandleFunc("/api/search", middleWareFunc(apiSearchMessages)).Methods("GET") - r.HandleFunc("/api/delete", middleWareFunc(apiDeleteAll)).Methods("GET") - r.HandleFunc("/api/delete", middleWareFunc(apiDeleteSelected)).Methods("POST") + r := defaultRoutes() + + // web UI websocket r.HandleFunc("/api/events", apiWebsocket).Methods("GET") - r.HandleFunc("/api/read", apiMarkAllRead).Methods("GET") - r.HandleFunc("/api/read", apiMarkSelectedRead).Methods("POST") - r.HandleFunc("/api/unread", apiMarkSelectedUnread).Methods("POST") - r.HandleFunc("/api/{id}/raw", middleWareFunc(apiDownloadRaw)).Methods("GET") - r.HandleFunc("/api/{id}/part/{partID}", middleWareFunc(apiDownloadAttachment)).Methods("GET") - r.HandleFunc("/api/{id}/part/{partID}/thumb", middleWareFunc(apiAttachmentThumbnail)).Methods("GET") - r.HandleFunc("/api/{id}/delete", middleWareFunc(apiDeleteOne)).Methods("GET") - r.HandleFunc("/api/{id}/unread", middleWareFunc(apiUnreadOne)).Methods("GET") - r.HandleFunc("/api/{id}", middleWareFunc(apiOpenMessage)).Methods("GET") + + // virtual filesystem for others r.PathPrefix("/").Handler(middlewareHandler(http.FileServer(http.FS(serverRoot)))) http.Handle("/", r) @@ -67,6 +54,22 @@ func Listen() { } } +func defaultRoutes() *mux.Router { + r := mux.NewRouter() + + // API V1 + r.HandleFunc("/api/v1/messages", middleWareFunc(apiv1.Messages)).Methods("GET") + r.HandleFunc("/api/v1/messages", middleWareFunc(apiv1.SetReadStatus)).Methods("PUT") + r.HandleFunc("/api/v1/messages", middleWareFunc(apiv1.DeleteMessages)).Methods("DELETE") + r.HandleFunc("/api/v1/search", middleWareFunc(apiv1.Search)).Methods("GET") + r.HandleFunc("/api/v1/message/{id}/raw", middleWareFunc(apiv1.DownloadRaw)).Methods("GET") + r.HandleFunc("/api/v1/message/{id}/part/{partID}", middleWareFunc(apiv1.DownloadAttachment)).Methods("GET") + r.HandleFunc("/api/v1/message/{id}/part/{partID}/thumb", middleWareFunc(apiv1.Thumbnail)).Methods("GET") + r.HandleFunc("/api/v1/message/{id}", middleWareFunc(apiv1.Message)).Methods("GET") + + return r +} + // BasicAuthResponse returns an basic auth response to the browser func basicAuthResponse(w http.ResponseWriter) { w.Header().Set("WWW-Authenticate", `Basic realm="Login"`) @@ -88,7 +91,7 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) { func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Referrer-Policy", "no-referrer") - w.Header().Set("Content-Security-Policy", contentSecurityPolicy) + w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) if config.UIAuthFile != "" { user, pass, ok := r.BasicAuth() @@ -121,7 +124,7 @@ func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc { func middlewareHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Referrer-Policy", "no-referrer") - w.Header().Set("Content-Security-Policy", contentSecurityPolicy) + w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) if config.UIAuthFile != "" { user, pass, ok := r.BasicAuth() @@ -148,38 +151,7 @@ func middlewareHandler(h http.Handler) http.Handler { }) } -// FourOFour returns a basic 404 message -func fourOFour(w http.ResponseWriter) { - w.Header().Set("Referrer-Policy", "no-referrer") - w.Header().Set("Content-Security-Policy", contentSecurityPolicy) - w.WriteHeader(http.StatusNotFound) - w.Header().Set("Content-Type", "text/plain") - fmt.Fprint(w, "404 page not found") -} - -// HTTPError returns a basic error message (400 response) -func httpError(w http.ResponseWriter, msg string) { - w.Header().Set("Referrer-Policy", "no-referrer") - w.Header().Set("Content-Security-Policy", contentSecurityPolicy) - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "text/plain") - fmt.Fprint(w, msg) -} - -// Get the start and limit based on query params. Defaults to 0, 50 -func getStartLimit(req *http.Request) (start int, limit int) { - start = 0 - limit = 50 - - s := req.URL.Query().Get("start") - if n, err := strconv.Atoi(s); err == nil && n > 0 { - start = n - } - - l := req.URL.Query().Get("limit") - if n, err := strconv.Atoi(l); err == nil && n > 0 { - limit = n - } - - return start, limit +// Websocket to broadcast changes +func apiWebsocket(w http.ResponseWriter, r *http.Request) { + websockets.ServeWs(websockets.MessageHub, w, r) } diff --git a/storage/database.go b/storage/database.go index 0cff82a..3e795f3 100644 --- a/storage/database.go +++ b/storage/database.go @@ -370,17 +370,16 @@ func GetMessage(id string) (*data.Message, error) { date, _ := env.Date() obj := data.Message{ - ID: id, - Read: true, - From: from, - Date: date, - To: addressToSlice(env, "To"), - Cc: addressToSlice(env, "Cc"), - Bcc: addressToSlice(env, "Bcc"), - Subject: env.GetHeader("Subject"), - Size: len(raw), - Text: env.Text, - HTMLSource: env.HTML, + ID: id, + Read: true, + From: from, + Date: date, + To: addressToSlice(env, "To"), + Cc: addressToSlice(env, "Cc"), + Bcc: addressToSlice(env, "Bcc"), + Subject: env.GetHeader("Subject"), + Size: len(raw), + Text: env.Text, } html := env.HTML @@ -388,6 +387,7 @@ func GetMessage(id string) (*data.Message, error) { // strip base tags var re = regexp.MustCompile(`(?U)`) html = re.ReplaceAllString(html, "") + obj.HTML = html for _, i := range env.Inlines { if i.FileName != "" || i.ContentID != "" { @@ -407,8 +407,6 @@ func GetMessage(id string) (*data.Message, error) { } } - obj.HTML = html - // mark message as read if err := MarkRead(id); err != nil { return &obj, err @@ -511,6 +509,7 @@ func MarkAllRead() error { _, err := sqlf.Update("mailbox"). Set("Read", 1). + Where("Read = ?", 0). ExecAndClose(context.Background(), db) if err != nil { return err @@ -524,6 +523,29 @@ func MarkAllRead() error { return nil } +// MarkAllUnread will mark all messages as unread +func MarkAllUnread() error { + var ( + start = time.Now() + total = CountRead() + ) + + _, err := sqlf.Update("mailbox"). + Set("Read", 0). + Where("Read = ?", 1). + ExecAndClose(context.Background(), db) + if err != nil { + return err + } + + elapsed := time.Since(start) + logger.Log().Debugf("[db] marked %d messages as unread in %s", total, elapsed) + + dbLastAction = time.Now() + + return nil +} + // MarkUnread will mark a message as unread func MarkUnread(id string) error { if IsUnread(id) { @@ -655,7 +677,6 @@ func CountTotal() int { } // CountUnread returns the number of emails in the database that are unread. -// If an ID is supplied, then it is just limited to that message. func CountUnread() int { var total int @@ -668,6 +689,19 @@ func CountUnread() int { return total } +// CountRead returns the number of emails in the database that are read. +func CountRead() int { + var total int + + q := sqlf.From("mailbox"). + Select("COUNT(*)").To(&total). + Where("Read = ?", 1) + + _ = q.QueryRowAndClose(nil, db) + + return total +} + // IsUnread returns the number of emails in the database that are unread. // If an ID is supplied, then it is just limited to that message. func IsUnread(id string) bool { From df758d063a5d5e5f3606f93f83323e45f71724d5 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Fri, 7 Oct 2022 19:47:41 +1300 Subject: [PATCH 5/8] UI: Changes to use new data API --- server/ui-src/App.vue | 98 ++++++++++++------------- server/ui-src/mixins.js | 35 +++++++-- server/ui-src/templates/Attachments.vue | 4 +- server/ui-src/templates/Message.vue | 10 +-- 4 files changed, 84 insertions(+), 63 deletions(-) diff --git a/server/ui-src/App.vue b/server/ui-src/App.vue index 1bb0ba1..1a92d2a 100644 --- a/server/ui-src/App.vue +++ b/server/ui-src/App.vue @@ -84,11 +84,11 @@ export default { let params = {}; this.selected = []; - let uri = 'api/messages'; + let uri = 'api/v1/messages'; if (self.search) { self.searching = true; self.items = []; - uri = 'api/search' + uri = 'api/v1/search' self.start = 0; // search is displayed on one page params['query'] = self.search; } else { @@ -104,7 +104,12 @@ export default { self.unread = response.data.unread; self.count = response.data.count; self.start = response.data.start; - self.items = response.data.items; + self.items = response.data.messages; + + if (self.items == 0 && self.start > 0) { + self.start = 0; + return self.loadMessages(); + } if (!self.scrollInPlace) { let mp = document.getElementById('message-page'); @@ -153,7 +158,7 @@ export default { let self = this; self.selected = []; - let uri = 'api/' + self.currentPath + let uri = 'api/v1/message/' + self.currentPath self.get(uri, false, function(response) { for (let i in self.items) { if (self.items[i].ID == self.currentPath) { @@ -171,14 +176,14 @@ export default { if (a.ContentID != '') { d.HTML = d.HTML.replace( new RegExp('cid:'+a.ContentID, 'g'), - window.location.origin+'/api/'+d.ID+'/part/'+a.PartID + window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID ); } if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) { // some old email clients use the filename d.HTML = d.HTML.replace( new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'), - 'src="'+window.location.origin+'/api/'+d.ID+'/part/'+a.PartID+'"' + 'src="'+window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID+'"' ); } } @@ -190,14 +195,14 @@ export default { if (a.ContentID != '') { d.HTML = d.HTML.replace( new RegExp('cid:'+a.ContentID, 'g'), - window.location.origin+'/api/'+d.ID+'/part/'+a.PartID + window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID ); } if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) { // some old email clients use the filename d.HTML = d.HTML.replace( new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'), - 'src="'+window.location.origin+'/api/'+d.ID+'/part/'+a.PartID+'"' + 'src="'+window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID+'"' ); } } @@ -221,48 +226,42 @@ export default { }); }, + // universal handler to delete current or selected messages + deleteMessages: function() { + let ids = []; + let self = this; + if (self.message) { + ids.push(self.message.ID); + } else { + ids = JSON.parse(JSON.stringify(self.selected)); + } + if (!ids.length) { + return false; + } + let uri = 'api/v1/messages'; + self.delete(uri, {'ids': ids}, function(response) { + window.location.hash = ""; + self.scrollInPlace = true; + self.loadMessages(); + }); + }, + deleteAll: function() { let self = this; - let uri = 'api/delete' - self.get(uri, false, function(response) { + let uri = 'api/v1/messages'; + self.delete(uri, false, function(response) { window.location.hash = ""; self.reloadMessages(); }); }, - deleteOne: function() { - let self = this; - if (!self.message) { - return false; - } - let uri = 'api/' + self.message.ID + '/delete' - self.get(uri, false, function(response) { - window.location.hash = ""; - self.scrollInPlace = true; - self.loadMessages(); - }); - }, - - deleteSelected: function() { - let self = this; - if (!self.selected.length) { - return false; - } - let uri = 'api/delete' - self.post(uri, {'ids': self.selected}, function(response) { - window.location.hash = ""; - self.scrollInPlace = true; - self.loadMessages(); - }); - }, - markUnread: function() { let self = this; if (!self.message) { return false; } - let uri = 'api/' + self.message.ID + '/unread' - self.get(uri, false, function(response) { + let uri = 'api/v1/messages'; + self.put(uri, {'read': false, 'ids': [self.message.ID]}, function(response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); @@ -271,8 +270,8 @@ export default { markAllRead: function() { let self = this; - let uri = 'api/read' - self.get(uri, false, function(response) { + let uri = 'api/v1/messages' + self.put(uri, {'read': true}, function(response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); @@ -284,8 +283,8 @@ export default { if (!self.selected.length) { return false; } - let uri = 'api/read' - self.post(uri, {'ids': self.selected}, function(response) { + let uri = 'api/v1/messages'; + self.put(uri, {'read': true, 'ids': self.selected}, function(response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); @@ -297,8 +296,8 @@ export default { if (!self.selected.length) { return false; } - let uri = 'api/unread' - self.post(uri, {'ids': self.selected}, function(response) { + let uri = 'api/v1/messages'; + self.put(uri, {'read': false, 'ids': self.selected}, function(response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); @@ -357,7 +356,8 @@ export default { } self.total++; self.unread++; - self.browserNotify("New mail from: " + response.Data.From.Address, response.Data.Subject); + let from = response.Data.From != null ? response.Data.From.Address : '[unknown]'; + self.browserNotify("New mail from: " + from, response.Data.Subject); } else if (response.Type == "prune") { // messages have been deleted, reload messages to adjust self.scrollInPlace = true; @@ -497,19 +497,19 @@ export default { - + - + Download diff --git a/server/ui-src/mixins.js b/server/ui-src/mixins.js index b69bd15..7e7976b 100644 --- a/server/ui-src/mixins.js +++ b/server/ui-src/mixins.js @@ -88,16 +88,16 @@ const commonMixins = { }, /** - * Axios Post request + * Axios POST request * * @params string url - * @params array array parameters Object/array + * @params array object/array values * @params function callback function */ - post: function (url, values, callback) { + post: function (url, data, callback) { let self = this; self.loading++; - axios.post(url, values) + axios.post(url, data) .then(callback) .catch(self.handleError) .then(function () { @@ -112,13 +112,34 @@ const commonMixins = { * Axios DELETE request (REST only) * * @params string url - * @params array array parameters Object/array + * @params array object/array values * @params function callback function */ - delete: function (url, values, callback) { + delete: function (url, data, callback) { let self = this; self.loading++; - axios.delete(url, { data: values }) + axios.delete(url, { data: data }) + .then(callback) + .catch(self.handleError) + .then(function () { + // always executed + if (self.loading > 0) { + self.loading--; + } + }); + }, + + /** + * Axios PUT request (REST only) + * + * @params string url + * @params array object/array values + * @params function callback function + */ + put: function (url, data, callback) { + let self = this; + self.loading++; + axios.put(url, data) .then(callback) .catch(self.handleError) .then(function () { diff --git a/server/ui-src/templates/Attachments.vue b/server/ui-src/templates/Attachments.vue index 07df5d8..a13cf68 100644 --- a/server/ui-src/templates/Attachments.vue +++ b/server/ui-src/templates/Attachments.vue @@ -14,8 +14,8 @@ export default {