mirror of
https://github.com/axllent/mailpit.git
synced 2026-03-04 23:37:02 +00:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fde942e0d | ||
|
|
b09d7ac75d | ||
|
|
fc2fdd20f6 | ||
|
|
cbbac40c0d | ||
|
|
6bc02fd4d4 | ||
|
|
57cfb2611c | ||
|
|
ba24d145ff | ||
|
|
376e799eb0 | ||
|
|
1dfadda07e | ||
|
|
fc0a7358ab | ||
|
|
d229b34d98 | ||
|
|
cbc3fe59a8 | ||
|
|
ab771cf76c | ||
|
|
7a27e09d23 | ||
|
|
cdce989a9c | ||
|
|
61dd3eddc5 | ||
|
|
290e48d875 | ||
|
|
e7ea94a5d2 | ||
|
|
43bd2a18ea | ||
|
|
ec95e58e13 | ||
|
|
70ac9c73ea | ||
|
|
0fcdcdd5f6 | ||
|
|
ea12a1ee56 | ||
|
|
9345ed60c6 | ||
|
|
0a13cf8304 | ||
|
|
4ebbdab7c0 | ||
|
|
cea9518b4b | ||
|
|
a9220277d6 | ||
|
|
bd45d9dffe | ||
|
|
baaf3a3a23 | ||
|
|
2e95a75d32 | ||
|
|
53d2296ff5 | ||
|
|
e8bf803ca0 | ||
|
|
d9dc000e89 | ||
|
|
205611856b | ||
|
|
5d396b9f25 | ||
|
|
4b95c6bda0 | ||
|
|
9982948c81 | ||
|
|
614b63cf28 | ||
|
|
b1027ca844 | ||
|
|
2176ad6ca2 | ||
|
|
971753e576 | ||
|
|
9053651cc1 | ||
|
|
a9593030ab | ||
|
|
75a7c1cfd4 | ||
|
|
699a534632 | ||
|
|
53f8d34961 | ||
|
|
81d09aabd1 | ||
|
|
11eec7db30 | ||
|
|
6e6482f6ad | ||
|
|
1efbbb353b | ||
|
|
b61fbe371a | ||
|
|
a2b6107dd6 | ||
|
|
f457412f98 | ||
|
|
14f1d75dba | ||
|
|
ce838dc054 | ||
|
|
0d29f3db1a | ||
|
|
cbc77530e9 | ||
|
|
70e8edf648 | ||
|
|
4368541a96 | ||
|
|
4d511bd29d | ||
|
|
b0894a8064 | ||
|
|
5d32d5190d | ||
|
|
b7154963c5 | ||
|
|
001e9de123 | ||
|
|
b64a5b7991 | ||
|
|
906a697542 | ||
|
|
46dbde04ae | ||
|
|
a31a7c3d2c | ||
|
|
675704ca91 | ||
|
|
d253d3164e | ||
|
|
ef3da383da | ||
|
|
db6c2596a0 | ||
|
|
7349d838bb | ||
|
|
d8c6364622 | ||
|
|
df758d063a | ||
|
|
34da0e5042 | ||
|
|
4a92b99a53 | ||
|
|
b1dc121cdd | ||
|
|
e5c8ef9e8d | ||
|
|
c6695c2418 |
@@ -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
|
||||
@@ -1,6 +0,0 @@
|
||||
tree 1f6b4426212043bf6b473fcbac898e9b871c84f6
|
||||
parent 802f6f5672200fff036a72abd84f39e78483d61f
|
||||
author Ralph Slooten <axllent@gmail.com> 1659602885 +1200
|
||||
committer Ralph Slooten <axllent@gmail.com> 1659602885 +1200
|
||||
|
||||
Release 0.0.6
|
||||
@@ -1 +0,0 @@
|
||||
refs/heads/develop
|
||||
@@ -1 +0,0 @@
|
||||
00d254d7c41be358cc0a99d388e796703c3ee5b9
|
||||
@@ -1 +0,0 @@
|
||||
07141d3a213a443b830fb05dae87f181a6b213cc
|
||||
@@ -1 +0,0 @@
|
||||
0799a6d67cfd65d0228014478632f85780ed5517
|
||||
@@ -1 +0,0 @@
|
||||
0fa58dffbd9d30f4c0aa9c93c96292fa89857bc5
|
||||
@@ -1 +0,0 @@
|
||||
154b2342056d3adb1be716e16f8919319d3c6dba
|
||||
@@ -1 +0,0 @@
|
||||
19966fad81e043ef9e2a2d062411e3990dad449d
|
||||
@@ -1 +0,0 @@
|
||||
1df270bab3caf8c2e36c5d6aaf84efdfb5da0db0
|
||||
@@ -1 +0,0 @@
|
||||
2944c2a32f6e1e3cbb7eb9603188b339f6d74700
|
||||
@@ -1 +0,0 @@
|
||||
2e5752f69385f8d3f75ab1446e46d3f2208a543f
|
||||
@@ -1 +0,0 @@
|
||||
3103b50f0857d18c48ec60ee622fabf4938c938f
|
||||
@@ -1 +0,0 @@
|
||||
324de3d99f22bbce918e0246daa6caf90a0fc734
|
||||
@@ -1 +0,0 @@
|
||||
335b0f3876e66da7bb06e6c39afb016068da1ac9
|
||||
@@ -1 +0,0 @@
|
||||
38da162cd90445a64e6586f72fed0a3c5e7dfce2
|
||||
@@ -1 +0,0 @@
|
||||
3bbc12286966b69455025aa4de3e88ecda439811
|
||||
@@ -1 +0,0 @@
|
||||
41c7c2a93af86acb48e5dd8b3d9406820c547b13
|
||||
@@ -1 +0,0 @@
|
||||
48db1437b3a9ea21bc987513c01bc4800b06f0f9
|
||||
@@ -1 +0,0 @@
|
||||
493e8a09aa10d8fe48896c429ea0c1260b2c0709
|
||||
@@ -1 +0,0 @@
|
||||
4b707537b9334641c37fb1d1aab96fe708e507ec
|
||||
@@ -1 +0,0 @@
|
||||
4e4fc22cb53abe2979d751e63671c771718147e5
|
||||
@@ -1 +0,0 @@
|
||||
50c7955e6d3b39b187c7aaa7da1cf901be6ac6b2
|
||||
@@ -1 +0,0 @@
|
||||
55fd56a4a3e40acbfa4720749018d13873d8af62
|
||||
@@ -1 +0,0 @@
|
||||
56449dd30e9f26312ddb2117faf319a514f73f1f
|
||||
@@ -1 +0,0 @@
|
||||
6d426c2a0bcd5c191c0e95163cf15b69a207fdd5
|
||||
@@ -1 +0,0 @@
|
||||
6fe1bdb579f0924b10b28f1fc9587ac49fc44f24
|
||||
@@ -1 +0,0 @@
|
||||
72709acb903556967dd5d1cead1f5b052151bb25
|
||||
@@ -1 +0,0 @@
|
||||
76c6c881d54b82d5ca4bc427c2968fbc15c7a090
|
||||
@@ -1 +0,0 @@
|
||||
7a9b11a9e5e63b987fde0e7d8536f9fa76585097
|
||||
@@ -1 +0,0 @@
|
||||
7cbe3a04ef7b95789287cf8fa7d654e18df11084
|
||||
@@ -1 +0,0 @@
|
||||
7fd73a6fdb49843d29aefa2bb8bb5de900e53811
|
||||
@@ -1 +0,0 @@
|
||||
8019d3e0e2a8a01f729911730128de72a2a1b6ba
|
||||
@@ -1 +0,0 @@
|
||||
802f6f5672200fff036a72abd84f39e78483d61f
|
||||
@@ -1 +0,0 @@
|
||||
8159694b935775e4072e1902124a8cb4bb6efacd
|
||||
@@ -1 +0,0 @@
|
||||
83f289eb40b34c07ad6b5df1099a7b6427e9be40
|
||||
@@ -1 +0,0 @@
|
||||
86d73f9118eee40fd842fc25898490a2923eaecd
|
||||
@@ -1 +0,0 @@
|
||||
8866720631a717b8df2df6fc206eb6db44f8a005
|
||||
@@ -1 +0,0 @@
|
||||
8d308a6776436ce1189e45268e4eceb8b5a1011a
|
||||
@@ -1 +0,0 @@
|
||||
8d6d48c59e9ca78d00a12c715494de99eb40a5ad
|
||||
@@ -1 +0,0 @@
|
||||
8e819aca56db7ab982243eddda0ab89525db1b23
|
||||
@@ -1 +0,0 @@
|
||||
8f474bc31303882270112cdfafeaedc908938707
|
||||
@@ -1 +0,0 @@
|
||||
927638f2d500dbfd29371b2b4a3f01b987749899
|
||||
@@ -1 +0,0 @@
|
||||
9a27f330791c08ee87dbb886c6d511bb6d3bffa0
|
||||
@@ -1 +0,0 @@
|
||||
a3b34ec32effa03acfa4aa99cbe38aee81f01e56
|
||||
@@ -1 +0,0 @@
|
||||
a56c9616adbf6f5c387a2ffc472cd42b6b9ba377
|
||||
@@ -1 +0,0 @@
|
||||
a810bdae244404a91d17c6293f3096b055dd74cf
|
||||
@@ -1 +0,0 @@
|
||||
a85a74bb9a03dafeaaaf3e2211cb796f195773e3
|
||||
@@ -1 +0,0 @@
|
||||
ad1037c02b654b7e582c2c644841ea7477fcf9b4
|
||||
@@ -1 +0,0 @@
|
||||
b7dbda0db65e24c8ad2549639cd22388db837e1f
|
||||
@@ -1 +0,0 @@
|
||||
bca7bec867cd6aff1b1692ed5508c18e62aa430b
|
||||
@@ -1 +0,0 @@
|
||||
bd87dcabf6b370316eac39b863ee5d84ad5851e2
|
||||
@@ -1 +0,0 @@
|
||||
c6f1c8213b0bb6489072bc379ae8b158e6a552d9
|
||||
@@ -1 +0,0 @@
|
||||
ce23e0616e4a29a266bbfc9c398c57c2976dc71a
|
||||
@@ -1 +0,0 @@
|
||||
cf12b3968b2d2a59fb1fa12ef83065b1d7090681
|
||||
@@ -1 +0,0 @@
|
||||
cf5c4297c9b975dff541a7384da6838dd7fab930
|
||||
@@ -1 +0,0 @@
|
||||
d15b3eb05e53326a81eab3f6839186e0840cb686
|
||||
@@ -1 +0,0 @@
|
||||
d4f17fb8ad988be6cbd467ff2d79a5ac894c5e89
|
||||
@@ -1 +0,0 @@
|
||||
e1dc184d7b25a64cc9b599b38cafbc3e7bfccb76
|
||||
@@ -1 +0,0 @@
|
||||
e363ece5a05eeb55264176e689ffdbf4e79b718f
|
||||
@@ -1 +0,0 @@
|
||||
eb7049163bbfa1bc73af5859dff1803901cf43b3
|
||||
@@ -1 +0,0 @@
|
||||
f6da1c6da4beacaa410beea8dc1fe45d21b3b8d2
|
||||
@@ -1 +0,0 @@
|
||||
f966b6a756f736f3f106a186faac183ce0eb0686
|
||||
@@ -1 +0,0 @@
|
||||
Release 0.0.6
|
||||
@@ -1 +0,0 @@
|
||||
refs/heads/develop
|
||||
@@ -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
|
||||
10
.github/workflows/build-docker.yml
vendored
10
.github/workflows/build-docker.yml
vendored
@@ -10,10 +10,6 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
@@ -30,8 +26,8 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm
|
||||
platforms: linux/386,linux/amd64,linux/arm,linux/arm64
|
||||
build-args: |
|
||||
"VERSION=${{ steps.tag.outputs.tag }}"
|
||||
"VERSION=${{ github.ref_name }}"
|
||||
push: true
|
||||
tags: axllent/mailpit:latest,axllent/mailpit:${{ steps.tag.outputs.tag }}
|
||||
tags: axllent/mailpit:latest,axllent/mailpit:${{ github.ref_name }}
|
||||
|
||||
18
.github/workflows/release-build.yml
vendored
18
.github/workflows/release-build.yml
vendored
@@ -10,24 +10,25 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, arm64]
|
||||
goarch: ["386", amd64, arm, arm64]
|
||||
exclude:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
- goarch: arm64
|
||||
- goarch: "386"
|
||||
goos: windows
|
||||
- goarch: arm
|
||||
goos: darwin
|
||||
- goarch: arm
|
||||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
|
||||
# build the assets
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
- run: echo "Building assets for ${{ github.ref_name }}"
|
||||
- run: npm install
|
||||
- run: npm run package
|
||||
|
||||
@@ -42,4 +43,5 @@ jobs:
|
||||
asset_name: mailpit-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
extra_files: LICENSE README.md
|
||||
md5sum: false
|
||||
ldflags: -w -X "github.com/axllent/mailpit/cmd.Version=${{ steps.tag.outputs.tag }}"
|
||||
overwrite: true
|
||||
ldflags: -w -X "github.com/axllent/mailpit/config.Version=${{ github.ref_name }}"
|
||||
|
||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -24,13 +24,13 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- run: go test ./storage -v
|
||||
- run: go test ./storage ./server -v
|
||||
- run: go test ./storage -bench=.
|
||||
|
||||
# build the assets
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm run package
|
||||
|
||||
81
CHANGELOG.md
81
CHANGELOG.md
@@ -2,6 +2,87 @@
|
||||
|
||||
Notable changes to Mailpit will be documented in this file.
|
||||
|
||||
## v1.2.8
|
||||
|
||||
### Bugfix
|
||||
- Return empty arrays rather than null for message To, CC, BCC, Inlines & Attachments
|
||||
|
||||
### Feature
|
||||
- Message tags and auto-tagging
|
||||
|
||||
|
||||
## v1.2.7
|
||||
|
||||
### Feature
|
||||
- Allow custom webroot
|
||||
|
||||
|
||||
## v1.2.6
|
||||
|
||||
### API
|
||||
- Provide structs of API v1 responses for use in client code
|
||||
|
||||
### Libs
|
||||
- Update go modules
|
||||
- Update node modules
|
||||
|
||||
|
||||
## 1.2.5
|
||||
|
||||
### UI
|
||||
- Broadcast "delete all" action to reload all connected clients
|
||||
- Load first page if paginated list returns 0 results
|
||||
- Theme changes
|
||||
- Bump build action to use node 18
|
||||
|
||||
|
||||
## 1.2.4
|
||||
|
||||
### Bugfix
|
||||
- Fix mail download link
|
||||
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### API
|
||||
- Add limit and start parameters to search
|
||||
|
||||
### UI
|
||||
- Prevent double message index request on websocket connect
|
||||
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### API
|
||||
- Add API endpoint to return message headers
|
||||
|
||||
### Libs
|
||||
- Update go modules
|
||||
|
||||
### Testing
|
||||
- Add API test for raw & message headers
|
||||
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### UI
|
||||
- Update frontend modules
|
||||
- Add about app modal with version update notification
|
||||
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Feature
|
||||
- Add REST API
|
||||
|
||||
### Testing
|
||||
- Add API tests
|
||||
|
||||
### UI
|
||||
- Changes to use new data API
|
||||
- Hide delete all / mark all read in message view
|
||||
|
||||
|
||||
## 1.1.7
|
||||
|
||||
### Fix
|
||||
|
||||
@@ -8,7 +8,7 @@ WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git npm && \
|
||||
npm install && npm run package && \
|
||||
CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/axllent/mailpit/cmd.Version=${VERSION}" -o /mailpit
|
||||
CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/axllent/mailpit/config.Version=${VERSION}" -o /mailpit
|
||||
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
@@ -12,7 +12,7 @@ It acts as both an SMTP server, and provides a web interface to view all capture
|
||||
|
||||
Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Features
|
||||
@@ -21,6 +21,7 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster.
|
||||
- SMTP server (default `0.0.0.0:1025`)
|
||||
- Web UI to view emails (formatted HTML, highlighted HTML source, text, raw source and MIME attachments including image thumbnails)
|
||||
- Advanced mail search ([see wiki](https://github.com/axllent/mailpit/wiki/Mail-search))
|
||||
- Message tagging ([see wiki](https://github.com/axllent/mailpit/wiki/Tagging))
|
||||
- Real-time web UI updates using web sockets for new mail
|
||||
- Optional browser notifications for new mail (HTTPS only)
|
||||
- Configurable automatic email pruning (default keeps the most recent 500 emails)
|
||||
@@ -30,6 +31,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 ([see docs](docs/apiv1/README.md))
|
||||
- Multi-architecture [Docker images](https://github.com/axllent/mailpit/wiki/Docker-images)
|
||||
|
||||
|
||||
@@ -41,7 +43,7 @@ Linux & Mac users can install it directly to `/usr/local/bin/mailpit` with:
|
||||
sudo bash < <(curl -sL https://raw.githubusercontent.com/axllent/mailpit/develop/install.sh)
|
||||
```
|
||||
|
||||
Or download a pre-built binary in the [releases](https://github.com/axllent/mailpit/releases/latest). The `mailpit` can be placed in your `$PATH`, or simply run as `./mailpit`. See `mailpit -h` for options, or see [the wiki](https://github.com/axllent/mailpit/wiki/Runtime-options) for additional information.
|
||||
Or download a static binary from the [releases](https://github.com/axllent/mailpit/releases/latest). The `mailpit` binary can be placed in your `$PATH`, or simply run as `./mailpit`. See `mailpit -h` for options, or see [the wiki](https://github.com/axllent/mailpit/wiki/Runtime-options) for additional information.
|
||||
|
||||
To build Mailpit from source see [building from source](https://github.com/axllent/mailpit/wiki/Building-from-source).
|
||||
|
||||
|
||||
10
cmd/root.go
10
cmd/root.go
@@ -6,10 +6,10 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/logger"
|
||||
"github.com/axllent/mailpit/server"
|
||||
"github.com/axllent/mailpit/smtpd"
|
||||
"github.com/axllent/mailpit/storage"
|
||||
"github.com/axllent/mailpit/utils/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -85,6 +85,9 @@ func init() {
|
||||
if len(os.Getenv("MP_MAX_MESSAGES")) > 0 {
|
||||
config.MaxMessages, _ = strconv.Atoi(os.Getenv("MP_MAX_MESSAGES"))
|
||||
}
|
||||
if len(os.Getenv("MP_TAG")) > 0 {
|
||||
config.SMTPCLITags = os.Getenv("MP_TAG")
|
||||
}
|
||||
if len(os.Getenv("MP_UI_AUTH_FILE")) > 0 {
|
||||
config.UIAuthFile = os.Getenv("MP_UI_AUTH_FILE")
|
||||
}
|
||||
@@ -103,6 +106,9 @@ func init() {
|
||||
if len(os.Getenv("MP_SMTP_SSL_KEY")) > 0 {
|
||||
config.SMTPSSLKey = os.Getenv("MP_SMTP_SSL_KEY")
|
||||
}
|
||||
if len(os.Getenv("MP_WEBROOT")) > 0 {
|
||||
config.Webroot = os.Getenv("MP_WEBROOT")
|
||||
}
|
||||
|
||||
// deprecated 2022/08/06
|
||||
if len(os.Getenv("MP_AUTH_FILE")) > 0 {
|
||||
@@ -127,6 +133,7 @@ func init() {
|
||||
rootCmd.Flags().StringVarP(&config.SMTPListen, "smtp", "s", config.SMTPListen, "SMTP bind interface and port")
|
||||
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI")
|
||||
rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages to store")
|
||||
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
|
||||
|
||||
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI authentication")
|
||||
rootCmd.Flags().StringVar(&config.UISSLCert, "ui-ssl-cert", config.UISSLCert, "SSL certificate for web UI - requires ui-ssl-key")
|
||||
@@ -135,6 +142,7 @@ func init() {
|
||||
rootCmd.Flags().StringVar(&config.SMTPAuthFile, "smtp-auth-file", config.SMTPAuthFile, "A password file for SMTP authentication")
|
||||
rootCmd.Flags().StringVar(&config.SMTPSSLCert, "smtp-ssl-cert", config.SMTPSSLCert, "SSL certificate for SMTP - requires smtp-ssl-key")
|
||||
rootCmd.Flags().StringVar(&config.SMTPSSLKey, "smtp-ssl-key", config.SMTPSSLKey, "SSL key for SMTP - requires smtp-ssl-cert")
|
||||
rootCmd.Flags().StringVarP(&config.SMTPCLITags, "tag", "t", "", "Tag new messages matching filters")
|
||||
|
||||
rootCmd.Flags().BoolVarP(&config.QuietLogging, "quiet", "q", false, "Quiet logging (errors only)")
|
||||
rootCmd.Flags().BoolVarP(&config.VerboseLogging, "verbose", "v", false, "Verbose logging")
|
||||
|
||||
@@ -5,21 +5,11 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/axllent/mailpit/updater"
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/utils/updater"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// Version is the default application version, updated on release
|
||||
Version = "dev"
|
||||
|
||||
// Repo on Github for updater
|
||||
Repo = "axllent/mailpit"
|
||||
|
||||
// RepoBinaryName on Github for updater
|
||||
RepoBinaryName = "mailpit"
|
||||
)
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
@@ -36,10 +26,10 @@ var versionCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s compiled with %s on %s/%s\n",
|
||||
os.Args[0], Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
os.Args[0], config.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
latest, _, _, err := updater.GithubLatest(Repo, RepoBinaryName)
|
||||
if err == nil && updater.GreaterThan(latest, Version) {
|
||||
latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName)
|
||||
if err == nil && updater.GreaterThan(latest, config.Version) {
|
||||
fmt.Printf(
|
||||
"\nUpdate available: %s\nRun `%s version -u` to update (requires read/write access to install directory).\n",
|
||||
latest,
|
||||
@@ -59,7 +49,7 @@ func init() {
|
||||
}
|
||||
|
||||
func updateApp() error {
|
||||
rel, err := updater.GithubUpdate(Repo, RepoBinaryName, Version)
|
||||
rel, err := updater.GithubUpdate(config.Repo, config.RepoBinaryName, config.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-shellwords"
|
||||
"github.com/tg123/go-htpasswd"
|
||||
)
|
||||
|
||||
@@ -44,6 +47,9 @@ var (
|
||||
// UIAuth used for euthentication
|
||||
UIAuth *htpasswd.File
|
||||
|
||||
// Webroot to define the base path for the UI and API
|
||||
Webroot = "/"
|
||||
|
||||
// SMTPSSLCert file
|
||||
SMTPSSLCert string
|
||||
|
||||
@@ -55,8 +61,35 @@ var (
|
||||
|
||||
// SMTPAuth used for euthentication
|
||||
SMTPAuth *htpasswd.File
|
||||
|
||||
// SMTPCLITags is used to map the CLI args
|
||||
SMTPCLITags string
|
||||
|
||||
// TagRegexp is the allowed tag characters
|
||||
TagRegexp = regexp.MustCompile(`^([a-zA-Z0-9\-\ \_]){3,}$`)
|
||||
|
||||
// SMTPTags are expressions to apply tags to new mail
|
||||
SMTPTags []Tag
|
||||
|
||||
// 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';"
|
||||
|
||||
// Version is the default application version, updated on release
|
||||
Version = "dev"
|
||||
|
||||
// Repo on Github for updater
|
||||
Repo = "axllent/mailpit"
|
||||
|
||||
// RepoBinaryName on Github for updater
|
||||
RepoBinaryName = "mailpit"
|
||||
)
|
||||
|
||||
// Tag struct
|
||||
type Tag struct {
|
||||
Tag string
|
||||
Match string
|
||||
}
|
||||
|
||||
// VerifyConfig wil do some basic checking
|
||||
func VerifyConfig() error {
|
||||
if DataFile != "" && isDir(DataFile) {
|
||||
@@ -127,6 +160,42 @@ func VerifyConfig() error {
|
||||
SMTPAuth = a
|
||||
}
|
||||
|
||||
if strings.Contains(Webroot, " ") {
|
||||
return fmt.Errorf("Webroot cannot contain spaces (%s)", Webroot)
|
||||
}
|
||||
|
||||
s := path.Join("/", Webroot, "/")
|
||||
Webroot = s
|
||||
|
||||
SMTPTags = []Tag{}
|
||||
|
||||
p := shellwords.NewParser()
|
||||
|
||||
if SMTPCLITags != "" {
|
||||
args, err := p.Parse(SMTPCLITags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing tags (%s)", err)
|
||||
}
|
||||
|
||||
for _, a := range args {
|
||||
t := strings.Split(a, "=")
|
||||
if len(t) > 1 {
|
||||
tag := strings.TrimSpace(t[0])
|
||||
if !TagRegexp.MatchString(tag) || len(tag) == 0 {
|
||||
return fmt.Errorf("Invalid tag (%s) - can only contain spaces, letters, numbers, - & _", tag)
|
||||
}
|
||||
match := strings.TrimSpace(strings.ToLower(strings.Join(t[1:], "=")))
|
||||
if len(match) == 0 {
|
||||
return fmt.Errorf("Invalid tag match (%s) - no search detected", tag)
|
||||
}
|
||||
SMTPTags = append(SMTPTags, Tag{Tag: tag, Match: match})
|
||||
} else {
|
||||
return fmt.Errorf("Error parsing tags (%s)", a)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package data
|
||||
|
||||
import "time"
|
||||
|
||||
// MailboxSummary struct
|
||||
type MailboxSummary struct {
|
||||
Name string
|
||||
Slug string
|
||||
Total int
|
||||
Unread int
|
||||
LastMessage time.Time
|
||||
}
|
||||
|
||||
// WebsocketNotification struct for responses
|
||||
type WebsocketNotification struct {
|
||||
Type string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// MailboxStats struct for quick mailbox total/read lookups
|
||||
type MailboxStats struct {
|
||||
Total int
|
||||
Unread int
|
||||
}
|
||||
115
docs/apiv1/Message.md
Normal file
115
docs/apiv1/Message.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Message
|
||||
|
||||
## Message summary
|
||||
|
||||
Returns a JSON summary of the message and attachments.
|
||||
|
||||
**URL** : `api/v1/message/<ID>`
|
||||
|
||||
**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": [],
|
||||
"Bcc": [],
|
||||
"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
|
||||
- `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/<ID>/part/<PartID>`
|
||||
|
||||
**Method** : `GET`
|
||||
|
||||
Returns the attachment using the MIME type provided by the attachment `ContentType`.
|
||||
|
||||
---
|
||||
## Headers
|
||||
|
||||
**URL** : `api/v1/message/<ID>/headers`
|
||||
|
||||
**Method** : `GET`
|
||||
|
||||
Returns all message headers as a JSON array.
|
||||
Each unique header key contains an array of one or more values (email headers can be listed multiple times.)
|
||||
|
||||
```json
|
||||
{
|
||||
"Content-Type": [
|
||||
"multipart/related; type=\"multipart/alternative\"; boundary=\"----=_NextPart_000_0013_01C6A60C.47EEAB80\""
|
||||
],
|
||||
"Date": [
|
||||
"Wed, 12 Jul 2006 23:38:30 +1200"
|
||||
],
|
||||
"Delivered-To": [
|
||||
"user@example.com",
|
||||
"user-alias@example.com"
|
||||
],
|
||||
"From": [
|
||||
"\"User Name\" \\u003remote@example.com\\u003e"
|
||||
],
|
||||
"Message-Id": [
|
||||
"\\u003c001701c6a5a7$b3205580$0201010a@HomeOfficeSM\\u003e"
|
||||
],
|
||||
....
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
## Raw (source) email
|
||||
|
||||
**URL** : `api/v1/message/<ID>/raw`
|
||||
|
||||
**Method** : `GET`
|
||||
|
||||
Returns the original email source including headers and attachments.
|
||||
166
docs/apiv1/Messages.md
Normal file
166
docs/apiv1/Messages.md
Normal file
@@ -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": [],
|
||||
"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
|
||||
- `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": ["<ID>","<ID>"...]
|
||||
}
|
||||
```
|
||||
|
||||
### 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": ["<ID>","<ID>"...],
|
||||
"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`
|
||||
12
docs/apiv1/README.md
Normal file
12
docs/apiv1/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 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
|
||||
- [Tags](Tags.md) - Set message tags
|
||||
- [Search](Search.md) - Searching messages
|
||||
69
docs/apiv1/Search.md
Normal file
69
docs/apiv1/Search.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Search
|
||||
|
||||
**URL** : `api/v1/search?query=<string>`
|
||||
|
||||
**Method** : `GET`
|
||||
|
||||
The search returns the most recent matches (default 50).
|
||||
Matching messages are returned in the order of latest received to oldest.
|
||||
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|---------|----------|----------------------------|
|
||||
| query | string | true | Search query |
|
||||
| limit | integer | false | Limit results (default 50) |
|
||||
| start | integer | false | Pagination offset |
|
||||
|
||||
|
||||
## 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": [],
|
||||
"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
|
||||
- `start` - The offset (default `0`) for pagination
|
||||
- `From` - Singular Name & Address, or null if none
|
||||
- `To`, `CC`, `BCC` - Array of Name & Address
|
||||
- `Size` - Total size of raw email in bytes
|
||||
27
docs/apiv1/Tags.md
Normal file
27
docs/apiv1/Tags.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Tags
|
||||
|
||||
Set message tags.
|
||||
|
||||
|
||||
---
|
||||
## Update message tags
|
||||
|
||||
Set the tags for one or more messages.
|
||||
If the tags array is empty then all tags are removed from the messages.
|
||||
|
||||
**URL** : `api/v1/tags`
|
||||
|
||||
**Method** : `PUT`
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"ids": ["<ID>","<ID>"...],
|
||||
"tags": ["<tag>","<tag>"]
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
**Status** : `200`
|
||||
BIN
docs/screenshot.png
Normal file
BIN
docs/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
37
go.mod
37
go.mod
@@ -8,19 +8,19 @@ require (
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jhillyerd/enmime v0.10.0
|
||||
github.com/jhillyerd/enmime v0.10.1
|
||||
github.com/k3a/html2text v1.0.8
|
||||
github.com/klauspost/compress v1.15.9
|
||||
github.com/klauspost/compress v1.15.12
|
||||
github.com/leporo/sqlf v1.3.0
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/mhale/smtpd v0.8.0
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/tg123/go-htpasswd v1.2.0
|
||||
golang.org/x/text v0.3.7
|
||||
modernc.org/sqlite v1.18.1
|
||||
golang.org/x/text v0.4.0
|
||||
modernc.org/sqlite v1.19.3
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -29,34 +29,33 @@ require (
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cznic/ql v1.2.0 // indirect
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.3.4 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/testify v1.7.2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/image v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.36.3 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.9 // indirect
|
||||
modernc.org/libc v1.17.1 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8 // indirect
|
||||
modernc.org/libc v1.21.4 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.2.1 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
|
||||
116
go.sum
116
go.sum
@@ -36,7 +36,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||
@@ -45,9 +44,7 @@ github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28/go.mod h1:Pcatq5tYkCW
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
@@ -56,22 +53,21 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jhillyerd/enmime v0.10.0 h1:DZEzhptPRBesvN3gf7K1BOh4rfpqdsdrEoxW1Edr/3s=
|
||||
github.com/jhillyerd/enmime v0.10.0/go.mod h1:Qpe8EEemJMFAF8+NZoWdpXvK2Yb9dRF0k/z6mkcDHsA=
|
||||
github.com/jhillyerd/enmime v0.10.1 h1:3VP8gFhK7R948YJBrna5bOgnTXEuPAoICo79kKkBKfA=
|
||||
github.com/jhillyerd/enmime v0.10.1/go.mod h1:Qpe8EEemJMFAF8+NZoWdpXvK2Yb9dRF0k/z6mkcDHsA=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/k3a/html2text v1.0.8 h1:rVanLhKilpnJUJs/CNKWzMC4YaQINGxK0rSG8ssmnV0=
|
||||
github.com/k3a/html2text v1.0.8/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
|
||||
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
@@ -81,17 +77,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leporo/sqlf v1.3.0 h1:nAkuPYxMIJg/sUmcd1h4avV5iYo8tBTGEGOIR4BIZO8=
|
||||
github.com/leporo/sqlf v1.3.0/go.mod h1:f4dHqIi1+nLl6k1IsNQ8QIEbGWK49th2ei1IxTXk+2E=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mhale/smtpd v0.8.0 h1:5JvdsehCg33PQrZBvFyDMMUDQmvbzVpZgKob7eYBJc0=
|
||||
github.com/mhale/smtpd v0.8.0/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
@@ -100,12 +95,13 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa h1:tEkEyxYeZ43TR55QU/hsIt9aRGBxbgGuz9CGykjvogY=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
|
||||
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -117,8 +113,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
@@ -131,95 +127,81 @@ github.com/tg123/go-htpasswd v1.2.0 h1:UKp34m9H467/xklxUxU15wKRru7fwXoTojtxg25IT
|
||||
github.com/tg123/go-htpasswd v1.2.0/go.mod h1:h7IzlfpvIWnVJhNZ0nQ9HaFxHb7pn5uFJYLlEUJa2sM=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
|
||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg=
|
||||
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM=
|
||||
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8 h1:0+dsXf0zeLx9ixj4nilg6jKe5Bg1ilzBwSFq4kJmIUc=
|
||||
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
|
||||
modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI=
|
||||
modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/libc v1.21.4 h1:CzTlumWeIbPV5/HVIMzYHNPCRP8uiU/CWiN2gtd/Qu8=
|
||||
modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs=
|
||||
modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=
|
||||
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/sqlite v1.19.3 h1:dIoagx6yIQT3V/zOSeAyZ8OqQyEr17YTgETOXTZNJMA=
|
||||
modernc.org/sqlite v1.19.3/go.mod h1:xiyJD7FY8mTZXnQwE/gEL1STtFrrnDx03V8KhVQmcr8=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
|
||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||
|
||||
974
package-lock.json
generated
974
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@
|
||||
"axios": "^0.27.2",
|
||||
"bootstrap": "^5.2.0",
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"bootstrap5-tags": "^1.4.41",
|
||||
"moment": "^2.29.4",
|
||||
"prismjs": "^1.29.0",
|
||||
"tinycon": "^0.6.8",
|
||||
|
||||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB |
@@ -8,12 +8,12 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
"github.com/axllent/mailpit/utils/logger"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@@ -80,6 +80,6 @@ func Run() {
|
||||
err = smtp.SendMail(smtpAddr, nil, fromAddr, recip, body)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error sending mail")
|
||||
log.Fatal(err)
|
||||
logger.Log().Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
279
server/api.go
279
server/api.go
@@ -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)
|
||||
}
|
||||
304
server/apiv1/api.go
Normal file
304
server/apiv1/api.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/storage"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// GetMessages returns a paginated list of messages as JSON
|
||||
func GetMessages(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 MessagesSummary
|
||||
|
||||
res.Start = start
|
||||
res.Messages = messages
|
||||
res.Count = len(messages)
|
||||
res.Total = stats.Total
|
||||
res.Unread = stats.Unread
|
||||
res.Tags = stats.Tags
|
||||
|
||||
bytes, _ := json.Marshal(res)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// Search returns up to 200 of the latest messages as JSON
|
||||
func Search(w http.ResponseWriter, r *http.Request) {
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search == "" {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
start, limit := getStartLimit(r)
|
||||
|
||||
messages, err := storage.Search(search, start, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
stats := storage.StatsGet()
|
||||
|
||||
var res MessagesSummary
|
||||
|
||||
res.Start = 0
|
||||
res.Messages = messages
|
||||
res.Count = len(messages)
|
||||
res.Total = stats.Total
|
||||
res.Unread = stats.Unread
|
||||
res.Tags = stats.Tags
|
||||
|
||||
bytes, _ := json.Marshal(res)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// GetMessage (method: GET) returns the *data.Message as JSON
|
||||
func GetMessage(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)
|
||||
}
|
||||
|
||||
// Headers (method: GET) returns the message headers as JSON
|
||||
func Headers(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
data, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(data)
|
||||
m, err := mail.ReadMessage(reader)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
headers := m.Header
|
||||
bytes, _ := json.Marshal(headers)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// 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"))
|
||||
}
|
||||
|
||||
// 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"))
|
||||
}
|
||||
|
||||
// SetTags (method: PUT) will set the tags for all provided IDs
|
||||
func SetTags(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var data struct {
|
||||
Tags []string
|
||||
IDs []string
|
||||
}
|
||||
|
||||
err := decoder.Decode(&data)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ids := data.IDs
|
||||
|
||||
if len(ids) > 0 {
|
||||
for _, id := range ids {
|
||||
if err := storage.SetTags(id, data.Tags); 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
|
||||
}
|
||||
52
server/apiv1/info.go
Normal file
52
server/apiv1/info.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/storage"
|
||||
"github.com/axllent/mailpit/utils/updater"
|
||||
)
|
||||
|
||||
type appVersion struct {
|
||||
Version string
|
||||
LatestVersion string
|
||||
Database string
|
||||
DatabaseSize int64
|
||||
Messages int
|
||||
Memory uint64
|
||||
}
|
||||
|
||||
// AppInfo returns some basic details about the running app, and latest release.
|
||||
func AppInfo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
info := appVersion{}
|
||||
info.Version = config.Version
|
||||
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
info.Memory = m.Sys - m.HeapReleased
|
||||
|
||||
latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName)
|
||||
if err == nil {
|
||||
info.LatestVersion = latest
|
||||
}
|
||||
|
||||
info.Database = config.DataFile
|
||||
|
||||
db, err := os.Stat(info.Database)
|
||||
if err == nil {
|
||||
info.DatabaseSize = db.Size()
|
||||
}
|
||||
|
||||
info.Messages = storage.CountTotal()
|
||||
|
||||
bytes, _ := json.Marshal(info)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
25
server/apiv1/structs.go
Normal file
25
server/apiv1/structs.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package apiv1
|
||||
|
||||
import "github.com/axllent/mailpit/storage"
|
||||
|
||||
// The following structs & aliases are provided for easy import
|
||||
// and understanding of the JSON structure.
|
||||
|
||||
// MessageSummary - summary of a single message
|
||||
type MessageSummary = storage.MessageSummary
|
||||
|
||||
// MessagesSummary - summary of a list of messages
|
||||
type MessagesSummary struct {
|
||||
Total int `json:"total"`
|
||||
Unread int `json:"unread"`
|
||||
Count int `json:"count"`
|
||||
Start int `json:"start"`
|
||||
Tags []string `json:"tags"`
|
||||
Messages []MessageSummary `json:"messages"`
|
||||
}
|
||||
|
||||
// Message data
|
||||
type Message = storage.Message
|
||||
|
||||
// Attachment summary
|
||||
type Attachment = storage.Attachment
|
||||
@@ -1,4 +1,4 @@
|
||||
package server
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/logger"
|
||||
"github.com/axllent/mailpit/storage"
|
||||
"github.com/axllent/mailpit/utils/logger"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jhillyerd/enmime"
|
||||
@@ -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"]
|
||||
108
server/server.go
108
server/server.go
@@ -3,26 +3,22 @@ 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/axllent/mailpit/utils/logger"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
//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,23 +31,20 @@ 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.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")
|
||||
r.PathPrefix("/").Handler(middlewareHandler(http.FileServer(http.FS(serverRoot))))
|
||||
r := defaultRoutes()
|
||||
|
||||
// web UI websocket
|
||||
r.HandleFunc(config.Webroot+"api/events", apiWebsocket).Methods("GET")
|
||||
|
||||
// virtual filesystem for others
|
||||
r.PathPrefix(config.Webroot).Handler(middlewareHandler(http.StripPrefix(config.Webroot, http.FileServer(http.FS(serverRoot)))))
|
||||
|
||||
// redirect to webroot if no trailing slash
|
||||
if config.Webroot != "/" {
|
||||
redir := strings.TrimRight(config.Webroot, "/")
|
||||
r.HandleFunc(redir, middleWareFunc(addSlashToWebroot)).Methods("GET")
|
||||
}
|
||||
|
||||
http.Handle("/", r)
|
||||
|
||||
if config.UIAuthFile != "" {
|
||||
@@ -59,14 +52,33 @@ func Listen() {
|
||||
}
|
||||
|
||||
if config.UISSLCert != "" && config.UISSLKey != "" {
|
||||
logger.Log().Infof("[http] starting secure server on https://%s", config.HTTPListen)
|
||||
log.Fatal(http.ListenAndServeTLS(config.HTTPListen, config.UISSLCert, config.UISSLKey, nil))
|
||||
logger.Log().Infof("[http] starting secure server on https://%s%s", config.HTTPListen, config.Webroot)
|
||||
logger.Log().Fatal(http.ListenAndServeTLS(config.HTTPListen, config.UISSLCert, config.UISSLKey, nil))
|
||||
} else {
|
||||
logger.Log().Infof("[http] starting server on http://%s", config.HTTPListen)
|
||||
log.Fatal(http.ListenAndServe(config.HTTPListen, nil))
|
||||
logger.Log().Infof("[http] starting server on http://%s%s", config.HTTPListen, config.Webroot)
|
||||
logger.Log().Fatal(http.ListenAndServe(config.HTTPListen, nil))
|
||||
}
|
||||
}
|
||||
|
||||
func defaultRoutes() *mux.Router {
|
||||
r := mux.NewRouter()
|
||||
|
||||
// API V1
|
||||
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.GetMessages)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.SetReadStatus)).Methods("PUT")
|
||||
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.DeleteMessages)).Methods("DELETE")
|
||||
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.SetTags)).Methods("PUT")
|
||||
r.HandleFunc(config.Webroot+"api/v1/search", middleWareFunc(apiv1.Search)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}", middleWareFunc(apiv1.DownloadAttachment)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}/thumb", middleWareFunc(apiv1.Thumbnail)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/raw", middleWareFunc(apiv1.DownloadRaw)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/headers", middleWareFunc(apiv1.Headers)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}", middleWareFunc(apiv1.GetMessage)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/info", middleWareFunc(apiv1.AppInfo)).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 +100,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 +133,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 +160,12 @@ 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")
|
||||
// Redirect to webroot
|
||||
func addSlashToWebroot(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, config.Webroot, http.StatusFound)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
316
server/server_test.go
Normal file
316
server/server_test.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/server/apiv1"
|
||||
"github.com/axllent/mailpit/storage"
|
||||
"github.com/jhillyerd/enmime"
|
||||
)
|
||||
|
||||
var (
|
||||
putDataStruct struct {
|
||||
Read bool `json:"read"`
|
||||
IDs []string `json:"ids"`
|
||||
}
|
||||
)
|
||||
|
||||
func Test_APIv1(t *testing.T) {
|
||||
setup()
|
||||
defer storage.Close()
|
||||
|
||||
r := defaultRoutes()
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
m, err := fetchMessages(ts.URL + "/api/v1/messages")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// check count of empty database
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 0, 0)
|
||||
|
||||
// insert 100
|
||||
t.Log("Insert 100 messages")
|
||||
insertEmailData(t)
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 100, 100)
|
||||
|
||||
// store this for later tests
|
||||
|
||||
m, err = fetchMessages(ts.URL + "/api/v1/messages")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// read first 10
|
||||
t.Log("Read first 10 messages including raw & headers")
|
||||
putIDS := []string{}
|
||||
for indx, msg := range m.Messages {
|
||||
if indx == 10 {
|
||||
break
|
||||
}
|
||||
|
||||
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// test RAW
|
||||
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/raw"); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// test headers
|
||||
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/headers"); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// store for later
|
||||
putIDS = append(putIDS, msg.ID)
|
||||
}
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 90, 100)
|
||||
|
||||
// mark first 10 as unread
|
||||
t.Log("Mark first 10 as unread")
|
||||
putData := putDataStruct
|
||||
putData.IDs = putIDS
|
||||
j, err := json.Marshal(putData)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
_, err = clientPut(ts.URL+"/api/v1/messages", string(j))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 100, 100)
|
||||
|
||||
// mark first 10 as read
|
||||
t.Log("Mark first 10 as read")
|
||||
putData.Read = true
|
||||
j, err = json.Marshal(putData)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
_, err = clientPut(ts.URL+"/api/v1/messages", string(j))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 90, 100)
|
||||
|
||||
// search
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "from-1@example.com", 1)
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "to:from-1@example.com", 0)
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "from:@example.com", 100)
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "subject:\"Subject line\"", 100)
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "subject:\"Subject line 17 end\"", 1)
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "!thisdoesnotexist", 100)
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "-thisdoesnotexist", 100)
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "thisdoesnotexist", 0)
|
||||
|
||||
// delete first 10
|
||||
t.Log("Delete first 10")
|
||||
_, err = clientDelete(ts.URL+"/api/v1/messages", string(j))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 90, 90)
|
||||
|
||||
// mark all as read
|
||||
putData.Read = true
|
||||
putData.IDs = []string{}
|
||||
j, err = json.Marshal(putData)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
t.Log("Mark all read")
|
||||
_, err = clientPut(ts.URL+"/api/v1/messages", string(j))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 0, 90)
|
||||
|
||||
// delete all
|
||||
t.Log("Delete all messages")
|
||||
_, err = clientDelete(ts.URL+"/api/v1/messages", "{}")
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil, received %s", err.Error())
|
||||
}
|
||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 0, 0)
|
||||
}
|
||||
|
||||
func setup() {
|
||||
config.NoLogging = true
|
||||
config.MaxMessages = 0
|
||||
config.DataFile = ""
|
||||
|
||||
if err := storage.InitDB(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertStatsEqual(t *testing.T, uri string, unread, total int) {
|
||||
m := apiv1.MessagesSummary{}
|
||||
|
||||
data, err := clientGet(uri)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assertEqual(t, unread, m.Unread, "wrong unread count")
|
||||
assertEqual(t, total, m.Total, "wrong total count")
|
||||
}
|
||||
|
||||
func assertSearchEqual(t *testing.T, uri, query string, count int) {
|
||||
t.Logf("Test search: %s", query)
|
||||
m := apiv1.MessagesSummary{}
|
||||
|
||||
limit := fmt.Sprintf("%d", count)
|
||||
|
||||
data, err := clientGet(uri + "?query=" + url.QueryEscape(query) + "&limit=" + limit)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assertEqual(t, count, m.Count, "wrong search results count")
|
||||
}
|
||||
|
||||
func insertEmailData(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
msg := enmime.Builder().
|
||||
From(fmt.Sprintf("From %d", i), fmt.Sprintf("from-%d@example.com", i)).
|
||||
Subject(fmt.Sprintf("Subject line %d end", i)).
|
||||
Text([]byte(fmt.Sprintf("This is the email body %d <jdsauk;dwqmdqw;>.", i))).
|
||||
To(fmt.Sprintf("To %d", i), fmt.Sprintf("to-%d@example.com", i))
|
||||
|
||||
env, err := msg.Build()
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if err := env.Encode(buf); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if _, err := storage.Store(buf.Bytes()); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func fetchMessages(url string) (apiv1.MessagesSummary, error) {
|
||||
m := apiv1.MessagesSummary{}
|
||||
|
||||
data, err := clientGet(url)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func clientGet(url string) ([]byte, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func clientDelete(url, body string) ([]byte, error) {
|
||||
client := new(http.Client)
|
||||
|
||||
b := strings.NewReader(body)
|
||||
req, err := http.NewRequest("DELETE", url, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func clientPut(url, body string) ([]byte, error) {
|
||||
client := new(http.Client)
|
||||
|
||||
b := strings.NewReader(body)
|
||||
req, err := http.NewRequest("PUT", url, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
||||
if a == b {
|
||||
return
|
||||
}
|
||||
message = fmt.Sprintf("%s: \"%v\" != \"%v\"", message, a, b)
|
||||
t.Fatal(message)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,4 +5,4 @@ import "./assets/styles.scss";
|
||||
import "../../node_modules/bootstrap-icons/font/bootstrap-icons.scss";
|
||||
import "bootstrap";
|
||||
|
||||
createApp(App).mount('#app')
|
||||
createApp(App).mount('#app');
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
$link-decoration: none;
|
||||
$primary: #3465b5;
|
||||
$primary: #2c3e50;
|
||||
$list-group-disabled-color: #adb5bd;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
.navbar-brand {
|
||||
color: #2d4a5d;
|
||||
transition: all 0.2s;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
@@ -24,6 +25,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
span {
|
||||
opacity: 0.8;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -71,16 +85,15 @@
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.list-group-item:first-child {
|
||||
.list-group-item.message:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.message.selected {
|
||||
background: $primary;
|
||||
color: #fff;
|
||||
background: $gray-300;
|
||||
|
||||
.text-muted {
|
||||
color: #fff !important;
|
||||
color: $body-color !important;
|
||||
}
|
||||
|
||||
&.read {
|
||||
@@ -136,6 +149,23 @@ body.blur {
|
||||
}
|
||||
}
|
||||
|
||||
// .tag.active {
|
||||
// font-weight: bold;
|
||||
// }
|
||||
|
||||
.form-select.tag-selector {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-control.dropdown {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
input {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
}
|
||||
|
||||
/* PrismJS 1.29.0 - modified!
|
||||
https://prismjs.com/download.html#themes=prism-coy&languages=markup+css */
|
||||
code[class*="language-"],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user