Compare commits
675
Commits
dev
...
fix/sync-update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f52641b739 | ||
|
|
955dcfb0ad | ||
|
|
112496fd1e | ||
|
|
6a5dffe567 | ||
|
|
7bf49680cf | ||
|
|
96040267bb | ||
|
|
ad3eccfd7c | ||
|
|
58739a6b32 | ||
|
|
1e3ceb5017 | ||
|
|
f72e45bc46 | ||
|
|
b22d3fd19f | ||
|
|
04d76d5961 | ||
|
|
7c939b5334 | ||
|
|
ba1700d8ab | ||
|
|
bc1a38043a | ||
|
|
a4999dad9e | ||
|
|
19f2535a24 | ||
|
|
830a193333 | ||
|
|
bf007b583c | ||
|
|
fa8b77252d | ||
|
|
21798f6821 | ||
|
|
0d76cc156e | ||
|
|
8741986bb2 | ||
|
|
1767b65856 | ||
|
|
faf9ee0f7c | ||
|
|
b0b3f98233 | ||
|
|
23b4b35395 | ||
|
|
729e09d061 | ||
|
|
6e110e3dd7 | ||
|
|
e800de1f92 | ||
|
|
1318328fe7 | ||
|
|
11ff066e6c | ||
|
|
8f54454d32 | ||
|
|
a14db0e8da | ||
|
|
087a4b6172 | ||
|
|
5e8ee6eec9 | ||
|
|
a15f030956 | ||
|
|
09b41182ce | ||
|
|
9fb6eaab66 | ||
|
|
0b230a26b4 | ||
|
|
79a127636c | ||
|
|
44973faea3 | ||
|
|
23835c27c9 | ||
|
|
6760f36177 | ||
|
|
970bdce53d | ||
|
|
85d2f7cc45 | ||
|
|
9a105ba45e | ||
|
|
ee5cba3634 | ||
|
|
b680cba50c | ||
|
|
5610e50863 | ||
|
|
92b64808ea | ||
|
|
6b6eca5f8d | ||
|
|
9eb087bf6d | ||
|
|
06745b45f4 | ||
|
|
d859fdb302 | ||
|
|
348c3baba8 | ||
|
|
68b7f4225d | ||
|
|
af7f32b1c0 | ||
|
|
286d5c4383 | ||
|
|
3fa1763913 | ||
|
|
46009ef8f4 | ||
|
|
42857974e1 | ||
|
|
de20ed726f | ||
|
|
263f18a1df | ||
|
|
55f44fab8c | ||
|
|
1b5fc82e59 | ||
|
|
ec3e09b509 | ||
|
|
092fa4578b | ||
|
|
a2a73034da | ||
|
|
abbb977b8e | ||
|
|
dcea20b046 | ||
|
|
71f4ca8a2c | ||
|
|
95925cffe5 | ||
|
|
5d56d308b5 | ||
|
|
58c317cc61 | ||
|
|
b9277795cf | ||
|
|
2ae7572e7e | ||
|
|
7b83b03b09 | ||
|
|
2774e05bc7 | ||
|
|
1544fae988 | ||
|
|
d5390fa99a | ||
|
|
0a470e2441 | ||
|
|
33dffa099e | ||
|
|
d1fc26fcf3 | ||
|
|
14a5804991 | ||
|
|
61e3b615a8 | ||
|
|
a16c05968a | ||
|
|
9975754aa7 | ||
|
|
3b532f1f50 | ||
|
|
f08d577e11 | ||
|
|
b96e06068e | ||
|
|
28ca79a19a | ||
|
|
58b5eac61e | ||
|
|
1e44a22c0c | ||
|
|
a01888f155 | ||
|
|
97fec50d48 | ||
|
|
c8fd2907ca | ||
|
|
f540f5be06 | ||
|
|
96328a1236 | ||
|
|
193e8e3ca2 | ||
|
|
38f6d5b62f | ||
|
|
cc2580aedb | ||
|
|
8b810d7817 | ||
|
|
f993b4e5b5 | ||
|
|
a738ef0f6b | ||
|
|
d7d582fe5b | ||
|
|
c943ffc814 | ||
|
|
9bdfe8faec | ||
|
|
3d59757c28 | ||
|
|
b9211e1ea3 | ||
|
|
893b5b9ccd | ||
|
|
fdac9d9ff8 | ||
|
|
ebd6290ccf | ||
|
|
4c0e968e5e | ||
|
|
9cf7de825a | ||
|
|
a3644c721b | ||
|
|
1d9495f5f4 | ||
|
|
6ba74ca911 | ||
|
|
0b3194a281 | ||
|
|
ad2c0367d0 | ||
|
|
b62a0588ae | ||
|
|
488ea6ec06 | ||
|
|
9e5ec860e8 | ||
|
|
21a89c7d66 | ||
|
|
66b26bc778 | ||
|
|
f8e2e9fc32 | ||
|
|
6aaeb7e048 | ||
|
|
e7de465e2c | ||
|
|
76e7a7e9bb | ||
|
|
cdeece2b0b | ||
|
|
b8d0c7aa91 | ||
|
|
dbf09f608a | ||
|
|
3f8941b613 | ||
|
|
88d5a56fec | ||
|
|
0fc762c418 | ||
|
|
8c57c69583 | ||
|
|
c4bf06f10a | ||
|
|
13b761a81e | ||
|
|
1dda6ce8cc | ||
|
|
2eaeb57241 | ||
|
|
6c052d897b | ||
|
|
20c81874fd | ||
|
|
5375165d9b | ||
|
|
123eb9e2d9 | ||
|
|
53a3e04913 | ||
|
|
79658002d5 | ||
|
|
1181bd61fd | ||
|
|
37bb74ac51 | ||
|
|
3bdb69c972 | ||
|
|
6825751c08 | ||
|
|
471d8f4714 | ||
|
|
822f1a0535 | ||
|
|
74f785134f | ||
|
|
aefa0ae2d4 | ||
|
|
cd0ef0d316 | ||
|
|
b2493a6ab2 | ||
|
|
f35f628aed | ||
|
|
95caa3bac5 | ||
|
|
ebaebec835 | ||
|
|
113fb9db77 | ||
|
|
4c6d089b78 | ||
|
|
df7169a6d2 | ||
|
|
ec2609e900 | ||
|
|
26c03dfbbf | ||
|
|
db485056e0 | ||
|
|
1dbbe04d59 | ||
|
|
e8fb2e817b | ||
|
|
57b6e249ea | ||
|
|
b07def3eee | ||
|
|
8a41a895fb | ||
|
|
be9f07bdb7 | ||
|
|
11d628fd50 | ||
|
|
2fa0083fd1 | ||
|
|
e09574586a | ||
|
|
70e8bd6460 | ||
|
|
86c7c63c6c | ||
|
|
692f50ede4 | ||
|
|
4ed68b235a | ||
|
|
5c7ab73e68 | ||
|
|
e66dfddeb9 | ||
|
|
89203b2b12 | ||
|
|
5730505fca | ||
|
|
13c7d473b1 | ||
|
|
0ce8486d99 | ||
|
|
8f01c6e9d2 | ||
|
|
a28f7ccd84 | ||
|
|
114bb8d9d4 | ||
|
|
97b6a4f192 | ||
|
|
eeccb97440 | ||
|
|
85af28ac50 | ||
|
|
400a08d1af | ||
|
|
1bdea85b1b | ||
|
|
f2b548aee8 | ||
|
|
bed0548909 | ||
|
|
7656f44b0b | ||
|
|
28483190e2 | ||
|
|
0e482ad73c | ||
|
|
65507c5d80 | ||
|
|
e7f3b0d7ce | ||
|
|
150404ca0b | ||
|
|
f01ba79977 | ||
|
|
d787789391 | ||
|
|
10cf899fe5 | ||
|
|
cc693228c6 | ||
|
|
a1a1ec20cc | ||
|
|
023ef5a348 | ||
|
|
25d49ed5b7 | ||
|
|
d144eb9cb5 | ||
|
|
3dbc1e6098 | ||
|
|
19aa1f415e | ||
|
|
59bd11acdc | ||
|
|
1ead6c44a0 | ||
|
|
4d0fb7a8f6 | ||
|
|
3a425c25fe | ||
|
|
c317d6631f | ||
|
|
9b8b3d73c5 | ||
|
|
07cb773c6f | ||
|
|
29c2a53c54 | ||
|
|
5cf2dbf8f7 | ||
|
|
168ebb81ce | ||
|
|
93d9021ac0 | ||
|
|
0b47b12d27 | ||
|
|
b46bc1234c | ||
|
|
3631216be4 | ||
|
|
55b4ce14d3 | ||
|
|
41467aa447 | ||
|
|
843ade58ad | ||
|
|
4bf3b55557 | ||
|
|
0ba0d74ea7 | ||
|
|
406c660f41 | ||
|
|
5a3fc93c4f | ||
|
|
88226e0038 | ||
|
|
4881ca1aae | ||
|
|
8ea5e74232 | ||
|
|
a668a8e602 | ||
|
|
8ed1e65055 | ||
|
|
2856afd263 | ||
|
|
48f7e971c9 | ||
|
|
d0a11deae8 | ||
|
|
01eaffaf14 | ||
|
|
19b54c56e4 | ||
|
|
add6b15265 | ||
|
|
5b19a6a359 | ||
|
|
c0dd5cf6ae | ||
|
|
4f0d61735f | ||
|
|
de63b5e778 | ||
|
|
5cc0aa1c0b | ||
|
|
2511f431c4 | ||
|
|
71738eab2a | ||
|
|
2287e59b71 | ||
|
|
e1778ba083 | ||
|
|
9829ccd4be | ||
|
|
7b6fd8e892 | ||
|
|
877e33c004 | ||
|
|
dce6933904 | ||
|
|
13781b3bbb | ||
|
|
e146c5e0a4 | ||
|
|
71585c1d75 | ||
|
|
8f2f30af77 | ||
|
|
e05c06589f | ||
|
|
851d888f89 | ||
|
|
3856c80021 | ||
|
|
678715cabb | ||
|
|
f8373765fd | ||
|
|
6dfacb56c9 | ||
|
|
491ac743b5 | ||
|
|
8d8232a866 | ||
|
|
91e9ff8a8d | ||
|
|
2f23c65b9e | ||
|
|
018b1fa8ba | ||
|
|
be60011567 | ||
|
|
a91daa07d1 | ||
|
|
20436c4aad | ||
|
|
fe02f04217 | ||
|
|
328c36a53a | ||
|
|
10f5e96616 | ||
|
|
a7edd93b00 | ||
|
|
60a68bcb9a | ||
|
|
f7e6b3bc17 | ||
|
|
fe3346e070 | ||
|
|
5b8e23f176 | ||
|
|
deea0cc1d8 | ||
|
|
6a86de6c84 | ||
|
|
f874de73b9 | ||
|
|
b778658479 | ||
|
|
44f51f907e | ||
|
|
0a9c0b632b | ||
|
|
f569152604 | ||
|
|
3d37276583 | ||
|
|
ca58994301 | ||
|
|
b9c2929bc3 | ||
|
|
411f7eee62 | ||
|
|
2ca613e916 | ||
|
|
42788cf8e4 | ||
|
|
3103f3689b | ||
|
|
4de352e2df | ||
|
|
f3febbe3a9 | ||
|
|
927201828c | ||
|
|
c0b43f8afd | ||
|
|
d3b5b2d5c6 | ||
|
|
b4da11e086 | ||
|
|
744f127585 | ||
|
|
8ebac8f4a0 | ||
|
|
64d783dede | ||
|
|
2611a874de | ||
|
|
48dab02c22 | ||
|
|
256dc70b1d | ||
|
|
afaf2d6bf4 | ||
|
|
1b104b7bab | ||
|
|
7f759989cb | ||
|
|
43e8b0a790 | ||
|
|
d66f995683 | ||
|
|
8a7cb50abd | ||
|
|
99b412ea8d | ||
|
|
502a314c68 | ||
|
|
80b5297215 | ||
|
|
df2f979b55 | ||
|
|
fc0724a900 | ||
|
|
8de250ca7e | ||
|
|
e6610fdabb | ||
|
|
c46fd4ddc5 | ||
|
|
a5a1493b3b | ||
|
|
e45c69dee0 | ||
|
|
997e44483e | ||
|
|
92460592b4 | ||
|
|
68b6d36e0e | ||
|
|
e375d305b8 | ||
|
|
657832e0e0 | ||
|
|
5071b0292d | ||
|
|
effaf07ecb | ||
|
|
e6bf189aba | ||
|
|
e024dab95d | ||
|
|
f16989c196 | ||
|
|
7b40c53c3a | ||
|
|
70007d3199 | ||
|
|
b232a02549 | ||
|
|
dd70ad7025 | ||
|
|
e6fbda5ae7 | ||
|
|
48bbce1e91 | ||
|
|
2768a1d109 | ||
|
|
3457c30feb | ||
|
|
180c9f34c5 | ||
|
|
38f58b0b1d | ||
|
|
569e775185 | ||
|
|
a0e92e066f | ||
|
|
308b832155 | ||
|
|
950d0c4ffc | ||
|
|
0505c29eea | ||
|
|
6cfbc18029 | ||
|
|
402d1e5af7 | ||
|
|
053f125ee4 | ||
|
|
225269e3cd | ||
|
|
6d099bda10 | ||
|
|
a4810cc5da | ||
|
|
21f9d1738c | ||
|
|
a52e25ee53 | ||
|
|
7c6b755022 | ||
|
|
5f7f90c879 | ||
|
|
8c2b17150b | ||
|
|
5cbce648c4 | ||
|
|
ef321453ef | ||
|
|
dd0ec5f203 | ||
|
|
e9fa1376e3 | ||
|
|
2601b8a54f | ||
|
|
0f85b77d14 | ||
|
|
05e55d2668 | ||
|
|
0fab398ca6 | ||
|
|
2ca22419ec | ||
|
|
17ca9123f5 | ||
|
|
473c4047be | ||
|
|
42322feced | ||
|
|
9a5056a362 | ||
|
|
22689425e3 | ||
|
|
2378b4476e | ||
|
|
237a67de01 | ||
|
|
6b8f6e8b66 | ||
|
|
4e076b787e | ||
|
|
436b58cede | ||
|
|
3902815113 | ||
|
|
228e7824de | ||
|
|
96c1330d9b | ||
|
|
0e7b312d97 | ||
|
|
8163c35efe | ||
|
|
95b5b56fd4 | ||
|
|
0207f85f00 | ||
|
|
9fb815081c | ||
|
|
3dfca4c3e2 | ||
|
|
2fc59dc082 | ||
|
|
ddd6c9daa3 | ||
|
|
feb59b8b8e | ||
|
|
4d7fc57934 | ||
|
|
fbcf204642 | ||
|
|
bdde0f79c1 | ||
|
|
30f1274405 | ||
|
|
04d6c8d223 | ||
|
|
f96b495b38 | ||
|
|
a597a0d061 | ||
|
|
d2f9a39d33 | ||
|
|
bf1c9c20ed | ||
|
|
10e7dfbbfa | ||
|
|
673bb83ab8 | ||
|
|
07df3bfa8f | ||
|
|
8c63e249b9 | ||
|
|
df2fec4999 | ||
|
|
64b51363a8 | ||
|
|
7c320b98da | ||
|
|
38f2c24167 | ||
|
|
c0303b6a40 | ||
|
|
b4349cde12 | ||
|
|
792be94a79 | ||
|
|
9ca50e87c2 | ||
|
|
cdd76789c6 | ||
|
|
4586ac0bdc | ||
|
|
0699df1754 | ||
|
|
12782e9869 | ||
|
|
ab577fa263 | ||
|
|
0f1438e8db | ||
|
|
52ffa79cb4 | ||
|
|
c98d69a483 | ||
|
|
cc32215fdd | ||
|
|
5d672842f3 | ||
|
|
a6e528d59b | ||
|
|
9b642a38ce | ||
|
|
ed9dae3760 | ||
|
|
eed3a3c007 | ||
|
|
aa22f882bd | ||
|
|
011b24e9ae | ||
|
|
b5a36e663d | ||
|
|
0ff7d40cb2 | ||
|
|
e20636b4f1 | ||
|
|
873c4ce65c | ||
|
|
5f637b4097 | ||
|
|
c48de3e09a | ||
|
|
f48f89ff1b | ||
|
|
d1d0011cc9 | ||
|
|
b0657c7fa4 | ||
|
|
c038ef882b | ||
|
|
717540682d | ||
|
|
bf1fd90133 | ||
|
|
f72652440e | ||
|
|
d4f6568269 | ||
|
|
5831275b7a | ||
|
|
7363d7c0ec | ||
|
|
b35e4e36e2 | ||
|
|
274e1e7fc0 | ||
|
|
9bdacb4493 | ||
|
|
aecd72847c | ||
|
|
d122d040fe | ||
|
|
4c4e800d19 | ||
|
|
5b4f36a419 | ||
|
|
8ba8a9af57 | ||
|
|
36ed564231 | ||
|
|
3f4ffdb93a | ||
|
|
34354e9577 | ||
|
|
7ce46cbae2 | ||
|
|
f16d91c5ad | ||
|
|
faa17a3e79 | ||
|
|
04c269b64a | ||
|
|
7df8ec73ec | ||
|
|
1f2b61cc29 | ||
|
|
c1d8ab3b97 | ||
|
|
ac02ab3f1a | ||
|
|
0c41bffb75 | ||
|
|
c77dc74965 | ||
|
|
f3b20b88f6 | ||
|
|
285475762e | ||
|
|
4fc424b145 | ||
|
|
57454962f1 | ||
|
|
fab2b8033e | ||
|
|
bb18eb3778 | ||
|
|
64f9ba7a21 | ||
|
|
ea5055b46c | ||
|
|
21db9a91b3 | ||
|
|
22d9757dc3 | ||
|
|
a898dc446f | ||
|
|
a70967f37d | ||
|
|
bb0a9a00f6 | ||
|
|
c3cbdd24ff | ||
|
|
f9a4b65452 | ||
|
|
f3062f7379 | ||
|
|
b052d48e1e | ||
|
|
2c57f772aa | ||
|
|
1c25448014 | ||
|
|
33b5c1ebe5 | ||
|
|
db8e187861 | ||
|
|
656fc1eb2c | ||
|
|
09ba4cb710 | ||
|
|
da713304fd | ||
|
|
d40c0855cb | ||
|
|
8d534a1cd2 | ||
|
|
ed355c4de2 | ||
|
|
249f002551 | ||
|
|
e053f06300 | ||
|
|
c8b0ad3731 | ||
|
|
3f90e35336 | ||
|
|
0603f64b4c | ||
|
|
71c9ec964a | ||
|
|
c97c7d14a0 | ||
|
|
6c201f9305 | ||
|
|
fe5cc36182 | ||
|
|
dded475c47 | ||
|
|
0ae9601309 | ||
|
|
281940942f | ||
|
|
97106dd427 | ||
|
|
40519b9c46 | ||
|
|
d82201d131 | ||
|
|
a70abcd64a | ||
|
|
f78deffbd5 | ||
|
|
44d2ba7bff | ||
|
|
6352ca66d9 | ||
|
|
7ab68f5a8e | ||
|
|
95c8369df2 | ||
|
|
cd7536f74f | ||
|
|
01b9064700 | ||
|
|
9925d4dc8b | ||
|
|
2445aa5aa9 | ||
|
|
a502ddd4df | ||
|
|
28a4d05fe4 | ||
|
|
2c1306ba2f | ||
|
|
fd126998bd | ||
|
|
d8716b71e2 | ||
|
|
bd81d696b8 | ||
|
|
b01172f405 | ||
|
|
68e0fd5dd5 | ||
|
|
800f43bd8a | ||
|
|
94d06aa8cd | ||
|
|
04db0114e7 | ||
|
|
f0f33fe201 | ||
|
|
0ea937847a | ||
|
|
28967ada51 | ||
|
|
8ca8b43f12 | ||
|
|
b6f70568fb | ||
|
|
1da972f0ea | ||
|
|
55406b2b18 | ||
|
|
6a87513703 | ||
|
|
e76d55c0a7 | ||
|
|
27bbdad7b1 | ||
|
|
24773f0c65 | ||
|
|
904555d1b6 | ||
|
|
54cfe7f2d0 | ||
|
|
2ab341cd71 | ||
|
|
bdf9b29f1e | ||
|
|
56e21000a5 | ||
|
|
4e13a18072 | ||
|
|
0a7a9587fc | ||
|
|
29a9383756 | ||
|
|
fee7a4f6da | ||
|
|
1b77f6b2de | ||
|
|
5ab2084763 | ||
|
|
1736fcb899 | ||
|
|
5ecff7fc4d | ||
|
|
5aff7d0fe7 | ||
|
|
46258f7f94 | ||
|
|
aa596b5859 | ||
|
|
e67d8ab40e | ||
|
|
464485a52c | ||
|
|
2eab1d18a7 | ||
|
|
1b96796ef0 | ||
|
|
ced4e47d54 | ||
|
|
4ee2ec7d74 | ||
|
|
33ee2d218e | ||
|
|
d29bfbff89 | ||
|
|
e42f739797 | ||
|
|
7fb04d8ffa | ||
|
|
d732777748 | ||
|
|
5708f778c7 | ||
|
|
f2802c87a8 | ||
|
|
866d82f44c | ||
|
|
2f3676f123 | ||
|
|
bab4f6cb3c | ||
|
|
8d67fbefd1 | ||
|
|
ed1a924156 | ||
|
|
0661780117 | ||
|
|
3ba574a147 | ||
|
|
d3fff01b14 | ||
|
|
a10ea32af3 | ||
|
|
aed6c1aac7 | ||
|
|
3ca52d6903 | ||
|
|
85439ced56 | ||
|
|
61db139a0d | ||
|
|
a31d8b3fa5 | ||
|
|
2c9f5f08d4 | ||
|
|
f4008a4750 | ||
|
|
2d847ed46f | ||
|
|
77dd528982 | ||
|
|
c2000bcb31 | ||
|
|
049c0f4aad | ||
|
|
a291899f52 | ||
|
|
ecd6efd4fd | ||
|
|
7c3f3542ac | ||
|
|
1c65440536 | ||
|
|
b0542dfe80 | ||
|
|
3da16a0a86 | ||
|
|
69dcb8793a | ||
|
|
b1d8b2483e | ||
|
|
ae48ee1842 | ||
|
|
e366830677 | ||
|
|
07cd42abc1 | ||
|
|
4378d49681 | ||
|
|
ef9618d443 | ||
|
|
862118fe1c | ||
|
|
d04c61aef5 | ||
|
|
869b65635c | ||
|
|
27722ae87c | ||
|
|
56a6735bfd | ||
|
|
a2b5b2a39a | ||
|
|
962780aeb1 | ||
|
|
7599e9d41f | ||
|
|
4443a5ca18 | ||
|
|
b90b2a3e77 | ||
|
|
606164bb43 | ||
|
|
1be8911070 | ||
|
|
4043682c3d | ||
|
|
7e1a5c7af0 | ||
|
|
579021658b | ||
|
|
0713bad871 | ||
|
|
43af2996bf | ||
|
|
dcca513aea | ||
|
|
d8cf5c7525 | ||
|
|
5ad84cd61e | ||
|
|
28627bfa85 | ||
|
|
4313650e69 | ||
|
|
30efb94287 | ||
|
|
3023f30225 | ||
|
|
4560b35b05 | ||
|
|
c8a0f19b3c | ||
|
|
e47856398b | ||
|
|
071dfaee9e | ||
|
|
07ec977efd | ||
|
|
73161efaf2 | ||
|
|
e73f5ce40e | ||
|
|
f37fc02067 | ||
|
|
a8d879c3f5 | ||
|
|
70004146e4 | ||
|
|
b570f413a9 | ||
|
|
4e9cb7a033 | ||
|
|
33651018e3 | ||
|
|
12accfee3f | ||
|
|
2925eb7469 | ||
|
|
16501372ae | ||
|
|
2933cb2e07 | ||
|
|
f05aea3b04 | ||
|
|
1dab596bc6 | ||
|
|
79891891b9 | ||
|
|
aabed9d686 | ||
|
|
b05c5856c2 | ||
|
|
dd8091abfc | ||
|
|
9dc776f1b3 | ||
|
|
81518fd57e | ||
|
|
10166293df | ||
|
|
85968e0609 | ||
|
|
cbc00bee03 | ||
|
|
83c7972877 | ||
|
|
a3505abd46 | ||
|
|
0cce97ac00 | ||
|
|
b1ce3a38ec | ||
|
|
3f27d6f5ad | ||
|
|
157f522b9b | ||
|
|
beca1919da | ||
|
|
88954f060f | ||
|
|
49cb2b349d | ||
|
|
2e474d4b56 | ||
|
|
89395b8681 | ||
|
|
701f786f07 | ||
|
|
1bca661e13 | ||
|
|
dbadbdafc3 | ||
|
|
759f1217b5 | ||
|
|
6805e07434 | ||
|
|
f976001c41 | ||
|
|
af4ccd29d7 | ||
|
|
07f65a0043 | ||
|
|
3002e9817f | ||
|
|
fe9a18dd03 | ||
|
|
19952b72f1 | ||
|
|
6668b5e861 |
No files matched your search
@@ -1,17 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "8.5.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"transform-react-jsx",
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties"
|
||||
"next/babel"
|
||||
]
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
pipeline:
|
||||
build:
|
||||
image: docker
|
||||
commands:
|
||||
- docker build -t tmshv/hudozka:latest -t tmshv/hudozka:build-${DRONE_BUILD_NUMBER} .
|
||||
- docker save -o hudozka-docker_container${DRONE_BUILD_NUMBER}.tar tmshv/hudozka:build-${DRONE_BUILD_NUMBER}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
scp:
|
||||
image: appleboy/drone-scp
|
||||
host: art.shlisselburg.org
|
||||
username: master
|
||||
target: ~/Hudozka
|
||||
source: hudozka-docker_container${DRONE_BUILD_NUMBER}.tar
|
||||
secrets: [scp_key]
|
||||
when:
|
||||
branch: master
|
||||
@@ -9,10 +9,6 @@ module.exports = {
|
||||
"promise",
|
||||
"react",
|
||||
],
|
||||
"globals": {
|
||||
"angular": true,
|
||||
"DISQUS": true
|
||||
},
|
||||
"rules": {
|
||||
"semi": ["error", "never"],
|
||||
"space-before-function-paren": "off",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
label: Article
|
||||
hide_body: false
|
||||
fields:
|
||||
- name: slug
|
||||
type: text
|
||||
config:
|
||||
required: false
|
||||
label: slug
|
||||
- name: tags
|
||||
type: tag_list
|
||||
default: []
|
||||
label: tags
|
||||
pages:
|
||||
- data/articles/article-test.md
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
new_page_extension: md
|
||||
auto_deploy: false
|
||||
admin_path: ''
|
||||
webhook_url:
|
||||
sections:
|
||||
- type: directory
|
||||
path: data/articles
|
||||
label: Articles
|
||||
create: documents
|
||||
match: "**/*"
|
||||
- type: document
|
||||
path: data/settings.json
|
||||
label: Settings
|
||||
upload_dir: uploads
|
||||
public_path: "/uploads"
|
||||
front_matter_path: ''
|
||||
use_front_matter_path: false
|
||||
file_template: ":year:/:filename:"
|
||||
build:
|
||||
preview_command: gatsby build
|
||||
publish_command: gatsby build
|
||||
preview_output_directory: public
|
||||
output_directory: public
|
||||
instant_preview_command: npm run forestry:preview
|
||||
install_dependencies_command: yarn install
|
||||
preview_docker_image: node:10
|
||||
mount_path: "/srv"
|
||||
working_dir: "/srv"
|
||||
+4
-1
@@ -8,12 +8,15 @@ private*.json
|
||||
/node_modules
|
||||
/bower_components
|
||||
/modules/hudozka-api-v1/lib
|
||||
/public
|
||||
/build
|
||||
/out
|
||||
dist
|
||||
.next
|
||||
**/env
|
||||
**/venv
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
/coverage
|
||||
**/.vscode
|
||||
*.log
|
||||
.now
|
||||
@@ -0,0 +1,5 @@
|
||||
.forestry
|
||||
./utils
|
||||
modules
|
||||
data
|
||||
uploads
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
# Build
|
||||
FROM node:10 as build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm i
|
||||
COPY webpack.config.js gulpfile.js .babelrc ./
|
||||
COPY robots.txt ./
|
||||
COPY src ./src
|
||||
RUN npm run gulp
|
||||
RUN npm run build-app
|
||||
RUN npm run out
|
||||
|
||||
|
||||
# Run
|
||||
FROM node:8-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm i --production
|
||||
|
||||
COPY --from=build /app/out ./src
|
||||
COPY --from=build /app/public ./public
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD npm start
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Site of Art school of Shlisselburg
|
||||
|
||||
Font: [«Shadow»](http://mishapanfilov.ru/font_shadow.html)
|
||||
|
||||
## Usage
|
||||
|
||||
### Build
|
||||
@@ -36,11 +38,5 @@ PWD=<mongo_password>
|
||||
mongodump -d ${DB} --gzip --archive | mongorestore -h ${HOST} -u ${USER} -p ${PWD} -d ${DB} --gzip --archive
|
||||
```
|
||||
|
||||
## Based on:
|
||||
- [NodeJS](http://nodejs.org) 10.0
|
||||
- [koa.js](http://koajs.com) 2.0
|
||||
- [React](https://react.org) 16.3
|
||||
- [MongoDB](http://mongodb.org) 3.6
|
||||
|
||||
## Contributors
|
||||
- Roman Timashev ([roman@tmshv.ru](mailto:roman@tmshv.ru))
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
tags:
|
||||
- test
|
||||
- dev
|
||||
slug: article-test
|
||||
|
||||
---
|
||||
This is **Hudozka**
|
||||
|
||||

|
||||
|
||||
1. 1
|
||||
2. 2
|
||||
3. 3
|
||||
|
||||
[Устав 2015](https://app.forestry.io/sites/tadnt0fp3fdrgg/body-media//uploads/2019/Устав 2015.pdf "Устав 2015")
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "Hudozka"
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
version: '3'
|
||||
volumes:
|
||||
data:
|
||||
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:3.6
|
||||
restart: always
|
||||
volumes:
|
||||
- data:/data/db
|
||||
ports:
|
||||
- "1817:27017"
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=hudozka
|
||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_HUDOZKA_PASSWORD}
|
||||
|
||||
app:
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongo
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
links:
|
||||
- mongo
|
||||
ports:
|
||||
- "1880:3000"
|
||||
environment:
|
||||
- DB_URI=${HUDOZKA_DB_URI}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"uid": "hudozka",
|
||||
"append": true,
|
||||
"watch": false,
|
||||
"script": "index.js",
|
||||
"sourceDir": "/home/hoster/www/art.shburg.org/out",
|
||||
"wordingDir": "/home/hoster/www/art.shburg.org"
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
const gulp = require('gulp')
|
||||
const postcss = require('gulp-postcss')
|
||||
const sass = require('gulp-sass')
|
||||
const imageMin = require('gulp-imagemin')
|
||||
const concat = require('gulp-concat')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const cssnano = require('cssnano')
|
||||
|
||||
gulp.task('style', () => {
|
||||
const processors = [
|
||||
autoprefixer({
|
||||
browsers: ['last 1 version']
|
||||
}),
|
||||
cssnano({
|
||||
zindex: false
|
||||
})
|
||||
]
|
||||
|
||||
return gulp.src('./src/style/style.scss')
|
||||
.pipe(sass())
|
||||
.pipe(postcss(processors))
|
||||
.pipe(concat('style.css'))
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy fonts', () => {
|
||||
return gulp.src('./src/assets/fonts/*/*')
|
||||
.pipe(gulp.dest('./public/fonts'))
|
||||
})
|
||||
|
||||
gulp.task('copy graphics', () => {
|
||||
return gulp.src('./src/assets/graphics/*')
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('copy 3rdparty', () => {
|
||||
return gulp.src('./src/3rdparty/*')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy robots.txt', () => {
|
||||
return gulp.src('./robots.txt')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy views', () => {
|
||||
return gulp.src('./src/views/**')
|
||||
.pipe(gulp.dest('./out/views'))
|
||||
})
|
||||
|
||||
gulp.task('imagemin', () => {
|
||||
return gulp.src('./src/assets/graphics/**/*')
|
||||
.pipe(imageMin())
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('copy', gulp.series('copy views', 'copy fonts', 'copy graphics', 'copy robots.txt', 'copy 3rdparty'))
|
||||
gulp.task('default', gulp.series('style', 'copy', 'imagemin'))
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
dir_path="$1"
|
||||
build_file="$2"
|
||||
app="hudozka"
|
||||
script="forever.json"
|
||||
|
||||
cd ${dir_path}
|
||||
|
||||
gunzip -c ${build_file} | tar xopf -
|
||||
|
||||
#rm build.tar
|
||||
|
||||
npm install --production --loglevel error
|
||||
|
||||
echo "Deploying finish. Please restart hudozka.service manually"
|
||||
@@ -1,27 +0,0 @@
|
||||
FROM alpine:3.5
|
||||
|
||||
RUN apk add --no-cache python3 && \
|
||||
apk add --no-cache py3-lxml && \
|
||||
python3 -m ensurepip && \
|
||||
rm -r /usr/lib/python*/ensurepip && \
|
||||
pip3 install --upgrade pip setuptools && \
|
||||
rm -r /root/.cache
|
||||
|
||||
LABEL maintainer "Roman Timashev <roman@tmshv.ru>"
|
||||
|
||||
ENV INSTALL_PATH /app
|
||||
RUN mkdir -p $INSTALL_PATH
|
||||
WORKDIR /$INSTALL_PATH
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
COPY app ./app
|
||||
|
||||
ENV DB_URI "mongodb://localhost:27017"
|
||||
ENV DB_USER ""
|
||||
ENV DB_PASSWORD ""
|
||||
ENV DB_NAME "hudozka"
|
||||
|
||||
EXPOSE 5000
|
||||
CMD python3 app/main.py
|
||||
@@ -1,10 +0,0 @@
|
||||
# Feed
|
||||
|
||||
Link to feed: [art.shlisselburg.org/feed](https://art.shlisselburg.org/feed)
|
||||
|
||||
## How to deploy with Docker
|
||||
|
||||
```bash
|
||||
docker build -t hudozka/feed .
|
||||
docker run -d --name hudozka-feed -p 18050:5000 -e DB_USER="<DB_USER>" -e DB_PASSWORD="<DB_PASSWORD>" -e DB_URI="<DB_URI>" hudozka/feed
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
from pymongo import MongoClient
|
||||
from settings import db_uri, db_name, db_user, db_password
|
||||
|
||||
db = MongoClient(db_uri)[db_name]
|
||||
if db_user and db_password:
|
||||
auth_status = db.authenticate(db_user, db_password)
|
||||
if not auth_status:
|
||||
print('Failed to authenticate user')
|
||||
exit(1)
|
||||
|
||||
|
||||
def collection(name):
|
||||
return db[name]
|
||||
@@ -1,39 +0,0 @@
|
||||
import html
|
||||
|
||||
from feedgen.feed import FeedGenerator
|
||||
|
||||
from db import collection
|
||||
|
||||
|
||||
def get_items(collection_name, query={}):
|
||||
return list(
|
||||
collection(collection_name).find(query)
|
||||
)
|
||||
|
||||
|
||||
def get_feed():
|
||||
events = get_items('events')
|
||||
news = get_items('timeline', {'type': 'post'})
|
||||
|
||||
articles = sorted(events + news, key=lambda item: item['date'], reverse=True)
|
||||
|
||||
fg = FeedGenerator()
|
||||
fg.id('art.shlisselburg.org')
|
||||
fg.title('Шлиссельбургская ДХШ')
|
||||
fg.link(href='https://art.shlisselburg.org', rel='alternate')
|
||||
fg.logo('https://static.shlisselburg.org/art/graphics/favicon-144.png')
|
||||
fg.link(href='https://art.shlisselburg.org/feed', rel='self')
|
||||
fg.language('ru')
|
||||
|
||||
for article in articles:
|
||||
content = html.unescape(article['post'])
|
||||
article_id = article['id']
|
||||
url = 'https://art.shlisselburg.org/article/{id}'.format(id=article_id)
|
||||
|
||||
fe = fg.add_entry()
|
||||
fe.id(article_id)
|
||||
fe.title(article['title'])
|
||||
fe.content(content, None, 'CDATA')
|
||||
fe.link({'href': url})
|
||||
|
||||
return fg.atom_str(pretty=True)
|
||||
@@ -1,5 +0,0 @@
|
||||
from server import app
|
||||
from settings import host
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host=host)
|
||||
@@ -1,14 +0,0 @@
|
||||
from flask import Flask
|
||||
|
||||
from feed import get_feed
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def root():
|
||||
feed = get_feed()
|
||||
if not feed:
|
||||
return '', 404
|
||||
|
||||
return feed
|
||||
@@ -1,7 +0,0 @@
|
||||
import os
|
||||
|
||||
db_uri = os.getenv('DB_URI', 'mongodb://localhost:27017')
|
||||
db_name = os.getenv('DB_NAME', 'hudozka')
|
||||
db_user = os.getenv('DB_USER', None)
|
||||
db_password = os.getenv('DB_PASSWORD', None)
|
||||
host = os.getenv('APP_HOST', '0.0.0.0')
|
||||
@@ -1,5 +0,0 @@
|
||||
pymongo==3.4.0
|
||||
Flask==0.12
|
||||
redis==2.10.5
|
||||
PyYAML==3.12
|
||||
feedgen==0.5.1
|
||||
@@ -0,0 +1,4 @@
|
||||
.env
|
||||
*.tar
|
||||
node_modules
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
.env
|
||||
*.tar
|
||||
@@ -0,0 +1,27 @@
|
||||
# BUILD
|
||||
FROM node:12-alpine as build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
# Building app
|
||||
RUN npm run build
|
||||
|
||||
# RUNTIME
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install --production
|
||||
|
||||
COPY --from=build /app/build ./build
|
||||
|
||||
EXPOSE 80
|
||||
ENV PORT 80
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Running the app
|
||||
CMD [ "npm", "start" ]
|
||||
Generated
+755
@@ -0,0 +1,755 @@
|
||||
{
|
||||
"name": "hudozka-api",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/bson": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.1.tgz",
|
||||
"integrity": "sha512-K6VAEdLVJFBxKp8m5cRTbUfeZpuSvOuLKJLrgw9ANIXo00RiyGzgH4BKWWR4F520gV4tWmxG7q9sKQRVDuzrBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/classnames": {
|
||||
"version": "2.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz",
|
||||
"integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
|
||||
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
|
||||
"integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"@types/express-serve-static-core": {
|
||||
"version": "4.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz",
|
||||
"integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
|
||||
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mongodb": {
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.3.16.tgz",
|
||||
"integrity": "sha512-i1Ov36BXdp+urtPsaSvrNfCdsaVn4Ukq1m1kGyzdWB1+eg3gJ68unXU5LNmnF4EAMRFY6FXZzA7/W3NST40b8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/bson": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "13.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz",
|
||||
"integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
|
||||
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/mime": "*"
|
||||
}
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"bl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
|
||||
"integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
|
||||
"requires": {
|
||||
"readable-stream": "^2.3.5",
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bson": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz",
|
||||
"integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg=="
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"denque": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
|
||||
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.1.2",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
}
|
||||
}
|
||||
},
|
||||
"feed": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/feed/-/feed-4.1.0.tgz",
|
||||
"integrity": "sha512-dAXWXM8QMxZ1DRnAxDmy1MaWZFlh1Ku7TU3onbXgHrVJynsxkNGPUed1AxszVW8AXo43xExronVkIqK+ACsoBA==",
|
||||
"requires": {
|
||||
"xml-js": "^1.6.11"
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~1.5.0",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
||||
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"make-error": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
|
||||
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
|
||||
"dev": true
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"memory-pager": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
||||
"optional": true
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.43.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
|
||||
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.26",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
|
||||
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
|
||||
"requires": {
|
||||
"mime-db": "1.43.0"
|
||||
}
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.3.tgz",
|
||||
"integrity": "sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg==",
|
||||
"requires": {
|
||||
"bl": "^2.2.0",
|
||||
"bson": "^1.1.1",
|
||||
"denque": "^1.4.1",
|
||||
"require_optional": "^1.0.1",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"saslprep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
|
||||
"integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
}
|
||||
}
|
||||
},
|
||||
"require_optional": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
|
||||
"integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
|
||||
"requires": {
|
||||
"resolve-from": "^2.0.0",
|
||||
"semver": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
|
||||
"integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"saslprep": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"sitemap": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-1.13.0.tgz",
|
||||
"integrity": "sha1-Vpy+IYAgKSamKiZs094Jyc60P4M=",
|
||||
"requires": {
|
||||
"underscore": "^1.7.0",
|
||||
"url-join": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
|
||||
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"sparse-bitfield": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||
"integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"memory-pager": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz",
|
||||
"integrity": "sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arg": "^4.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"source-map-support": "^0.5.6",
|
||||
"yn": "3.1.1"
|
||||
}
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.7.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz",
|
||||
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
|
||||
"dev": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz",
|
||||
"integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ=="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"url-join": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz",
|
||||
"integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"xml-js": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
|
||||
"requires": {
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "hudozka-api",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/server.ts",
|
||||
"scripts": {
|
||||
"start": "node build/server.js",
|
||||
"dev": "ts-node src/server.ts",
|
||||
"build": "tsc",
|
||||
"sitemap": "node scripts/sitemap.js",
|
||||
"feed": "node scripts/feed.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tmshv/hudozka"
|
||||
},
|
||||
"author": "Roman Timashev",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"feed": "^4.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"mongodb": "^3.5.3",
|
||||
"sitemap": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/mongodb": "^3.3.16",
|
||||
"@types/node": "^13.7.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.7.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { getPathWaterfall } from '../lib/url'
|
||||
import { Breadcrumb } from '../types'
|
||||
import Page from '../core/Page'
|
||||
|
||||
export async function getItems(path: string): Promise<Breadcrumb> {
|
||||
const bc = getPathWaterfall(path)
|
||||
const parts = await Promise.all(bc.map(async href => {
|
||||
const resource = await Page.findByUrl(href)
|
||||
|
||||
if (!resource) {
|
||||
return null
|
||||
}
|
||||
|
||||
const name = resource.title
|
||||
|
||||
return {
|
||||
name,
|
||||
href,
|
||||
}
|
||||
}))
|
||||
|
||||
return parts.filter(Boolean)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Request, Response } from 'express'
|
||||
import Document from '../core/Document'
|
||||
import { encodeFile } from '../factory/file'
|
||||
|
||||
export async function getItem(req: Request, res: Response) {
|
||||
const id = req.params.slug
|
||||
const document = await Document.findById(id)
|
||||
|
||||
if (document) {
|
||||
res.json(encodeFile(document))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Resource ${id} not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAll(req: Request, res: Response) {
|
||||
const documents = await Document.find({ hidden: false })
|
||||
if (documents) {
|
||||
const items = documents
|
||||
.map(encodeFile)
|
||||
.filter(x => x.imageUrl) // skip documents without preview image; needs to be refactored
|
||||
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUrls(req: Request, res: Response) {
|
||||
const data = await Document.find({})
|
||||
|
||||
if (data) {
|
||||
const items = data.map(x => `/document/${x.id}`)
|
||||
res.json({
|
||||
items
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import Image from '../core/Image'
|
||||
import { encodeImage } from '../factory/image'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
export async function getItem(req: Request, res: Response) {
|
||||
const file = req.query.file
|
||||
const image = await Image.findByFile(file)
|
||||
|
||||
if (image) {
|
||||
res.json(encodeImage(image))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Resource ${file} not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { pick } from 'lodash'
|
||||
import { Request, Response } from 'express'
|
||||
import Page from '../core/Page'
|
||||
import { dropTrailingSlash, readQueryArray, readQueryInt, readQueryString } from '../lib/url'
|
||||
import { encodePage } from '../factory/page'
|
||||
import * as breadcrumb from './breadcrumb'
|
||||
import { isArrayOk } from '../lib/array'
|
||||
|
||||
function getSort(sortBy: string) {
|
||||
if (!sortBy) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (sortBy.charAt(0) === '-') {
|
||||
const key = sortBy.substr(1, sortBy.length)
|
||||
|
||||
return {
|
||||
[key]: -1,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[sortBy]: 1,
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveItem(page: Page) {
|
||||
const breadcrumbs = await breadcrumb.getItems(page.getUrl())
|
||||
page.setBreadcrumb(breadcrumbs)
|
||||
|
||||
return page
|
||||
}
|
||||
|
||||
export async function getItem(req: Request, res: Response) {
|
||||
const pageUrl = dropTrailingSlash(req.query.page)
|
||||
const resource = await Page.findByUrl(pageUrl)
|
||||
|
||||
if (resource) {
|
||||
const page = await resolveItem(resource)
|
||||
res.json(encodePage(page))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Resource ${pageUrl} not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAll(req: Request, res: Response) {
|
||||
const items = await Page.find({})
|
||||
|
||||
if (items) {
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getByTags(req: Request, res: Response) {
|
||||
const skip = readQueryInt(req, 'skip')
|
||||
const limit = readQueryInt(req, 'limit')
|
||||
const sortBy = readQueryString(req, 'sortBy')
|
||||
const sort = getSort(sortBy)
|
||||
const fields = readQueryArray(req, 'field')
|
||||
|
||||
const tags = readQueryArray(req, 'tag')
|
||||
if (tags.length === 0) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `No tags in request`
|
||||
})
|
||||
}
|
||||
|
||||
const options = { skip, limit, sort }
|
||||
const models = await Page.find({
|
||||
tags: { $in: tags },
|
||||
}, options)
|
||||
|
||||
if (models) {
|
||||
const pages = await Promise.all(models.map(resolveItem))
|
||||
const encodedItems = pages.map(encodePage)
|
||||
const items = isArrayOk(fields)
|
||||
? encodedItems.map(x => pick(x, fields))
|
||||
: encodedItems
|
||||
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUrls(req: Request, res: Response) {
|
||||
const data = await Page.find({})
|
||||
|
||||
if (data) {
|
||||
const items = data.map(x => x.url)
|
||||
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import Page from '../core/Page'
|
||||
import { Request, Response } from 'express'
|
||||
import { unique } from '../lib/array'
|
||||
|
||||
export async function getAll(req: Request, res: Response) {
|
||||
const data = await Page.find({})
|
||||
const tags = data
|
||||
.flatMap(x => x.tags)
|
||||
.filter(x => x.isValid())
|
||||
const items = unique(tags, t => t.slug)
|
||||
|
||||
if (data) {
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Collection } from 'mongodb'
|
||||
import * as db from '../db'
|
||||
|
||||
const store = new Map<string, Collection>()
|
||||
|
||||
export default class Data {
|
||||
static setStore(key: string, collection: Collection) {
|
||||
store.set(key, collection)
|
||||
}
|
||||
|
||||
static getStore<T>(key: string): Collection<T> {
|
||||
return store.get(key)
|
||||
}
|
||||
|
||||
static getCollection(name: string): Collection {
|
||||
return db.collection(name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import Data from './Data'
|
||||
import Image from './Image'
|
||||
import { find, findOne } from '../lib/store'
|
||||
|
||||
const store = () => Data.getStore('Document')
|
||||
|
||||
export default class Document {
|
||||
static async find(query, options = {}): Promise<Document[]> {
|
||||
const items = await find(store(), query, options as any)
|
||||
|
||||
return Promise.all(items.map(processDocument))
|
||||
}
|
||||
|
||||
static async findById(id) {
|
||||
const data = await findOne(store(), { id })
|
||||
if (!data) return null
|
||||
|
||||
return processDocument(data)
|
||||
}
|
||||
|
||||
public id: any
|
||||
public hash: any
|
||||
public file: any
|
||||
public fileInfo: any
|
||||
public type: any
|
||||
public title: any
|
||||
public category: any
|
||||
public url: any
|
||||
public preview: any
|
||||
public viewUrl: any
|
||||
|
||||
constructor(data) {
|
||||
this.id = data.id
|
||||
this.hash = data.hash
|
||||
this.file = data.file
|
||||
// In the future: use fileInfo only
|
||||
this.fileInfo = data.fileInfo
|
||||
? new DocumentFile(data.fileInfo)
|
||||
: new DocumentFile(data.file)
|
||||
this.type = data.type
|
||||
this.title = data.title
|
||||
this.category = data.category
|
||||
this.url = data.url
|
||||
|
||||
this.preview = data.preview
|
||||
this.viewUrl = `/document/${this.id}`
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentFile {
|
||||
public name: any
|
||||
public size: any
|
||||
|
||||
constructor(data) {
|
||||
this.name = data.name
|
||||
this.size = data.size
|
||||
}
|
||||
}
|
||||
|
||||
const processDocument = async data => {
|
||||
const imageId = data.preview
|
||||
const preview = await Image.findById(imageId)
|
||||
|
||||
return new Document({
|
||||
...data,
|
||||
preview,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import Data from './Data'
|
||||
import ImageArtifactType from './ImageArtifactType'
|
||||
import { findOne } from '../lib/store'
|
||||
import { ObjectId } from 'mongodb'
|
||||
|
||||
/**
|
||||
*
|
||||
* {a: {}, b: {}, ...} -> Map (a: Class, b: Class)
|
||||
*
|
||||
* @param Class
|
||||
* @param object
|
||||
*/
|
||||
function mapOf(Class, object) {
|
||||
return Object
|
||||
.entries(object)
|
||||
.reduce((map, i) => (
|
||||
map.set(i[0], new Class(i[1]))
|
||||
), new Map())
|
||||
}
|
||||
|
||||
const store = () => Data.getStore('Image')
|
||||
|
||||
export default class Image {
|
||||
static async findById(id: ObjectId) {
|
||||
const data = await findOne(store(), { _id: id })
|
||||
if (!data) return null
|
||||
|
||||
const artifacts = data.data
|
||||
return new Image({ ...data, artifacts })
|
||||
}
|
||||
|
||||
static async findByFile(file: string) {
|
||||
const data = await findOne(store(), { file })
|
||||
if (!data) return null
|
||||
|
||||
const artifacts = data.data
|
||||
return new Image({ ...data, artifacts })
|
||||
}
|
||||
public hash: string
|
||||
public file: string
|
||||
public artifacts: Map<string, ImageArtifact>
|
||||
|
||||
constructor({ hash, file, artifacts }) {
|
||||
this.hash = hash
|
||||
this.file = file
|
||||
|
||||
this.artifacts = mapOf(ImageArtifact, artifacts)
|
||||
}
|
||||
|
||||
getArtifact(size) {
|
||||
return this.artifacts.get(size)
|
||||
}
|
||||
|
||||
findArtifact(sizes) {
|
||||
for (let size of sizes) {
|
||||
const a = this.getArtifact(size)
|
||||
if (a) return a
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
getPicture(size) {
|
||||
const retinaSize = ImageArtifactType.retina(size)
|
||||
|
||||
const a = this.getArtifact(size)
|
||||
const a2 = this.getArtifact(retinaSize)
|
||||
const srcset = !a2
|
||||
? []
|
||||
: [{
|
||||
url: a2.url,
|
||||
density: ImageArtifactType.RETINA_DENSITY,
|
||||
}]
|
||||
|
||||
return {
|
||||
src: a.url,
|
||||
width: a.width,
|
||||
height: a.height,
|
||||
set: srcset,
|
||||
}
|
||||
}
|
||||
|
||||
getSrc(): string | null {
|
||||
const a = this.getArtifact(ImageArtifactType.ORIGINAL)
|
||||
if (!a) {
|
||||
return null
|
||||
}
|
||||
|
||||
return a.url
|
||||
}
|
||||
}
|
||||
|
||||
class ImageArtifact {
|
||||
public url: string
|
||||
public size: string
|
||||
public width: number
|
||||
public height: number
|
||||
|
||||
constructor({ url, size, width, height }) {
|
||||
this.url = url
|
||||
this.size = size
|
||||
this.width = width
|
||||
this.height = height
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,113 @@
|
||||
import Data from './Data'
|
||||
import { find as storeFind, findOne, total } from '../lib/store'
|
||||
import Image from './Image'
|
||||
import { Tag } from './Tag'
|
||||
import { ObjectId } from 'mongodb'
|
||||
import { Breadcrumb } from '../types'
|
||||
|
||||
const store = () => Data.getStore('Page')
|
||||
|
||||
type PageDto = {
|
||||
[name: string]: any
|
||||
preview?: ObjectId
|
||||
images: ObjectId[]
|
||||
}
|
||||
|
||||
export default class Page {
|
||||
static async find(query, options = {}): Promise<Page[]> {
|
||||
const items = await storeFind(store(), query, options as any)
|
||||
|
||||
return Promise.all(items.map(processPage))
|
||||
}
|
||||
|
||||
static async findById(id) {
|
||||
const item = await findOne(store(), { id })
|
||||
|
||||
return !item
|
||||
? null
|
||||
: processPage(item)
|
||||
}
|
||||
|
||||
static async findByUrl(url: string) {
|
||||
const item = await findOne(store(), { url })
|
||||
|
||||
return !item
|
||||
? null
|
||||
: processPage(item)
|
||||
}
|
||||
|
||||
static async total(query = {}) {
|
||||
return total(store(), query)
|
||||
}
|
||||
|
||||
public id: any
|
||||
public hash: any
|
||||
public file: any
|
||||
public url: string
|
||||
public content: any
|
||||
public title: any
|
||||
public date: Date
|
||||
public tags: Tag[]
|
||||
public data: any
|
||||
public images: ObjectId[]
|
||||
public documents: any
|
||||
public preview?: Image
|
||||
public tokens: any[]
|
||||
public description: string
|
||||
public featured: boolean
|
||||
|
||||
private breadcrumb: Breadcrumb
|
||||
|
||||
constructor(data: { [name: string]: any }) {
|
||||
this.id = data.id
|
||||
this.hash = data.hash
|
||||
this.file = data.file
|
||||
this.url = data.url
|
||||
this.content = data.content
|
||||
this.title = data.title
|
||||
this.data = data.data
|
||||
this.date = data.date
|
||||
this.images = data.images
|
||||
this.documents = data.documents
|
||||
this.preview = data.preview
|
||||
this.tokens = data.tokens
|
||||
this.tags = (data.tags || []).map((x: string) => new Tag(x, x))
|
||||
this.featured = data.featured
|
||||
this.description = data.description
|
||||
}
|
||||
|
||||
public setBreadcrumb(value: Breadcrumb) {
|
||||
this.breadcrumb = value
|
||||
return this
|
||||
}
|
||||
|
||||
public getBreadcrumb() {
|
||||
return this.breadcrumb
|
||||
}
|
||||
|
||||
public getUrl() {
|
||||
return this.url
|
||||
}
|
||||
}
|
||||
|
||||
function firstItem<T>(items: T[] = []): T {
|
||||
return items.length
|
||||
? items[0]
|
||||
: null
|
||||
}
|
||||
|
||||
async function processPage(item: PageDto): Promise<Page> {
|
||||
let preview: Image = null
|
||||
const previewId = item.preview
|
||||
? item.preview
|
||||
: firstItem(item.images)
|
||||
|
||||
if (previewId) {
|
||||
preview = await Image.findById(previewId)
|
||||
}
|
||||
|
||||
return new Page({
|
||||
...item,
|
||||
preview,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { translit } from '../lib/translit'
|
||||
|
||||
export class Tag {
|
||||
public static fromCyrillic(tag: string) {
|
||||
const slug = tag
|
||||
? translit(tag).toLowerCase()
|
||||
: tag
|
||||
|
||||
return new Tag(tag, slug)
|
||||
}
|
||||
|
||||
public name: string
|
||||
public slug: string
|
||||
|
||||
constructor(name: string, slug: string) {
|
||||
this.name = name
|
||||
this.slug = slug
|
||||
}
|
||||
|
||||
public isValid() {
|
||||
return !!this.slug
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Data from './core/Data'
|
||||
import { MongoClient, ObjectId, Db } from 'mongodb'
|
||||
|
||||
var db: Db
|
||||
|
||||
export async function connect(dbName: string, mongoUri: string) {
|
||||
if (db) {
|
||||
return db
|
||||
}
|
||||
const connection = await MongoClient.connect(mongoUri, { useUnifiedTopology: true })
|
||||
db = init(connection.db(dbName))
|
||||
return db
|
||||
}
|
||||
|
||||
export function collection(name) {
|
||||
return db.collection(name)
|
||||
}
|
||||
|
||||
export function id(i) {
|
||||
if (typeof i === 'string') return new ObjectId(i)
|
||||
else if (i instanceof ObjectId) return i
|
||||
else if (i instanceof Object) return '_id' in i ? new ObjectId(i._id) : null
|
||||
return null
|
||||
}
|
||||
|
||||
function init(db: Db) {
|
||||
console.log('INIT DB')
|
||||
|
||||
Data.setStore('Page', db.collection('pages'))
|
||||
Data.setStore('Image', db.collection('images'))
|
||||
Data.setStore('Document', db.collection('documents'))
|
||||
Data.setStore('Settings', db.collection('settings'))
|
||||
|
||||
return db
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import ImageArtifactType from '../core/ImageArtifactType'
|
||||
|
||||
const sizes = [
|
||||
ImageArtifactType.MEDIUM,
|
||||
ImageArtifactType.BIG,
|
||||
ImageArtifactType.ORIGIN,
|
||||
]
|
||||
|
||||
export function encodeFilePreview(file, sizes) {
|
||||
const preview = file.preview
|
||||
if (!preview) return null
|
||||
|
||||
const image = preview.findArtifact(sizes)
|
||||
if (!image) return null
|
||||
|
||||
return image.url
|
||||
}
|
||||
|
||||
export function encodeFile(file) {
|
||||
const imageUrl = encodeFilePreview(file, sizes)
|
||||
const category = (file.category || '')
|
||||
.replace('Documents/', '')
|
||||
.replace('Pages/', '')
|
||||
.replace('Articles /', '')
|
||||
|
||||
return {
|
||||
imageUrl,
|
||||
category,
|
||||
title: file.title,
|
||||
fileName: file.fileInfo.name,
|
||||
fileSize: file.fileInfo.size,
|
||||
fileUrl: file.url,
|
||||
url: file.viewUrl,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import ImageArtifactType from '../core/ImageArtifactType'
|
||||
import Image from '../core/Image'
|
||||
|
||||
export type ImageArtifactDto = {
|
||||
height: number
|
||||
width: number
|
||||
src: string
|
||||
set: Array<{
|
||||
density: number
|
||||
url: string
|
||||
}>
|
||||
}
|
||||
|
||||
export type ImageDto = {
|
||||
artifacts: {
|
||||
original: ImageArtifactDto,
|
||||
large: ImageArtifactDto,
|
||||
big: ImageArtifactDto,
|
||||
medium: ImageArtifactDto,
|
||||
small: ImageArtifactDto,
|
||||
fb: ImageArtifactDto,
|
||||
},
|
||||
hash: string
|
||||
file: string
|
||||
}
|
||||
|
||||
export function encodeImage(image: Image): ImageDto {
|
||||
const artifacts = [
|
||||
ImageArtifactType.SMALL,
|
||||
ImageArtifactType.MEDIUM,
|
||||
ImageArtifactType.BIG,
|
||||
ImageArtifactType.LARGE,
|
||||
ImageArtifactType.ORIGIN,
|
||||
ImageArtifactType.ORIGINAL,
|
||||
ImageArtifactType.FACEBOOK = 'fb'
|
||||
].reduce((acc, size) => {
|
||||
return {
|
||||
...acc,
|
||||
[size]: image.getPicture(size),
|
||||
}
|
||||
}, {})
|
||||
|
||||
return {
|
||||
artifacts: artifacts as any,
|
||||
hash: image.hash,
|
||||
file: image.file,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { encodeImage, ImageDto } from './image'
|
||||
import Page from '../core/Page'
|
||||
import { Breadcrumb } from '../types'
|
||||
import { Tag } from '../core/Tag'
|
||||
|
||||
type Token = {
|
||||
token: string
|
||||
data: any
|
||||
}
|
||||
|
||||
type PageDto = {
|
||||
id: string
|
||||
title: string
|
||||
date?: string
|
||||
url: string
|
||||
data: string
|
||||
description: string
|
||||
preview?: ImageDto
|
||||
tokens: Token[]
|
||||
tags: Tag[]
|
||||
featured: boolean
|
||||
breadcrumb: Breadcrumb
|
||||
coverSrc?: string
|
||||
}
|
||||
|
||||
export function encodePage(page: Page): PageDto {
|
||||
const preview = page.preview ? encodeImage(page.preview) : null
|
||||
const date = page.date ? page.date.toString() : null
|
||||
const breadcrumb = page.getBreadcrumb()
|
||||
|
||||
const coverSrc = page.preview ? page.preview.getSrc() : null
|
||||
|
||||
return {
|
||||
id: page.id,
|
||||
title: page.title,
|
||||
url: page.url,
|
||||
data: page.data,
|
||||
description: page.description,
|
||||
tokens: page.tokens,
|
||||
tags: page.tags,
|
||||
featured: page.featured,
|
||||
preview,
|
||||
date,
|
||||
breadcrumb,
|
||||
coverSrc,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
export function unique<T>(items: T[], key: (item: T) => string): T[] {
|
||||
const map = new Map(items.map(x => [key(x), x]))
|
||||
|
||||
return [...map.values()]
|
||||
}
|
||||
|
||||
export function ensureArray<T>(value: T | T[]): T[] {
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
return [value]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Check if array has one item at least
|
||||
*
|
||||
* @param items
|
||||
*/
|
||||
export function isArrayOk<T>(items: T[]): boolean {
|
||||
if (!Array.isArray(items)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return items.length > 0
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export async function find(collection, query, { sort, skip, limit }) {
|
||||
const cursor = collection.find(query)
|
||||
|
||||
if (sort) cursor.sort(sort)
|
||||
if (skip) cursor.skip(skip)
|
||||
if (limit) cursor.limit(limit)
|
||||
|
||||
return cursor.toArray()
|
||||
}
|
||||
|
||||
export async function findOne(collection, query) {
|
||||
return collection.findOne(query)
|
||||
}
|
||||
|
||||
export async function total(collection, query) {
|
||||
return collection
|
||||
.find(query)
|
||||
.count()
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
const map = new Map<string, string>([
|
||||
['', '_'],
|
||||
|
||||
['А', 'A'],
|
||||
['а', 'a'],
|
||||
['Б', 'B'],
|
||||
['б', 'b'],
|
||||
['В', 'V'],
|
||||
['в', 'v'],
|
||||
['Г', 'G'],
|
||||
['г', 'g'],
|
||||
['Д', 'D'],
|
||||
['д', 'd'],
|
||||
['Е', 'E'],
|
||||
['е', 'e'],
|
||||
['Ё', 'E'],
|
||||
['ё', 'e'],
|
||||
['Ж', 'Zh'],
|
||||
['ж', 'zh'],
|
||||
['З', 'Z'],
|
||||
['з', 'z'],
|
||||
['И', 'I'],
|
||||
['и', 'i'],
|
||||
['Й', 'Y'],
|
||||
['й', 'y'],
|
||||
['К', 'K'],
|
||||
['к', 'k'],
|
||||
['Л', 'L'],
|
||||
['л', 'l'],
|
||||
['М', 'M'],
|
||||
['м', 'm'],
|
||||
['Н', 'N'],
|
||||
['н', 'n'],
|
||||
['О', 'O'],
|
||||
['о', 'o'],
|
||||
['П', 'P'],
|
||||
['п', 'p'],
|
||||
['Р', 'R'],
|
||||
['р', 'r'],
|
||||
['С', 'S'],
|
||||
['с', 's'],
|
||||
['Т', 'T'],
|
||||
['т', 't'],
|
||||
['У', 'U'],
|
||||
['у', 'u'],
|
||||
['Ф', 'F'],
|
||||
['ф', 'f'],
|
||||
['Х', 'Kh'],
|
||||
['х', 'kh'],
|
||||
['Ц', 'Ts'],
|
||||
['ц', 'ts'],
|
||||
['Ч', 'Ch'],
|
||||
['ч', 'ch'],
|
||||
['Ш', 'Sh'],
|
||||
['ш', 'sh'],
|
||||
['Щ', 'Sch'],
|
||||
['щ', 'sch'],
|
||||
['ь', ''],
|
||||
['Ы', 'Y'],
|
||||
['ы', 'y'],
|
||||
['ъ', ''],
|
||||
['Э', 'E'],
|
||||
['э', 'e'],
|
||||
['Ю', 'Yu'],
|
||||
['ю', 'yu'],
|
||||
['Я', 'Ya'],
|
||||
['я', 'ya'],
|
||||
])
|
||||
|
||||
export function translit(sample: string): string {
|
||||
return [...sample]
|
||||
.map(char => {
|
||||
if (map.has(char)) {
|
||||
return map.get(char)
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Request } from 'express'
|
||||
import { ensureArray } from './array'
|
||||
|
||||
export function dropTrailingSlash(path: string) {
|
||||
if (path === '/' || path === '') {
|
||||
return '/'
|
||||
}
|
||||
|
||||
const lastChar = path.charAt(path.length - 1)
|
||||
if (lastChar === '/') {
|
||||
return path.substr(0, path.length - 1)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
export function getPathWaterfall(path: string): string[] {
|
||||
const parts = path
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.reduce((acc, part, index, parts) => {
|
||||
const i = index + 1
|
||||
const item = `/${parts.slice(0, i).join('/')}`
|
||||
return [
|
||||
...acc,
|
||||
item
|
||||
]
|
||||
}, [])
|
||||
|
||||
return ['/', ...parts]
|
||||
}
|
||||
|
||||
export function readQueryInt(req: Request, key: string) {
|
||||
const int = (value: string | string[]) => parseInt(Array.isArray(value)
|
||||
? value[0]
|
||||
: value
|
||||
)
|
||||
|
||||
return int(req.query[key]) || 0
|
||||
}
|
||||
|
||||
export function readQueryString(req: Request, key: string) {
|
||||
return `${req.query[key]}`
|
||||
}
|
||||
|
||||
export function readQueryArray(req: Request, key: string) {
|
||||
return ensureArray(req.query[key])
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { config } from 'dotenv'
|
||||
import express from 'express'
|
||||
import { connect } from './db'
|
||||
import * as file from './controllers/file'
|
||||
import * as page from './controllers/page'
|
||||
import * as image from './controllers/image'
|
||||
import * as tag from './controllers/tag'
|
||||
|
||||
async function main() {
|
||||
const port = process.env.PORT || '3000'
|
||||
const mongoUri = process.env.MONGO_URI
|
||||
|
||||
try {
|
||||
await connect('hudozka', mongoUri)
|
||||
|
||||
console.log('connected')
|
||||
} catch (e) {
|
||||
console.error(e.message)
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const app = express()
|
||||
app.get('/files', file.getAll)
|
||||
app.get('/files/urls', file.getUrls)
|
||||
app.get('/files/:slug', file.getItem)
|
||||
|
||||
app.get('/pages', page.getAll)
|
||||
app.get('/pages/urls', page.getUrls)
|
||||
app.get('/pages/tags', page.getByTags)
|
||||
app.get('/page', page.getItem)
|
||||
|
||||
app.get('/image', image.getItem)
|
||||
|
||||
app.get('/tags', tag.getAll)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
config()
|
||||
main()
|
||||
@@ -0,0 +1,6 @@
|
||||
export type BreadcrumbPart = {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export type Breadcrumb = BreadcrumbPart[]
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./build",
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
## Usage
|
||||
|
||||
Sync Hudozka
|
||||
Sync tool
|
||||
```bash
|
||||
<ENV> python3 sync.py
|
||||
```
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -11,9 +11,13 @@ class DocumentToken(Token):
|
||||
self.joinable = False
|
||||
self.build = None
|
||||
|
||||
async def compile(self):
|
||||
async def get_data(self):
|
||||
data = parse_file(self.data)
|
||||
document = await self.build(data)
|
||||
return document
|
||||
|
||||
async def compile(self):
|
||||
document = await self.get_data()
|
||||
|
||||
return create_document(document)
|
||||
|
||||
File renamed without changes.
@@ -16,12 +16,15 @@ class Marker:
|
||||
def add_tree_middleware(self, callback):
|
||||
self.tree_middlewares.append(callback)
|
||||
|
||||
async def create_tree(self, text: str):
|
||||
def parse(self, text: str):
|
||||
tokens = text.split('\n')
|
||||
tokens = self.markup_tokens(tokens)
|
||||
tokens = join_tokens(tokens)
|
||||
tokens = merge_tokens(tokens)
|
||||
|
||||
return tokens
|
||||
|
||||
async def create_tree(self, tokens: list):
|
||||
compiled = await compile_tokens(tokens)
|
||||
tree = lxml.html.fromstring(compiled)
|
||||
return await self.process_tree(tree)
|
||||
@@ -102,17 +105,8 @@ async def compile_tokens(tokens: list):
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
def extract_files(text: str) -> [str]:
|
||||
html = markdown(text)
|
||||
post_html = lxml.html.fromstring(html)
|
||||
|
||||
images = post_html.cssselect('img')
|
||||
images = [img.get('src') for img in images]
|
||||
|
||||
return images, []
|
||||
|
||||
|
||||
def html_from_tree(tree):
|
||||
html = lxml.html.tostring(tree, encoding='unicode')
|
||||
|
||||
return html
|
||||
|
||||
|
||||
@@ -38,8 +38,12 @@ class Token:
|
||||
self.data = data
|
||||
self.joinable = True
|
||||
|
||||
async def compile(self):
|
||||
return str(self.data)
|
||||
async def get_data(self):
|
||||
return self.data
|
||||
|
||||
async def compile(self) -> str:
|
||||
data = await self.get_data()
|
||||
return str(data)
|
||||
|
||||
def merge(self, token):
|
||||
return self
|
||||
@@ -73,7 +77,8 @@ class TextToken(Token):
|
||||
return TextToken(data.strip())
|
||||
|
||||
async def compile(self):
|
||||
return markdown_to_html(self.data)
|
||||
data = await self.get_data()
|
||||
return markdown_to_html(data)
|
||||
|
||||
|
||||
class FileToken(Token):
|
||||
@@ -85,8 +90,11 @@ class FileToken(Token):
|
||||
super().__init__(name='file', data=data)
|
||||
self.joinable = False
|
||||
|
||||
async def get_data(self):
|
||||
return parse_file(self.data)
|
||||
|
||||
async def compile(self):
|
||||
data = parse_file(self.data)
|
||||
data = await self.get_data()
|
||||
url = data['file']
|
||||
text = data['caption'] if data['caption'] else url
|
||||
return f'<a href="{url}">{text}</a>'
|
||||
@@ -99,7 +107,7 @@ class ImageToken(Token):
|
||||
|
||||
def __init__(self, data) -> None:
|
||||
super().__init__(name='image', data=data)
|
||||
self.joinable = True
|
||||
self.joinable = False
|
||||
self.build = None
|
||||
|
||||
def merge(self, token: Token):
|
||||
@@ -107,9 +115,13 @@ class ImageToken(Token):
|
||||
merged_token.build = self.build
|
||||
return merged_token
|
||||
|
||||
async def get_data(self):
|
||||
data = parse_file(self.data)
|
||||
data = await self.build(data)
|
||||
return data
|
||||
|
||||
async def compile(self):
|
||||
img = self.parse_data()
|
||||
img = await self.build(img)
|
||||
img = await self.get_data()
|
||||
alt = img['alt']
|
||||
caption = markdown_to_html(img['caption'])
|
||||
src = img['src']
|
||||
@@ -124,9 +136,6 @@ class ImageToken(Token):
|
||||
'''
|
||||
return tpl.format(img).strip()
|
||||
|
||||
def parse_data(self):
|
||||
return parse_file(self.data)
|
||||
|
||||
|
||||
class ImageCollectionToken(ImageToken):
|
||||
def __init__(self, data) -> None:
|
||||
@@ -143,13 +152,16 @@ class ImageCollectionToken(ImageToken):
|
||||
merged_token.build = self.build
|
||||
return merged_token
|
||||
|
||||
async def compile(self):
|
||||
data = self.parse_data()
|
||||
async def get_data(self):
|
||||
items = [parse_file(x) for x in self.data]
|
||||
images = []
|
||||
for x in data:
|
||||
for x in items:
|
||||
img = await self.build(x)
|
||||
images.append(img)
|
||||
return images
|
||||
|
||||
async def compile(self):
|
||||
images = await self.get_data()
|
||||
images = [x['src'] for x in images]
|
||||
images = [f'<img src="{x}">' for x in images]
|
||||
images = '\n'.join(images)
|
||||
@@ -165,10 +177,6 @@ class ImageCollectionToken(ImageToken):
|
||||
</div>
|
||||
'''
|
||||
|
||||
def parse_data(self):
|
||||
return [parse_file(x) for x in self.data]
|
||||
|
||||
|
||||
class UrlToken(Token):
|
||||
@staticmethod
|
||||
def test(data: str):
|
||||
File renamed without changes.
@@ -0,0 +1,34 @@
|
||||
import lxml.html
|
||||
import re
|
||||
from markdown import markdown
|
||||
|
||||
from kazimir.figure import FigureExtension
|
||||
from kazimir.instagram import InstagramExtension
|
||||
from sync.data import request
|
||||
|
||||
def extract_files(text: str) -> [str]:
|
||||
html = markdown(text)
|
||||
post_html = lxml.html.fromstring(html)
|
||||
|
||||
images = post_html.cssselect('img')
|
||||
images = [img.get('src') for img in images]
|
||||
|
||||
return images, []
|
||||
|
||||
|
||||
def html_from_tree(tree):
|
||||
html = lxml.html.tostring(tree, encoding='unicode')
|
||||
|
||||
return html
|
||||
|
||||
|
||||
async def typo(text):
|
||||
from kazimir import artlebedev
|
||||
result = await artlebedev.typograf(text)
|
||||
if result == 'Размер текста ограничен 32 КБ':
|
||||
return text
|
||||
|
||||
return result
|
||||
# url = 'http://www.typograf.ru/webservice/'
|
||||
# params = dict(text=text)
|
||||
# return await request.post(url, params, {'chr': 'utf-8'})
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,12 +1,12 @@
|
||||
cssselect==0.9.2
|
||||
lxml==3.6.1
|
||||
Markdown==2.6.6
|
||||
Pillow==3.3.0
|
||||
Pillow==6.2.0
|
||||
pymongo==3.3.0
|
||||
PyPDF2==1.26.0
|
||||
python-frontmatter==0.3.1
|
||||
PyYAML==3.11
|
||||
requests==2.10.0
|
||||
PyYAML==5.1
|
||||
requests==2.20.0
|
||||
six==1.10.0
|
||||
smartcrop==0.1
|
||||
toml==0.9.2
|
||||
@@ -41,26 +41,8 @@ database_uri = value('database_uri') # env('MONGO_URI')
|
||||
|
||||
skip_unchanged = value('skip_unchanged')
|
||||
|
||||
person_uri = {
|
||||
'Н.В.Андреева': 'nv-andreeva',
|
||||
'И.Н.Втюрина': 'in-vturina',
|
||||
'О.Д.Гоголева': 'od-gogoleva',
|
||||
'В.А.Саржин': 'va-sarzhin',
|
||||
'Р.К.Тимашев': 'rk-timashev',
|
||||
'А.С.Тимашева': 'as-timasheva',
|
||||
}
|
||||
|
||||
upload_enabled = value('upload_enabled')
|
||||
upload_url_image = 'https://static.shlisselburg.org/upload/art/images/{}'
|
||||
upload_auth = (
|
||||
value('upload_auth')['login'],
|
||||
value('upload_auth')['password'],
|
||||
)
|
||||
|
||||
image_processing_enabled = value('image_processing')
|
||||
|
||||
album_html_img_class = 'hudozka-product'
|
||||
|
||||
image_ext = '.jpg'
|
||||
image_sizes = [
|
||||
('original', None, None),
|
||||
@@ -92,60 +74,33 @@ date_formats = [
|
||||
'%d.%m.%Y %H:%M:%S'
|
||||
]
|
||||
|
||||
date_formats_direct = [
|
||||
'%Y',
|
||||
'%m.%Y',
|
||||
'%d.%m.%Y',
|
||||
]
|
||||
|
||||
date_formats_reverse = [
|
||||
'%Y',
|
||||
'%Y.%m',
|
||||
'%Y.%m.%d'
|
||||
]
|
||||
|
||||
origin = value('origin')
|
||||
|
||||
collection_settings = 'settings'
|
||||
collection_images = 'images'
|
||||
collection_documents = 'documents'
|
||||
collection_schedules = 'schedules'
|
||||
collection_articles = 'articles'
|
||||
collection_collective = 'collective'
|
||||
collection_albums = 'albums'
|
||||
collection_pages = 'pages'
|
||||
|
||||
hash_salt_articles = '5'
|
||||
hash_salt_pages = '2'
|
||||
hash_salt_albums = '0'
|
||||
hash_salt_documents = '1'
|
||||
hash_salt_images = '1'
|
||||
hash_salt_person = '2'
|
||||
hash_salt_pages = '4'
|
||||
hash_salt_documents = '4'
|
||||
hash_salt_images = '5'
|
||||
|
||||
provider_name = value('provider')['name'] # env('SYNC_PROVIDER', 'fs')
|
||||
provider_root = value('provider')['root'] # env('SYNC_PROVIDER', 'fs')
|
||||
|
||||
f = abs_fn(provider_root)
|
||||
|
||||
dir_settings = './Settings'
|
||||
dir_documents = './Documents'
|
||||
dir_schedules = './Schedules'
|
||||
dir_articles = './Articles'
|
||||
dir_persons = './Collective'
|
||||
dir_albums = './Gallery'
|
||||
dir_pages = './Pages'
|
||||
dir_images = './Images'
|
||||
|
||||
document_url_template = 'https://static.shlisselburg.org/art/uploads/{file}'
|
||||
document_url_upload_template = 'https://static.shlisselburg.org/upload/art/uploads/{file}'
|
||||
document_url_preview_template = 'https://static.shlisselburg.org/art/images/{id}-{size}{ext}'
|
||||
|
||||
image_url_upload = 'https://static.shlisselburg.org/upload/art/images/'
|
||||
image_url_base = 'https://static.shlisselburg.org/art/images/'
|
||||
|
||||
person_image_url_template = 'https://static.shlisselburg.org/art/images/person-{id}-{size}{ext}'
|
||||
|
||||
yandex_disk_access_token = value('yandex_disk_access_token') # env('YANDEX_DISK_ACCESS_TOKEN')
|
||||
|
||||
update_enabled = value('update_enabled')
|
||||
delete_enabled = value('delete_enabled')
|
||||
auth_s3 = {
|
||||
'region_name': 'ams3',
|
||||
'endpoint_url': 'https://dir.ams3.digitaloceanspaces.com',
|
||||
'aws_access_key_id': value('s3')['client_id'],
|
||||
'aws_secret_access_key': value('s3')['client_secret'],
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import settings
|
||||
from sync.data.fs import FSProvider
|
||||
from sync.data.yandexdisk import YDProvider
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
async def run_check(run_interval=0):
|
||||
run_interval = 1
|
||||
|
||||
from yandexdisk import YDClient
|
||||
client = YDClient(settings.yandex_disk_access_token)
|
||||
|
||||
async def need_to_update():
|
||||
res = client.last_uploaded()
|
||||
items = res['items']
|
||||
paths = [i['path'] for i in items]
|
||||
|
||||
for i in paths:
|
||||
if i.startswith('disk:/Hudozka/Site'):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
while True:
|
||||
status = await need_to_update()
|
||||
if status:
|
||||
print('DO SYNC')
|
||||
# await run(0)
|
||||
|
||||
if run_interval == 0:
|
||||
break
|
||||
else:
|
||||
await asyncio.sleep(run_interval)
|
||||
|
||||
|
||||
def get_provider(provider_type, root):
|
||||
if provider_type == 'fs':
|
||||
return FSProvider(root)
|
||||
elif provider_type == 'yd':
|
||||
return YDProvider(root, settings.yandex_disk_access_token)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
interval = settings.interval
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(run_check(interval))
|
||||
loop.close()
|
||||
@@ -0,0 +1,75 @@
|
||||
import settings
|
||||
from sync.core import Sync
|
||||
from sync.data.fs import FSProvider
|
||||
from sync.data.yandexdisk import YDProvider
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from sync.models.Page import Page, PageController
|
||||
|
||||
|
||||
def init_logger(name: str, file: str):
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# create file handler which logs even debug messages
|
||||
fh = logging.FileHandler(file)
|
||||
fh.setLevel(logging.DEBUG)
|
||||
|
||||
# create console handler with a higher log level
|
||||
ch = logging.StreamHandler()
|
||||
# ch.setLevel(logging.ERROR)
|
||||
ch.setLevel(logging.DEBUG)
|
||||
|
||||
# create formatter and add it to the handlers
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
fh.setFormatter(formatter)
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
# add the handlers to the logger
|
||||
logger.addHandler(fh)
|
||||
logger.addHandler(ch)
|
||||
|
||||
|
||||
async def run_sync(sync: Sync, **kwargs):
|
||||
await sync.run(**kwargs)
|
||||
|
||||
|
||||
async def run(run_interval=0):
|
||||
provider = get_provider(settings.provider_name, settings.provider_root)
|
||||
controller = PageController(provider)
|
||||
|
||||
sync_pages = Sync(
|
||||
ctrl=controller,
|
||||
name='Page'
|
||||
)
|
||||
sync_pages.set_options(True, True, True)
|
||||
|
||||
while True:
|
||||
await asyncio.wait([
|
||||
run_sync(sync_pages),
|
||||
])
|
||||
|
||||
if run_interval == 0:
|
||||
break
|
||||
else:
|
||||
await asyncio.sleep(run_interval)
|
||||
|
||||
|
||||
def get_provider(provider_type, root):
|
||||
if provider_type == 'fs':
|
||||
return FSProvider(root)
|
||||
elif provider_type == 'yd':
|
||||
return YDProvider(root, settings.yandex_disk_access_token)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_logger(settings.name, 'hudozka.log')
|
||||
|
||||
interval = settings.interval
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(run(interval))
|
||||
loop.close()
|
||||
@@ -3,8 +3,6 @@ import re
|
||||
from datetime import datetime
|
||||
|
||||
import lxml.html
|
||||
from markdown import markdown
|
||||
|
||||
import kazimir
|
||||
import settings
|
||||
from kazimir.CSVToken import CSVToken
|
||||
@@ -47,12 +45,6 @@ def create_date(date_str, date_formats=None):
|
||||
return None
|
||||
|
||||
|
||||
# 2016 На крыльях бабочек
|
||||
# 2016.10 На крыльях бабочек
|
||||
# 2016.10.10 На крыльях бабочек
|
||||
folder_name_pattern = re.compile('([\d.]+)(.*)')
|
||||
|
||||
|
||||
async def untouched(items: [Model]) -> [Model]:
|
||||
if settings.skip_unchanged:
|
||||
return items
|
||||
@@ -75,21 +67,6 @@ async def untouched(items: [Model]) -> [Model]:
|
||||
# return filtered_documents
|
||||
|
||||
|
||||
def create_date_and_title_from_folder_name(folder_name, date_formats=None):
|
||||
m = folder_name_pattern.findall(folder_name)
|
||||
if not m:
|
||||
return None, None
|
||||
|
||||
date_str, title = m[0]
|
||||
title = os.path.splitext(title.strip())[0]
|
||||
date = create_date(date_str, date_formats)
|
||||
|
||||
if not date:
|
||||
return None, title
|
||||
|
||||
return date, title
|
||||
|
||||
|
||||
async def create_post(provider: Provider, folder: str, md: str, sizes):
|
||||
from kazimir.fix_links_quotes import fix_links_quotes
|
||||
|
||||
@@ -155,25 +132,22 @@ async def create_post(provider: Provider, folder: str, md: str, sizes):
|
||||
# m.add_token_factory(TokenFactory(FileToken))
|
||||
m.add_token_factory(TokenFactory(TextToken))
|
||||
m.add_tree_middleware(fix_links_quotes)
|
||||
html = await m.create_tree(md)
|
||||
|
||||
post = kazimir.html_from_tree(html)
|
||||
tokens = m.parse(md)
|
||||
tree = await m.create_tree(tokens)
|
||||
|
||||
post = kazimir.html_from_tree(tree)
|
||||
post = await kazimir.typo(post)
|
||||
|
||||
return post, images, documents
|
||||
encoded_tokens = []
|
||||
for t in tokens:
|
||||
data = await t.get_data()
|
||||
encoded_tokens.append({
|
||||
'token': t.name,
|
||||
'data': data,
|
||||
})
|
||||
|
||||
|
||||
def images_from_html(md):
|
||||
if not md:
|
||||
return []
|
||||
|
||||
post_html = lxml.html.fromstring(md)
|
||||
|
||||
images = []
|
||||
for img in post_html.cssselect('img'):
|
||||
src = img.get('src')
|
||||
images.append(src)
|
||||
return images
|
||||
return post, images, documents, encoded_tokens
|
||||
|
||||
|
||||
def title_from_html(md):
|
||||
@@ -181,11 +155,3 @@ def title_from_html(md):
|
||||
titles = html.cssselect('h1')
|
||||
if len(titles):
|
||||
return titles[0].text
|
||||
|
||||
|
||||
create_post_from_image_list = lambda images: markdown(
|
||||
'\n'.join(map(
|
||||
lambda i: ''.format(img=i),
|
||||
images
|
||||
))
|
||||
)
|
||||
@@ -1,19 +1,10 @@
|
||||
import logging
|
||||
|
||||
import settings
|
||||
from sync import untouched
|
||||
from sync.data import Provider
|
||||
|
||||
|
||||
def get_id(item):
|
||||
try:
|
||||
return item.id
|
||||
except AttributeError:
|
||||
return item['id']
|
||||
|
||||
|
||||
class Sync:
|
||||
def __init__(self, provider: Provider, model):
|
||||
def __init__(self, ctrl, name: str):
|
||||
super().__init__()
|
||||
|
||||
self.validate_urls = True
|
||||
@@ -23,21 +14,34 @@ class Sync:
|
||||
}
|
||||
self.__delete_query = lambda item: {'_id': item['_id']}
|
||||
|
||||
name = model.__name__
|
||||
self.logger = logging.getLogger('%s.%s' % (settings.name, name))
|
||||
|
||||
self.provider = provider
|
||||
self.model = model
|
||||
self.ctrl = ctrl
|
||||
self.filter_item_fn = lambda x: True
|
||||
|
||||
async def clean(self):
|
||||
pass
|
||||
self.skip_unchanged = True
|
||||
self.update_enabled = True
|
||||
self.delete_enabled = True
|
||||
|
||||
def set_options(self, su: bool, u: bool, d: bool):
|
||||
self.skip_unchanged = su
|
||||
self.update_enabled = u
|
||||
self.delete_enabled = d
|
||||
return self
|
||||
|
||||
def set_item_filter(self, fn):
|
||||
self.filter_item_fn = fn
|
||||
return self
|
||||
|
||||
async def build(self, item):
|
||||
args = self._get_build_args()
|
||||
await item.build(**args)
|
||||
return item
|
||||
|
||||
async def update(self, item):
|
||||
args = self._get_build_args()
|
||||
|
||||
await item.build(**args)
|
||||
await item.upload()
|
||||
await item.save()
|
||||
return item
|
||||
|
||||
def _get_build_args(self):
|
||||
return {
|
||||
@@ -72,32 +76,43 @@ class Sync:
|
||||
"""
|
||||
self.logger.info('Checking for update')
|
||||
|
||||
items = await self.model.scan(self.provider)
|
||||
items = await self.ctrl.get_items()
|
||||
self.logger.info('Found {} Item(s)'.format(len(items)))
|
||||
all_items = items
|
||||
|
||||
if self.validate_urls:
|
||||
self.check_urls_uniqueness(items)
|
||||
self.check_urls_uniqueness(all_items)
|
||||
|
||||
# SKIP UNTOUCHED DOCUMENTS
|
||||
changed_items = await untouched(items)
|
||||
self.logger.info('Changed {} Items(s)'.format(len(changed_items)))
|
||||
if self.skip_unchanged:
|
||||
items = await untouched(all_items)
|
||||
|
||||
# Optionally trim list of models by custom filter
|
||||
items = [
|
||||
x
|
||||
for x in items
|
||||
if self.filter_item_fn(x)
|
||||
]
|
||||
|
||||
self.logger.info(f'Ready to process {len(items)} item(s)')
|
||||
|
||||
# BUILDING
|
||||
for item in items:
|
||||
self.logger.info(f'Build Item {item}')
|
||||
await self.build(item)
|
||||
|
||||
# UPDATING
|
||||
if settings.update_enabled:
|
||||
for item in changed_items:
|
||||
if self.update_enabled:
|
||||
for item in items:
|
||||
await self.update(item)
|
||||
self.logger.info('Updated Item {}'.format(item))
|
||||
|
||||
# DELETING
|
||||
if settings.delete_enabled:
|
||||
obsolete_items = await self.model.find(self.__items_to_delete_query(items))
|
||||
obsolete_items = list(obsolete_items)
|
||||
|
||||
if self.delete_enabled:
|
||||
obsolete_items = await self.ctrl.find(self.__items_to_delete_query(all_items))
|
||||
for item in obsolete_items:
|
||||
self.logger.info('Deleting Item {}'.format(item['id']))
|
||||
await self.model.delete(self.__delete_query(item))
|
||||
|
||||
await self.clean()
|
||||
await self.ctrl.delete_item(self.__delete_query(item))
|
||||
|
||||
def check_urls_uniqueness(self, items):
|
||||
urls = [x.url for x in items]
|
||||
@@ -106,6 +121,3 @@ class Sync:
|
||||
if len(urls) != len(unique_urls):
|
||||
self.logger.error('URLs is not unique')
|
||||
raise Exception('URLs is not unique')
|
||||
|
||||
async def upload(self):
|
||||
pass
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,40 @@
|
||||
import aiohttp
|
||||
from boto3 import session
|
||||
from mimetypes import MimeTypes
|
||||
import logging
|
||||
import settings
|
||||
|
||||
logger = logging.getLogger(settings.name + '.S3')
|
||||
|
||||
|
||||
async def s3_put(obj, path):
|
||||
"""
|
||||
:param obj: uploads/file.pdf
|
||||
:param path: to/to/local/file.pdf
|
||||
:return:
|
||||
"""
|
||||
logger.info('Put {}'.format(obj))
|
||||
|
||||
s = session.Session()
|
||||
client = s.client('s3', **settings.auth_s3)
|
||||
|
||||
mime = MimeTypes()
|
||||
mime_type, enc = mime.guess_type(path)
|
||||
|
||||
# Upload a file to your Space
|
||||
client.upload_file(path, 'hudozka', obj, ExtraArgs={
|
||||
'ACL': 'public-read',
|
||||
'ContentType': mime_type,
|
||||
})
|
||||
|
||||
|
||||
async def get(url):
|
||||
async with aiohttp.ClientSession as aio_session:
|
||||
res = aio_session.get(url)
|
||||
return await res.text()
|
||||
|
||||
|
||||
async def post(url, data, params={}, headers={}):
|
||||
async with aiohttp.ClientSession() as aio_session:
|
||||
async with aio_session.post(url, data=data, params=params, headers=headers) as resp:
|
||||
return await resp.text()
|
||||
File renamed without changes.
@@ -3,7 +3,7 @@ from tempfile import mkstemp
|
||||
|
||||
import settings
|
||||
from db import collection
|
||||
from sync.data import scan_subdirs, request
|
||||
from sync.data import request
|
||||
from sync.models import Model
|
||||
from sync.models.DocumentPreviewImage import DocumentPreviewImage
|
||||
from utils.hash import hash_str, md5
|
||||
@@ -25,12 +25,6 @@ class Document(Model):
|
||||
async def delete(query):
|
||||
return store.find_one_and_delete(query)
|
||||
|
||||
@staticmethod
|
||||
async def scan(provider):
|
||||
scan_dir = settings.dir_documents
|
||||
paths = scan_subdirs(provider, '.pdf', scan_dir)
|
||||
return [Document.read_path(provider, path) for path in paths]
|
||||
|
||||
@staticmethod
|
||||
def read_path(provider, path):
|
||||
name = os.path.basename(path)
|
||||
@@ -68,7 +62,7 @@ class Document(Model):
|
||||
self.__hash_keys = ['id', 'url']
|
||||
|
||||
self.__id_template: str = '{category}-{file}'
|
||||
self.__url_template: str = settings.document_url_template
|
||||
self.__url_template: str = 'https://static.shlisselburg.org/art/uploads/{file}'
|
||||
self.__url_preview_template: str = settings.document_url_preview_template
|
||||
|
||||
self.title = None
|
||||
@@ -123,9 +117,8 @@ class Document(Model):
|
||||
async def upload(self):
|
||||
filepath = self.provider.get_local(self.file)
|
||||
name = os.path.basename(self.url)
|
||||
url = settings.document_url_upload_template.format(file=name)
|
||||
|
||||
await request.upload(url, filepath)
|
||||
await request.s3_put(f'uploads/{name}', filepath)
|
||||
|
||||
def bake(self):
|
||||
return {
|
||||
@@ -182,14 +175,21 @@ async def get_pdf_title(provider, file):
|
||||
from PyPDF2.generic import TextStringObject
|
||||
from PyPDF2.generic import IndirectObject
|
||||
|
||||
def t(value):
|
||||
value = str(value)
|
||||
value = value.replace('\u0000', '')
|
||||
if value == 'None':
|
||||
return None
|
||||
return value
|
||||
|
||||
pdf = PdfFileReader(provider.read(file))
|
||||
info = pdf.getDocumentInfo()
|
||||
|
||||
if info:
|
||||
if type(info.title) == TextStringObject:
|
||||
return str(info.title)
|
||||
return t(info.title)
|
||||
|
||||
if type(info.title_raw) == IndirectObject:
|
||||
o = pdf.getObject(info.title_raw)
|
||||
return str(o)
|
||||
return t(info.title)
|
||||
return None
|
||||
File renamed without changes.
@@ -7,7 +7,7 @@ from typing import Optional, List
|
||||
|
||||
import settings
|
||||
from db import collection
|
||||
from sync.data import request, list_images
|
||||
from sync.data import request
|
||||
from sync.models import Model
|
||||
from utils.hash import md5, hash_str
|
||||
from utils.image import image_magick_resize
|
||||
@@ -18,10 +18,6 @@ logger = logging.getLogger(settings.name + '.Image')
|
||||
store = collection(settings.collection_images)
|
||||
|
||||
|
||||
def get_upload_url(url):
|
||||
return os.path.join(settings.image_url_upload, os.path.basename(url))
|
||||
|
||||
|
||||
def default_url_factory(file, size, ext):
|
||||
url = settings.image_url_base + '{}-{}{}'
|
||||
return url.format(md5(file), size, ext.lower())
|
||||
@@ -58,13 +54,6 @@ class Image(Model):
|
||||
|
||||
return img
|
||||
|
||||
@staticmethod
|
||||
async def scan(provider):
|
||||
scan_path = settings.dir_images
|
||||
documents = list_images(provider, scan_path)
|
||||
|
||||
return [Image.read_path(provider, i) for i in documents]
|
||||
|
||||
@staticmethod
|
||||
def read_path(provider, path):
|
||||
return Image(
|
||||
@@ -115,11 +104,10 @@ class Image(Model):
|
||||
async def upload(self):
|
||||
if self.data:
|
||||
async def fn(i):
|
||||
url = get_upload_url(i['url'])
|
||||
file = i['file']
|
||||
filename = os.path.basename(file)
|
||||
|
||||
logger.info('Uploading to {}'.format(url))
|
||||
await request.upload(url, file)
|
||||
await request.s3_put(f'images/{filename}', file)
|
||||
|
||||
await asyncio.wait([fn(i) for i in self.data.values()])
|
||||
|
||||
@@ -161,10 +149,10 @@ class Image(Model):
|
||||
def sync_image(record):
|
||||
from db import db
|
||||
|
||||
q = {'hash': record['hash']}
|
||||
q = {'file': record['file']}
|
||||
try:
|
||||
db().images.update_one(q, {'$set': record}, upsert=True)
|
||||
i = db().images.find_one({'hash': record['hash']})
|
||||
i = db().images.find_one({'file': record['file']})
|
||||
return i
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -187,6 +175,11 @@ async def create_image(file: str, sizes: [()], url_fn, output_dir: str) -> Optio
|
||||
_, ext = os.path.splitext(file)
|
||||
images = []
|
||||
|
||||
if not settings.image_processing_enabled:
|
||||
logger.warning(
|
||||
f'Image processing disabled {file}. Size of image will be set to original'
|
||||
)
|
||||
|
||||
async def fn(size):
|
||||
size_name, width, height = size
|
||||
|
||||
@@ -194,10 +187,12 @@ async def create_image(file: str, sizes: [()], url_fn, output_dir: str) -> Optio
|
||||
image_filename = os.path.basename(image_url)
|
||||
local_image_path = os.path.join(output_dir, image_filename)
|
||||
|
||||
# if settings.image_processing_enabled:
|
||||
if settings.image_processing_enabled:
|
||||
image = await process_image(file, local_image_path, size)
|
||||
else:
|
||||
image = await read_image(file)
|
||||
if not image:
|
||||
logger.warning('Failed to process image {}'.format(file))
|
||||
logger.warning(f'Failed to process image {file}')
|
||||
return None
|
||||
width, height = image.size
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import os
|
||||
|
||||
import logging
|
||||
|
||||
from lxml import etree
|
||||
import lxml.html
|
||||
|
||||
import kazimir
|
||||
import settings
|
||||
from db import collection
|
||||
from sync.data import request
|
||||
from sync.models import Model
|
||||
|
||||
from sync import create_date, images_from_html, title_from_html, create_post
|
||||
from sync import create_date, title_from_html, create_post
|
||||
from sync.models.Image import Image
|
||||
from utils.hash import hash_str
|
||||
from utils.io import read_yaml_md, parse_yaml_front_matter
|
||||
from utils.front_matter import parse_yaml_front_matter
|
||||
from utils.text.transform import url_encode_text
|
||||
|
||||
logger = logging.getLogger(settings.name + '.Page')
|
||||
@@ -30,62 +26,77 @@ def find(store, item_id: str):
|
||||
return None
|
||||
|
||||
|
||||
class Page(Model):
|
||||
@staticmethod
|
||||
async def find(query):
|
||||
class PageController:
|
||||
def __init__(self, provider):
|
||||
self.provider = provider
|
||||
|
||||
async def find(self, query):
|
||||
return store.find(query)
|
||||
|
||||
@staticmethod
|
||||
async def delete(query):
|
||||
async def delete_item(self, query):
|
||||
return store.find_one_and_delete(query)
|
||||
|
||||
@staticmethod
|
||||
async def scan(provider):
|
||||
async def get_items(self):
|
||||
scan_path = settings.dir_pages
|
||||
documents = [i for i in provider.scan(scan_path) if provider.is_dir(i)]
|
||||
items = []
|
||||
for path in self.provider.scan(scan_path):
|
||||
if not self.provider.is_dir(path):
|
||||
continue
|
||||
item = await self.create_item(path)
|
||||
if not item:
|
||||
continue
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
documents = [Page.read(provider, i) for i in documents]
|
||||
documents = [i for i in documents if i]
|
||||
|
||||
return documents
|
||||
|
||||
@staticmethod
|
||||
def read(provider, path):
|
||||
async def create_item(self, path: str):
|
||||
"""
|
||||
:param provider:
|
||||
:param path: path to page manifest file. For example: /Pages/<Some_Page>/<Some_Page>.md
|
||||
:return:
|
||||
"""
|
||||
dir_name = os.path.basename(path)
|
||||
|
||||
manifest_file = lambda ext: os.path.join(path, dir_name + ext)
|
||||
manifest_path = manifest_file('.md')
|
||||
|
||||
if not provider.exists(manifest_path):
|
||||
manifest_path = os.path.join(path, f'{dir_name}.md')
|
||||
if not self.provider.exists(manifest_path):
|
||||
logger.warning(f'Manifest not found on path {manifest_path}')
|
||||
return None
|
||||
|
||||
params = {
|
||||
'file': os.path.basename(manifest_path),
|
||||
'folder': os.path.dirname(manifest_path),
|
||||
'title': os.path.basename(manifest_path),
|
||||
}
|
||||
|
||||
manifest = get_manifest(provider, manifest_path)
|
||||
params = {
|
||||
**params,
|
||||
**manifest,
|
||||
}
|
||||
|
||||
if 'url' not in params:
|
||||
logger.warning('URL not specified for Page {}'.format(manifest_path))
|
||||
manifest = await self.get_manifest(manifest_path)
|
||||
if not manifest:
|
||||
logger.warning(f'Cannot define manifest. Skip {manifest_path}')
|
||||
return None
|
||||
|
||||
return Page(
|
||||
provider,
|
||||
self.provider,
|
||||
manifest_path,
|
||||
params=params,
|
||||
params=manifest,
|
||||
)
|
||||
|
||||
async def get_manifest(self, path: str):
|
||||
data = self.provider.read(path).read().decode('utf-8')
|
||||
manifest = parse_yaml_front_matter(data)
|
||||
|
||||
if 'url' not in manifest:
|
||||
logger.warning(f'URL not specified for Page {path}')
|
||||
return None
|
||||
|
||||
if 'date' in manifest:
|
||||
manifest['date'] = create_date(
|
||||
manifest['date'], settings.date_formats)
|
||||
|
||||
if 'until' in manifest:
|
||||
manifest['until'] = create_date(
|
||||
manifest['until'], settings.date_formats)
|
||||
|
||||
if 'id' in manifest:
|
||||
manifest['id'] = str(manifest['id'])
|
||||
|
||||
return {
|
||||
**manifest,
|
||||
'file': os.path.basename(path),
|
||||
'folder': os.path.dirname(path),
|
||||
}
|
||||
|
||||
|
||||
class Page(Model):
|
||||
def __init__(self, provider, file, params=None):
|
||||
self.__hash_salt = settings.hash_salt_pages
|
||||
self.__hash_keys = ['id', 'url']
|
||||
@@ -94,6 +105,7 @@ class Page(Model):
|
||||
self.title = None
|
||||
self.data = None
|
||||
self.preview = None
|
||||
self.tokens = None
|
||||
|
||||
self.__id_template: str = '{category}-{file}'
|
||||
self.__url_template: str = settings.document_url_template
|
||||
@@ -124,11 +136,12 @@ class Page(Model):
|
||||
sizes = kwargs['sizes']
|
||||
|
||||
markdown_post = self.get_param('content')
|
||||
post, images, documents = await create_post(self.provider, self.get_param('folder'), markdown_post, sizes)
|
||||
post, images, documents, tokens = await create_post(self.provider, self.get_param('folder'), markdown_post, sizes)
|
||||
self.title = title_from_html(post)
|
||||
self.data = post
|
||||
self.images = images
|
||||
self.documents = documents
|
||||
self.tokens = tokens
|
||||
|
||||
if self.has_param('preview'):
|
||||
preview = self.get_param('preview')
|
||||
@@ -149,6 +162,7 @@ class Page(Model):
|
||||
'images': images,
|
||||
'documents': documents,
|
||||
'title': self.title,
|
||||
'tokens': self.tokens,
|
||||
'preview': self.preview.ref if self.preview else None,
|
||||
}
|
||||
|
||||
@@ -168,9 +182,6 @@ class Page(Model):
|
||||
files = []
|
||||
files += self.get_param('images')
|
||||
files += self.get_param('documents')
|
||||
if self.has_param('contentFile'):
|
||||
files += [self.get_param('contentFile')]
|
||||
|
||||
folder = self.get_param('folder')
|
||||
files = [os.path.join(folder, x) for x in files]
|
||||
files = sorted([self.file] + files)
|
||||
@@ -180,23 +191,3 @@ class Page(Model):
|
||||
|
||||
def __str__(self):
|
||||
return '<Page file={} id={}>'.format(self.file, self.id)
|
||||
|
||||
|
||||
def get_manifest(provider, path):
|
||||
data = provider.read(path).read().decode('utf-8')
|
||||
manifest = parse_yaml_front_matter(data)
|
||||
|
||||
if 'date' in manifest:
|
||||
manifest['date'] = create_date(manifest['date'], settings.date_formats)
|
||||
|
||||
if 'until' in manifest:
|
||||
manifest['until'] = create_date(manifest['until'], settings.date_formats)
|
||||
|
||||
if 'id' in manifest:
|
||||
manifest['id'] = str(manifest['id'])
|
||||
|
||||
if 'contentFile' in manifest:
|
||||
dirname = os.path.dirname(path)
|
||||
manifest['contentHtml'] = provider.read(os.path.join(dirname, manifest['contentFile'])).read().decode('utf-8')
|
||||
|
||||
return manifest
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,16 +1,9 @@
|
||||
import re
|
||||
import yaml
|
||||
|
||||
from kazimir import markdown_to_html
|
||||
|
||||
FM_BOUNDARY = re.compile(r'^-{3,}', re.MULTILINE)
|
||||
|
||||
|
||||
def read_yaml_md(data):
|
||||
yaml_front_matter, content = split_yaml_front_matter(data)
|
||||
return yaml_front_matter, markdown_to_html(content)
|
||||
|
||||
|
||||
def parse_yaml_front_matter(data: str):
|
||||
fm, content = split_yaml_front_matter(data)
|
||||
fm = fm if fm else {}
|
||||
@@ -20,7 +13,7 @@ def parse_yaml_front_matter(data: str):
|
||||
}
|
||||
|
||||
|
||||
def split_yaml_front_matter(text, **defaults):
|
||||
def split_yaml_front_matter(text: str):
|
||||
"""
|
||||
Parse text with YAML frontmatter, return metadata and content.
|
||||
Pass in optional metadata defaults as keyword args.
|
||||
@@ -29,19 +22,15 @@ def split_yaml_front_matter(text, **defaults):
|
||||
and original text content.
|
||||
"""
|
||||
|
||||
# metadata starts with defaults
|
||||
metadata = defaults.copy()
|
||||
|
||||
# split on the first two triple-dashes
|
||||
try:
|
||||
_, fm, content = FM_BOUNDARY.split(text, 2)
|
||||
except ValueError:
|
||||
# if we can't split, bail
|
||||
return metadata, text
|
||||
return None, None
|
||||
|
||||
# parse yaml, now that we have frontmatter
|
||||
fm = yaml.safe_load(fm)
|
||||
if isinstance(fm, dict):
|
||||
metadata.update(fm)
|
||||
|
||||
return metadata, content.strip()
|
||||
return fm, content.strip()
|
||||
return None, None
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -16,6 +16,30 @@ class YDClient:
|
||||
Authorization='OAuth %s' % self.access_token,
|
||||
)
|
||||
|
||||
async def get_flat(self, limit=20):
|
||||
"""
|
||||
https://cloud-api.yandex.net/v1/disk/resources/files?
|
||||
[ limit=<количество файлов в списке>]
|
||||
[& media_type=<тип запрашиваемых файлов>]
|
||||
[& offset=<смещение относительно начала списка>]
|
||||
[& fields=<нужные ключи ответа>]
|
||||
[& preview_size=<размер превью>]
|
||||
[& preview_crop=<признак обрезки превью>]
|
||||
:return:
|
||||
"""
|
||||
|
||||
url = '{0}/resources/files'.format(self.base_url)
|
||||
payload = dict(
|
||||
limit=limit,
|
||||
offset=0,
|
||||
)
|
||||
r = requests.get(url, headers=self.base_headers, params=payload)
|
||||
self.check_code(r)
|
||||
|
||||
json_dict = r.json()
|
||||
# return Directory(**json_dict)
|
||||
return json_dict
|
||||
|
||||
def get_disk_metadata(self):
|
||||
"""
|
||||
:return: disk metadata
|
||||
@@ -31,6 +55,26 @@ class YDClient:
|
||||
system_folders=disk['system_folders']
|
||||
)
|
||||
|
||||
def last_uploaded(self):
|
||||
"""
|
||||
https://cloud-api.yandex.net/v1/disk/resources/last-uploaded?
|
||||
[ limit=<количество файлов в списке>]
|
||||
[& media_type=<тип запрашиваемых файлов>]
|
||||
[& fields=<нужные ключи ответа>]
|
||||
[& preview_size=<размер превью>]
|
||||
[& preview_crop=<признак обрезки превью>]
|
||||
"""
|
||||
url = '{0}/resources/last-uploaded'.format(self.base_url)
|
||||
payload = dict(
|
||||
limit=100,
|
||||
)
|
||||
r = requests.get(url, headers=self.base_headers, params=payload)
|
||||
self.check_code(r)
|
||||
|
||||
json_dict = r.json()
|
||||
# return Directory(**json_dict)
|
||||
return json_dict
|
||||
|
||||
def get_content(self, path):
|
||||
"""
|
||||
:param path: path to folder
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
@@ -0,0 +1,14 @@
|
||||
const withCss = require('@zeit/next-css')
|
||||
const withSass = require('@zeit/next-sass')
|
||||
|
||||
module.exports = withCss(withSass({
|
||||
exportTrailingSlash: true,
|
||||
env: {
|
||||
APP_ARTICLES_PAGE_SIZE: 24,
|
||||
APP_CARD_DEFAULT_IMAGE: 'https://art.shlisselburg.org/static/img/main.jpg',
|
||||
},
|
||||
webpack(config) {
|
||||
config.resolve.modules.push(__dirname)
|
||||
return config;
|
||||
},
|
||||
}))
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
charset utf-8;
|
||||
|
||||
server_name art.shlisselburg.org;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/art.shlisselburg.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/art.shlisselburg.org/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers AES256+EECDH:AES256+EDH:!aNULL;
|
||||
|
||||
location / {
|
||||
proxy_pass http://hudozka;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/art.shburg.error.log;
|
||||
access_log /var/log/nginx/art.shburg.access.log;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name art.shlisselburg.org art.shburg.org;
|
||||
|
||||
location ^~ /.well-known/acme-challenge {
|
||||
root /home/hoster/www/art.shburg.org/public;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://art.shlisselburg.org$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream hudozka {
|
||||
server localhost:1800;
|
||||
}
|
||||
Generated
+7931
-13241
File diff suppressed because it is too large.
Load diff
+30
-79
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "Hudozka",
|
||||
"name": "hudozka",
|
||||
"private": true,
|
||||
"version": "4.0.0",
|
||||
"version": "6.10.0",
|
||||
"description": "",
|
||||
"main": "src/bin/www.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"build-app": "webpack --env.NODE_ENV=production --env.TARGET=app",
|
||||
"out": "babel src/ -d out/",
|
||||
"gulp": "gulp",
|
||||
"test": "mocha --compilers js:babel-register",
|
||||
"coverage": "istanbul cover mocha -- --compilers js:babel-register"
|
||||
"start": "next dev",
|
||||
"build": "npm run next-build && npm run export && npm run sitemap && npm run feed",
|
||||
"next-build": "next build",
|
||||
"export": "next export -o dist",
|
||||
"sitemap": "node scripts/sitemap.js",
|
||||
"feed": "node scripts/feed.js",
|
||||
"test": "mocha --compilers js:babel-register"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -19,81 +19,32 @@
|
||||
"author": "Roman Timashev",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"async-file": "^2.0.2",
|
||||
"classnames": "^2.2.5",
|
||||
"co": "^4.4.0",
|
||||
"ejson": "^2.1.2",
|
||||
"handlebars": "^4.0.5",
|
||||
"instagram-node": "^0.5.1",
|
||||
"koa": "^2.0.0-alpha.3",
|
||||
"koa-bodyparser": "^3.0.0",
|
||||
"koa-compose": "^3.1.0",
|
||||
"koa-conditional-get": "^1.0.2",
|
||||
"koa-convert": "^1.2.0",
|
||||
"koa-cookie": "^1.0.0",
|
||||
"koa-etag": "^2.0.0",
|
||||
"koa-helmet": "^1.0.0",
|
||||
"koa-logger": "^2.0.0",
|
||||
"koa-mount": "^2.0.0",
|
||||
"koa-route": "^3.1.0",
|
||||
"koa-session": "^4.0.0",
|
||||
"koa-static": "^3.0.0",
|
||||
"markdown": "^0.5.0",
|
||||
"moment": "^2.18.1",
|
||||
"mongodb": "^2.1.14",
|
||||
"mz": "^2.4.0",
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-redux": "^4.4.8",
|
||||
"react-router": "^4.2.0",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"request-promise": "^2.0.1",
|
||||
"axios": "^0.19.0",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.10.0",
|
||||
"lodash": "^4.17.15",
|
||||
"next": "^9.2.2",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"sitemap": "^1.5.0",
|
||||
"thunkify": "^2.1.2"
|
||||
"use-media": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0-beta.47",
|
||||
"@babel/core": "^7.0.0-beta.47",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0-beta.47",
|
||||
"@babel/polyfill": "^7.0.0-beta.47",
|
||||
"@babel/preset-env": "^7.0.0-beta.47",
|
||||
"@babel/register": "^7.0.0-beta.47",
|
||||
"autoprefixer": "^6.3.6",
|
||||
"babel-loader": "^8.0.0-beta.3",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.23.0",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"css-loader": "^0.28.9",
|
||||
"cssnano": "^3.4.0",
|
||||
"eslint": "^3.9.1",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/node": "^12.12.12",
|
||||
"@types/react": "^16.9.12",
|
||||
"@zeit/next-css": "^1.0.1",
|
||||
"@zeit/next-sass": "^1.0.1",
|
||||
"eslint": "^4.18.2",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-plugin-promise": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eslint-plugin-promise": "^3.8.0",
|
||||
"eslint-plugin-react": "^7.16.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-concat": "^2.4.1",
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-imagemin": "~2.4.0",
|
||||
"gulp-postcss": "^6.0.1",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-sass": "^4.0.1",
|
||||
"gulp-uglify": "~1.5.0",
|
||||
"html-minify-loader": "~1.1.0",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"istanbul": "~0.4.2",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^4.1.0",
|
||||
"mocha": "~2.4.5",
|
||||
"raw-loader": "~0.5.1",
|
||||
"feed": "^4.0.0",
|
||||
"mocha": "^6.2.2",
|
||||
"node-sass": "^4.13.0",
|
||||
"should": "^8.2.2",
|
||||
"style-loader": "^0.20.2",
|
||||
"vinyl-named": "~1.1.0",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-merge": "^4.1.1"
|
||||
"typescript": "^3.7.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { tail } from 'lodash'
|
||||
import { Page } from 'src/components/Page'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { MetaBuilder } from 'src/lib/meta'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
import { NextPage } from 'next'
|
||||
import { IBreadcumbsPart, IMeta, IPage, ITag } from 'src/types'
|
||||
|
||||
function array<T>(value: T | T[]) {
|
||||
return Array.isArray(value)
|
||||
? value
|
||||
: [value]
|
||||
}
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
tags: ITag[]
|
||||
date: string
|
||||
content: string
|
||||
breadcrumb: IBreadcumbsPart[]
|
||||
meta?: IMeta
|
||||
}
|
||||
|
||||
const Index: NextPage<Props> = props => (
|
||||
<App
|
||||
contentStyle={{
|
||||
marginTop: 'var(--size-l)',
|
||||
marginBottom: 'var(--size-xl)',
|
||||
}}
|
||||
breadcrumbs={props.breadcrumb}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
{!props.meta ? null : (
|
||||
<Meta meta={props.meta} />
|
||||
)}
|
||||
</Head>
|
||||
|
||||
<Page
|
||||
tags={props.tags}
|
||||
date={props.date ? new Date(props.date) : null}
|
||||
>
|
||||
{props.content}
|
||||
</Page>
|
||||
</App>
|
||||
)
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
let slug = null
|
||||
if (ctx.query) {
|
||||
slug = '/' + array(ctx.query.slug).join('/')
|
||||
} else {
|
||||
slug = '/' + array(ctx.params.slug).join('/')
|
||||
}
|
||||
const page = await requestGet<IPage>(createApiUrl(`/api/page?page=${slug}`), null)
|
||||
if (!page) {
|
||||
throw new Error(`Not found: ${slug}`)
|
||||
}
|
||||
const description = page.description ?? undefined
|
||||
const breadcrumbSize = page?.breadcrumb?.length ?? 0
|
||||
const breadcrumb = breadcrumbSize < 2 ? null : page.breadcrumb
|
||||
const meta = (new MetaBuilder())
|
||||
.setImage(page.preview)
|
||||
.setTitle(page.title)
|
||||
.setDescription(description)
|
||||
.build()
|
||||
|
||||
return {
|
||||
props: {
|
||||
content: page.data,
|
||||
title: page.title,
|
||||
tags: page.tags,
|
||||
date: page.date,
|
||||
meta,
|
||||
breadcrumb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const unstable_getStaticPaths = async () => {
|
||||
const urls = await requestGet<IResponseItems<string>>(createApiUrl(`/api/pages/urls`), null)
|
||||
if (!urls) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
paths: urls.items
|
||||
.map(path => {
|
||||
const slug = tail(path.split('/'))
|
||||
|
||||
return {
|
||||
params: {
|
||||
slug,
|
||||
},
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export default Index
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'src/style.css'
|
||||
import 'src/style/content.css'
|
||||
import 'src/style/article.css'
|
||||
import 'src/style/marker/marker-table.css'
|
||||
import 'src/style/kazimir/image.css'
|
||||
import 'src/style/kazimir/video.css'
|
||||
import 'src/style/style.scss'
|
||||
|
||||
import App from 'next/app'
|
||||
import Head from 'next/head'
|
||||
import { YMetrika } from 'src/components/YMetrika'
|
||||
import { GAnalytics } from 'src/components/GAnalytics'
|
||||
|
||||
export default class MyApp extends App {
|
||||
render() {
|
||||
const { Component, pageProps } = this.props
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, maximum-scale=1.0" />
|
||||
<meta name="referrer" content="unsafe-url" />
|
||||
|
||||
<meta name="google-site-verification" content="8BxOTw-Q02DS2KAdcGcnIMwiPZ58DVB18XpOfRibCcg" />
|
||||
<meta name="google-site-verification" content="A-TEemUw4hHpULUDCd6xhxfHnyRbgKtss90JGKXnSpw" />
|
||||
<meta name='yandex-verification' content='643c7dac144b9dac' />
|
||||
<meta name="msvalidate.01" content="E878301FE3E3F12D233A6B1156889601" />
|
||||
|
||||
<link rel="icon" type="image/png" href="/static/graphics/favicon-32.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/static/graphics/favicon-57.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/static/graphics/favicon-72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/static/graphics/favicon-114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/static/graphics/favicon-144.png" />
|
||||
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Шлиссельбургская Детская Художественная Школа"
|
||||
href="/feed.xml"
|
||||
/>
|
||||
|
||||
<link href='/static/shadow-font/style.css' rel='stylesheet' />
|
||||
|
||||
{/* Opa */}
|
||||
<script src="/static/opa.js" />
|
||||
<link rel="stylesheet" href="/static/opa.css" />
|
||||
|
||||
{/* Likely */}
|
||||
<script src='/static/likely.js' />
|
||||
<link href='/static/likely.css' rel='stylesheet' />
|
||||
|
||||
{/* Instagram Embed */}
|
||||
<script async defer src="//platform.instagram.com/en_US/embeds.js" />
|
||||
|
||||
<YMetrika account={24027460} />
|
||||
<GAnalytics account={'UA-60209057-1'} />
|
||||
</Head>
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NextPage } from 'next'
|
||||
import { Wrapper } from 'src/components/Wrapper'
|
||||
import { NotFound } from 'src/components/NotFound'
|
||||
import { Copyright } from 'src/components/Copyright'
|
||||
import { Block } from 'src/components/Block'
|
||||
import { Spacer } from 'src/components/Spacer'
|
||||
|
||||
const Page: NextPage = () => (
|
||||
<Wrapper
|
||||
header={null}
|
||||
footer={(
|
||||
<Block as={'footer'} direction={'horizontal'}>
|
||||
<Spacer />
|
||||
<Copyright />
|
||||
<Spacer />
|
||||
</Block>
|
||||
)}
|
||||
>
|
||||
<NotFound />
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,136 @@
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { IMeta, IPage, ImageSize } from 'src/types'
|
||||
import { createApiUrl, requestGet } from 'src/next-lib'
|
||||
import { NextPage, NextPageContext } from 'next'
|
||||
import { CardGrid } from 'src/components/CardGrid'
|
||||
import { Card } from 'src/components/Card'
|
||||
import { Block } from 'src/components/Block'
|
||||
import { Image } from 'src/components/Image'
|
||||
import { imageSrcSet } from 'src/lib/image'
|
||||
|
||||
const persons = [
|
||||
'/teacher/mg-timasheva',
|
||||
'/teacher/va-sarzhin',
|
||||
'/teacher/od-gogoleva',
|
||||
'/teacher/nv-andreeva',
|
||||
'/teacher/vv-voronova',
|
||||
'/teacher/ea-burovtseva',
|
||||
'/teacher/sa-latipova',
|
||||
'/teacher/iv-khramov',
|
||||
'/teacher/ma-khramova',
|
||||
// АС Тимашева
|
||||
// ЕЮ Тарасова
|
||||
// ИН Втюрина
|
||||
// МЮ Валькова
|
||||
// РК Тимашев
|
||||
]
|
||||
|
||||
type CardData = {
|
||||
title: string
|
||||
caption?: string
|
||||
imageSrc: string
|
||||
imageAlt: string
|
||||
imageSrcSet: string
|
||||
href: string
|
||||
}
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
meta: IMeta
|
||||
data: CardData[]
|
||||
}
|
||||
|
||||
const Page: NextPage<Props> = props => {
|
||||
if (!props.data) {
|
||||
console.log('kek error')
|
||||
return
|
||||
}
|
||||
|
||||
const src = 'https://art.shlisselburg.org/static/img/collective.jpg'
|
||||
const srcSet = imageSrcSet(src, [ImageSize.big, ImageSize.large, ImageSize.medium, ImageSize.small])
|
||||
|
||||
return (
|
||||
<App
|
||||
showAuthor={true}
|
||||
wide={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
|
||||
<Image
|
||||
src={src}
|
||||
srcSet={srcSet}
|
||||
style={{
|
||||
marginBottom: 'var(--size-l)',
|
||||
borderRadius: 'var(--radius)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<CardGrid style={{
|
||||
marginBottom: 'var(--size-xl)',
|
||||
}}>
|
||||
{props.data.map(item => (
|
||||
<Card
|
||||
key={item.href}
|
||||
layout={'featured'}
|
||||
img={{
|
||||
src: item.imageSrc,
|
||||
alt: item.imageAlt,
|
||||
srcSet: item.imageSrcSet,
|
||||
}}
|
||||
href={item.href}
|
||||
>
|
||||
<Block direction={'vertical'}>
|
||||
<span>{item.title}</span>
|
||||
<span style={{
|
||||
paddingTop: 'var(--size-s)',
|
||||
fontSize: 'var(--font-size-second)',
|
||||
}}>
|
||||
{item.caption}
|
||||
</span>
|
||||
</Block>
|
||||
</Card>
|
||||
))}
|
||||
</CardGrid>
|
||||
</App>
|
||||
)
|
||||
}
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: NextPageContext) => {
|
||||
const title = 'Преподаватели Шлиссельбургской ДХШ'
|
||||
const pages = await Promise.all(persons
|
||||
.map(page => requestGet<IPage>(createApiUrl(`/api/page?page=${page}`), null))
|
||||
)
|
||||
const data: CardData[] = pages
|
||||
.filter(Boolean)
|
||||
.map(x => {
|
||||
const image = x.preview.artifacts.large
|
||||
|
||||
return {
|
||||
title: x.title,
|
||||
caption: x.description,
|
||||
href: x.url,
|
||||
imageAlt: title,
|
||||
imageSrc: image.src,
|
||||
imageSrcSet: imageSrcSet(image.src, [ImageSize.medium, ImageSize.small])
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
description: 'Преподаватели Шлиссельбургской ДХШ',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import Document from 'src/components/Document'
|
||||
import { Share } from 'src/components/Share'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
import { NextPage } from 'next'
|
||||
import { IMeta } from 'src/types'
|
||||
|
||||
type Props = {
|
||||
data: any //(file)
|
||||
meta: IMeta
|
||||
}
|
||||
|
||||
const Page: NextPage<Props> = props => {
|
||||
if (!props.data) {
|
||||
console.log('kek error', props)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<App
|
||||
showAuthor={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.data.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
<Document {...props.data} />
|
||||
<Share />
|
||||
</App>
|
||||
)
|
||||
}
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
const id = ctx.params.slug
|
||||
const data = await requestGet(createApiUrl(`/api/files/${id}`), {})
|
||||
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
meta: meta({
|
||||
// title: file.meta,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const unstable_getStaticPaths = async () => {
|
||||
const urls = await requestGet<IResponseItems<string>>(createApiUrl(`/api/files/urls`), null)
|
||||
if (!urls) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
paths: urls.items
|
||||
.map(x => x.replace('/document/', ''))
|
||||
.map(slug => {
|
||||
return {
|
||||
params: {
|
||||
slug,
|
||||
},
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,62 @@
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { PageGrid } from 'src/components/PageGrid'
|
||||
import { HudozkaTitle } from 'src/components/HudozkaTitle'
|
||||
import { MetaBuilder } from 'src/lib/meta'
|
||||
import { getPagesCardsByTags } from 'src/rest'
|
||||
import { NextPage } from 'next'
|
||||
import { IMeta, PageCardData } from 'src/types'
|
||||
|
||||
interface IProps {
|
||||
title: string
|
||||
meta: IMeta
|
||||
items: PageCardData[]
|
||||
}
|
||||
|
||||
const Index: NextPage<IProps> = props => (
|
||||
<App
|
||||
showAuthor={true}
|
||||
wide={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
|
||||
<HudozkaTitle
|
||||
style={{
|
||||
marginBottom: 'var(--size-l)'
|
||||
}}
|
||||
/>
|
||||
|
||||
<PageGrid
|
||||
items={props.items}
|
||||
/>
|
||||
</App>
|
||||
)
|
||||
|
||||
export const unstable_getStaticProps = async () => {
|
||||
const items = await getPagesCardsByTags([
|
||||
'event',
|
||||
'album',
|
||||
'post',
|
||||
])
|
||||
const title = 'Шлиссельбургская ДХШ'
|
||||
const meta = (new MetaBuilder())
|
||||
.setTitle(title)
|
||||
.setData({
|
||||
url: '/',
|
||||
})
|
||||
.build()
|
||||
|
||||
return {
|
||||
props: {
|
||||
items,
|
||||
title,
|
||||
meta,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Index
|
||||
Loaded 100 of 384 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user