mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 20:23:12 +08:00
Compare commits
922 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d945c28a75 | ||
|
|
b66922b3d5 | ||
|
|
7d981434e1 | ||
|
|
c672d35b4a | ||
|
|
6259c73b33 | ||
|
|
e4b00e832a | ||
|
|
a452aeaf7e | ||
|
|
6e83b90028 | ||
|
|
45ffdce312 | ||
|
|
5485efc9ae | ||
|
|
850360dd2b | ||
|
|
8d3ceaf462 | ||
|
|
bb6c5de697 | ||
|
|
ca4c1b00dc | ||
|
|
dda6d30c12 | ||
|
|
7bb40c74c1 | ||
|
|
4515a83e9b | ||
|
|
907a282b73 | ||
|
|
5154638ddf | ||
|
|
52c9994eb4 | ||
|
|
912f287a27 | ||
|
|
e99ab66afd | ||
|
|
fb022eae68 | ||
|
|
259c52a698 | ||
|
|
deeca582db | ||
|
|
4dbcf6857e | ||
|
|
5190a03113 | ||
|
|
d60c3a4aa9 | ||
|
|
ce1c70fd7c | ||
|
|
29bb201581 | ||
|
|
006ec7c1d0 | ||
|
|
1bc9e0643d | ||
|
|
d6395a3de8 | ||
|
|
098751d256 | ||
|
|
8577d6bd99 | ||
|
|
fe4e171ecb | ||
|
|
d99ea82761 | ||
|
|
27bc8d4823 | ||
|
|
7c9f572eb1 | ||
|
|
2732b05834 | ||
|
|
97f89bcede | ||
|
|
00255b04eb | ||
|
|
f90ea4bd98 | ||
|
|
0d7fb1163a | ||
|
|
cb340b5867 | ||
|
|
1621f3a943 | ||
|
|
ae636aef5a | ||
|
|
1346d7902e | ||
|
|
544836b986 | ||
|
|
397f7d09e3 | ||
|
|
efa707c676 | ||
|
|
51c8b80489 | ||
|
|
e71fb0ac7f | ||
|
|
681b7c836d | ||
|
|
7c2c90fc49 | ||
|
|
ebe6a5f4aa | ||
|
|
65651e4bbd | ||
|
|
33190c07c7 | ||
|
|
f651b0e5a1 | ||
|
|
c85eb84b4c | ||
|
|
86d428184e | ||
|
|
c1529b7d6c | ||
|
|
7ecc3aacc4 | ||
|
|
b091bd4e2a | ||
|
|
90b4bc9b1a | ||
|
|
de7096010e | ||
|
|
03a9697298 | ||
|
|
fdcf88de67 | ||
|
|
7c343f42c1 | ||
|
|
786e4f642b | ||
|
|
b1a073b1ab | ||
|
|
2256f6a9e7 | ||
|
|
ec3e7212b2 | ||
|
|
554d9b092f | ||
|
|
33d3837795 | ||
|
|
0686ac2fb1 | ||
|
|
ce3d267572 | ||
|
|
4ec90e02c9 | ||
|
|
e7bccb088d | ||
|
|
cbc9f1257d | ||
|
|
c7f15b77fd | ||
|
|
a8b0c46142 | ||
|
|
065d314608 | ||
|
|
d426dbc684 | ||
|
|
c6c78f383f | ||
|
|
450eea64aa | ||
|
|
c8d7413dd4 | ||
|
|
00a96e6215 | ||
|
|
b15e90408f | ||
|
|
34c10c0bc9 | ||
|
|
63a24342e3 | ||
|
|
073e4eeac8 | ||
|
|
06eda073bf | ||
|
|
614e1c37f8 | ||
|
|
24aab5cc0e | ||
|
|
76d6d9a7a9 | ||
|
|
8109ceb7eb | ||
|
|
6163e34327 | ||
|
|
84b291e310 | ||
|
|
20cd5cd752 | ||
|
|
d9d57743e6 | ||
|
|
cc57dcd01a | ||
|
|
10fbf8f8ff | ||
|
|
cfc9a1b772 | ||
|
|
2555424124 | ||
|
|
405369824b | ||
|
|
cdd87f5962 | ||
|
|
d5eac3b1c3 | ||
|
|
1f78f5266a | ||
|
|
aa0124f4e9 | ||
|
|
641f35ae05 | ||
|
|
4e94f07050 | ||
|
|
460ca032d2 | ||
|
|
8d438aa6aa | ||
|
|
504d08101a | ||
|
|
4b7184ac85 | ||
|
|
55d9f2a4a1 | ||
|
|
319b790628 | ||
|
|
ee563978ac | ||
|
|
be71a6eeb4 | ||
|
|
25fbbee883 | ||
|
|
8dbd9ca33d | ||
|
|
cad8c7ed48 | ||
|
|
c11c731bef | ||
|
|
a79362d520 | ||
|
|
c708df2029 | ||
|
|
e0760c371a | ||
|
|
714a58945e | ||
|
|
87e8ae7af6 | ||
|
|
a66dca4f07 | ||
|
|
9853b0916f | ||
|
|
18afd92fc3 | ||
|
|
0474b32c34 | ||
|
|
ca6fb1c656 | ||
|
|
a7a69506a0 | ||
|
|
a32651aab3 | ||
|
|
977af0ee43 | ||
|
|
53bbcd34a6 | ||
|
|
1eb9ef0361 | ||
|
|
1ac05fd3a4 | ||
|
|
4aef0fa728 | ||
|
|
f4da1b0a2e | ||
|
|
163839494b | ||
|
|
8a811c5b33 | ||
|
|
007139e4ff | ||
|
|
6be0310933 | ||
|
|
5f9b629676 | ||
|
|
8e84b0e8ac | ||
|
|
3ff9e101a8 | ||
|
|
71570af7db | ||
|
|
1401dd9156 | ||
|
|
36c12a69b1 | ||
|
|
742e2d724e | ||
|
|
f74851b0d8 | ||
|
|
dd5dcf6ec1 | ||
|
|
a448972e3c | ||
|
|
f784a4f794 | ||
|
|
554a66503f | ||
|
|
73c6c52b2a | ||
|
|
509672f4c7 | ||
|
|
e4f01e4cf8 | ||
|
|
156eae56d0 | ||
|
|
2df6e694ea | ||
|
|
78b838f6b6 | ||
|
|
08950f6433 | ||
|
|
a74e1df55c | ||
|
|
bf1c6e3d43 | ||
|
|
d234e114db | ||
|
|
035743ef6a | ||
|
|
c7c9ab71ff | ||
|
|
e107aa9ed8 | ||
|
|
e9191732bd | ||
|
|
f44540f95e | ||
|
|
6b3fd2ac83 | ||
|
|
eed353fedf | ||
|
|
b4e58cc1bb | ||
|
|
56d690d9a6 | ||
|
|
bffc044bc7 | ||
|
|
58dd1c54f9 | ||
|
|
5c163490c7 | ||
|
|
f2f07be11f | ||
|
|
0bea843e7f | ||
|
|
83cc11030d | ||
|
|
cb70dc5389 | ||
|
|
68db52679b | ||
|
|
186168b26c | ||
|
|
e9c64b46b7 | ||
|
|
f476cfc30f | ||
|
|
3706d6c82d | ||
|
|
248209ab41 | ||
|
|
2867a9b7b0 | ||
|
|
68919c19b8 | ||
|
|
ee1aa42900 | ||
|
|
c3de39d313 | ||
|
|
8ecc9c69a2 | ||
|
|
e814f3afcf | ||
|
|
a4eda9d761 | ||
|
|
f3f3ef5c18 | ||
|
|
296134078c | ||
|
|
527faf27a8 | ||
|
|
9c339b9c4f | ||
|
|
f274cafa7c | ||
|
|
371f246c41 | ||
|
|
95ae540799 | ||
|
|
f340e0e76e | ||
|
|
14e0766f72 | ||
|
|
17f3897746 | ||
|
|
3c3a30cc0e | ||
|
|
726811f91f | ||
|
|
75f5da5846 | ||
|
|
489c739be2 | ||
|
|
9d6f14c81a | ||
|
|
788f9635dd | ||
|
|
09aa4cc977 | ||
|
|
9252cc269b | ||
|
|
65e6676c00 | ||
|
|
135b62a9cc | ||
|
|
2ea58acde2 | ||
|
|
e1085f3ef5 | ||
|
|
f1db195a06 | ||
|
|
fa646b0176 | ||
|
|
21ef1dfee8 | ||
|
|
5ef548bc2a | ||
|
|
1fa9896524 | ||
|
|
cb6756d801 | ||
|
|
cf605407ad | ||
|
|
1a4eee1c81 | ||
|
|
4c5cd88041 | ||
|
|
4d346d1fea | ||
|
|
7f39e6d768 | ||
|
|
9b9de8e290 | ||
|
|
e1e3c7f061 | ||
|
|
3218f8c283 | ||
|
|
f0acce1be0 | ||
|
|
dd4b4277c9 | ||
|
|
baf77b6ffb | ||
|
|
5be882a31b | ||
|
|
ae060f3b13 | ||
|
|
e8ab664561 | ||
|
|
f4bf3551f6 | ||
|
|
8f06e4b20f | ||
|
|
bfdd6e0c50 | ||
|
|
ba13d99a71 | ||
|
|
eac468514b | ||
|
|
7da00fbc8c | ||
|
|
4b7c54d7a9 | ||
|
|
c336bb1901 | ||
|
|
fbb1c449da | ||
|
|
252cc7f49d | ||
|
|
00cac4ba72 | ||
|
|
91aaffc6db | ||
|
|
2e269bcacd | ||
|
|
bdd14427d9 | ||
|
|
06c59fe2dd | ||
|
|
9a78fd1a26 | ||
|
|
626c92460b | ||
|
|
7e0a14493e | ||
|
|
8d23e60d3a | ||
|
|
5f0293bc21 | ||
|
|
afe7381263 | ||
|
|
b75922f1d3 | ||
|
|
d9ae83a92b | ||
|
|
22cc9ace4d | ||
|
|
53a37feafe | ||
|
|
0cab51b01b | ||
|
|
5f258b6a28 | ||
|
|
cc41c51f77 | ||
|
|
5f42266cf2 | ||
|
|
522d8ed236 | ||
|
|
78359c28c7 | ||
|
|
13bb48d068 | ||
|
|
f6d9a83202 | ||
|
|
dfdae96da5 | ||
|
|
9f13063b1f | ||
|
|
1e8c36b5f1 | ||
|
|
e747a0b250 | ||
|
|
79b8834c61 | ||
|
|
313b6a59b9 | ||
|
|
d5288f756e | ||
|
|
867659ea25 | ||
|
|
74ad3553cb | ||
|
|
8b0ce8e6f3 | ||
|
|
0e4bccc4bb | ||
|
|
c4ae89a279 | ||
|
|
80d41db901 | ||
|
|
6c30fc73ee | ||
|
|
56932e4ea6 | ||
|
|
84b38c4940 | ||
|
|
aece9e68ba | ||
|
|
d067f52ac2 | ||
|
|
b5947d1642 | ||
|
|
3232ab401f | ||
|
|
1ef10f11cb | ||
|
|
5beaab85ac | ||
|
|
4498386f56 | ||
|
|
a30f796100 | ||
|
|
93a2c83652 | ||
|
|
4d4423413d | ||
|
|
a1c74c4038 | ||
|
|
f71bb5e307 | ||
|
|
9190996e54 | ||
|
|
af8b6b7f96 | ||
|
|
704958994d | ||
|
|
a6864bca7c | ||
|
|
15a8305209 | ||
|
|
469b70d708 | ||
|
|
4f988d223b | ||
|
|
e95abc4efb | ||
|
|
4917e53acd | ||
|
|
45db4c321a | ||
|
|
e6d914cfe1 | ||
|
|
85714c931d | ||
|
|
66a7f279c2 | ||
|
|
7c6827ea9f | ||
|
|
078c8ea011 | ||
|
|
655fd58a9d | ||
|
|
6ba6b951bf | ||
|
|
8e0e11f3b3 | ||
|
|
18b12ab1e6 | ||
|
|
3908f63b0d | ||
|
|
ca2c32fa8c | ||
|
|
a3a3ac1859 | ||
|
|
f5e0d0c2b1 | ||
|
|
ba0678946f | ||
|
|
934fa937b5 | ||
|
|
8d888cb43d | ||
|
|
7f888c49b4 | ||
|
|
e97dfbfdda | ||
|
|
b858d79b9e | ||
|
|
72d8edf4ff | ||
|
|
31db97228d | ||
|
|
2ffbf03cf8 | ||
|
|
85440c1c5f | ||
|
|
69962f1fe8 | ||
|
|
10cad33248 | ||
|
|
0c9deca3f8 | ||
|
|
e1cd4a6ee3 | ||
|
|
f915b97606 | ||
|
|
e775b0f12f | ||
|
|
3ee5853b32 | ||
|
|
832a24e4c7 | ||
|
|
551368c30d | ||
|
|
9f24b8eb76 | ||
|
|
4c83ae2b22 | ||
|
|
28e0fb049b | ||
|
|
f79a369884 | ||
|
|
34c7b870a7 | ||
|
|
ec9f874bb9 | ||
|
|
1980d35691 | ||
|
|
ec8cbf82e5 | ||
|
|
2b1011de52 | ||
|
|
fa9ace83ad | ||
|
|
a9a53a9652 | ||
|
|
afca8b2a62 | ||
|
|
daeb293fb9 | ||
|
|
1ca50a4a8a | ||
|
|
c6602a81ab | ||
|
|
46da0e7824 | ||
|
|
e790a38cea | ||
|
|
11879ea377 | ||
|
|
7105add009 | ||
|
|
af61fdb52d | ||
|
|
2b4fc354db | ||
|
|
5b72001a8c | ||
|
|
7c79906ac4 | ||
|
|
e30a724529 | ||
|
|
73f97ea874 | ||
|
|
09c155b7d3 | ||
|
|
1e2d115768 | ||
|
|
53ba58d7e9 | ||
|
|
2a30afbe7d | ||
|
|
4d39c7041b | ||
|
|
c012b4740d | ||
|
|
55778eb810 | ||
|
|
fb8ab0b5fe | ||
|
|
fcce18b059 | ||
|
|
43a3d469e7 | ||
|
|
f730c24ecb | ||
|
|
b8522b8c17 | ||
|
|
b0d96040be | ||
|
|
5554675d03 | ||
|
|
c88401ef29 | ||
|
|
75f77ed929 | ||
|
|
4539ab2dcf | ||
|
|
9ad0561cac | ||
|
|
774052a983 | ||
|
|
3beb27acc2 | ||
|
|
5432eb7b77 | ||
|
|
181ff16409 | ||
|
|
682a0768b7 | ||
|
|
d29ebb706e | ||
|
|
4e6836c605 | ||
|
|
59c0d36c0b | ||
|
|
e10b2884c0 | ||
|
|
8fb54e815e | ||
|
|
3a14483568 | ||
|
|
fdd8efc12d | ||
|
|
3fef4b4a28 | ||
|
|
cea50a860e | ||
|
|
f9318bb1b3 | ||
|
|
d22f020eb5 | ||
|
|
c556d8123b | ||
|
|
a592833bd0 | ||
|
|
881da3db3b | ||
|
|
32eb9924e5 | ||
|
|
d2c06dd0ab | ||
|
|
75b8ed813f | ||
|
|
f811002c2b | ||
|
|
ca1b17863c | ||
|
|
644f5be6fe | ||
|
|
65fbb1dfb3 | ||
|
|
8597966187 | ||
|
|
6ddd70d21d | ||
|
|
d32112fe7e | ||
|
|
da858f0353 | ||
|
|
ba8b041698 | ||
|
|
d9fee5582a | ||
|
|
c9f17d54ee | ||
|
|
f5c1e12db4 | ||
|
|
18ceafeb60 | ||
|
|
67e93a6874 | ||
|
|
f6a83a3062 | ||
|
|
8a0e902bdd | ||
|
|
590fc3fc92 | ||
|
|
0d54b7696f | ||
|
|
6b1dadc35c | ||
|
|
86009543ed | ||
|
|
b728a78673 | ||
|
|
fb89cb80ac | ||
|
|
d0a986dd5a | ||
|
|
bb231e668b | ||
|
|
f53fa5c90f | ||
|
|
1f3e0d1f73 | ||
|
|
33a404f051 | ||
|
|
51e130c7a0 | ||
|
|
343ee04acb | ||
|
|
9372d1739a | ||
|
|
13555366e3 | ||
|
|
8162dafe16 | ||
|
|
0b6602b275 | ||
|
|
2cf9f64e8e | ||
|
|
37c0a813db | ||
|
|
a9269811dc | ||
|
|
0b353737ea | ||
|
|
a3fc1513e1 | ||
|
|
5886d73093 | ||
|
|
12adce9ac2 | ||
|
|
d8cbf0512b | ||
|
|
2bb2c2cde3 | ||
|
|
27fd9c5988 | ||
|
|
542673fcb0 | ||
|
|
e60d80bb16 | ||
|
|
bb9e57fa8b | ||
|
|
1d4bea68af | ||
|
|
d2f9f5fd5e | ||
|
|
f13c3364eb | ||
|
|
ac04bedead | ||
|
|
67a66cefa6 | ||
|
|
43db066cb4 | ||
|
|
faec758fba | ||
|
|
ccec6e03aa | ||
|
|
3f08b38558 | ||
|
|
1ee5f64738 | ||
|
|
d22169803f | ||
|
|
57807c4360 | ||
|
|
6e1d07b0cc | ||
|
|
0c0349fa39 | ||
|
|
8d8a0f2484 | ||
|
|
8ff913aaf2 | ||
|
|
f7d54abb2b | ||
|
|
ce990c67e3 | ||
|
|
907b8453c6 | ||
|
|
8a0ba11f79 | ||
|
|
0c1ecd3caa | ||
|
|
c07c37f7bd | ||
|
|
fe652c67f4 | ||
|
|
297985cf01 | ||
|
|
10f79ec01d | ||
|
|
e87d4d61bc | ||
|
|
e58f2a384e | ||
|
|
881002634a | ||
|
|
aa80c2bb82 | ||
|
|
75cd9774b7 | ||
|
|
1a8e4cdfd7 | ||
|
|
6858dc4785 | ||
|
|
5d1d9dcac3 | ||
|
|
732bb06c62 | ||
|
|
5f00d8b732 | ||
|
|
a3ff545d43 | ||
|
|
279bd12a2d | ||
|
|
1b2a0d674f | ||
|
|
fd82de31c0 | ||
|
|
327d35fe57 | ||
|
|
e18187f04e | ||
|
|
bd2f488e2c | ||
|
|
96350c8b8f | ||
|
|
a737eed5c5 | ||
|
|
9b2ccaeb7b | ||
|
|
304979273f | ||
|
|
7add41d560 | ||
|
|
51ebe57ac8 | ||
|
|
6ff5a532ea | ||
|
|
fccd92497a | ||
|
|
452bfea088 | ||
|
|
240d2588f9 | ||
|
|
eca7800487 | ||
|
|
b9e58b9300 | ||
|
|
54918db9ef | ||
|
|
b07a603456 | ||
|
|
4fb2c64fa8 | ||
|
|
258c4102be | ||
|
|
b9c7f8e8d4 | ||
|
|
f32e7cc7c4 | ||
|
|
4ebd48b2b0 | ||
|
|
f71bd84a20 | ||
|
|
33b45eb620 | ||
|
|
1f9a912c04 | ||
|
|
45151cdde6 | ||
|
|
8ca45eb388 | ||
|
|
b7a34a6640 | ||
|
|
8ec686f4e2 | ||
|
|
43fc655218 | ||
|
|
f5a30c4c2d | ||
|
|
af1dd78b2c | ||
|
|
fc95986f0e | ||
|
|
c52794aed6 | ||
|
|
15a72e2a88 | ||
|
|
94af588a3c | ||
|
|
0a4f3bc0f5 | ||
|
|
09e3464426 | ||
|
|
7fcc4ebd57 | ||
|
|
b246815529 | ||
|
|
ad1334f289 | ||
|
|
49bccf9a06 | ||
|
|
1a8bf54e8b | ||
|
|
96bb0321eb | ||
|
|
37f0abf86f | ||
|
|
f9119a38b3 | ||
|
|
8465a9da31 | ||
|
|
b6b0d09647 | ||
|
|
3d2932782d | ||
|
|
fa2fede604 | ||
|
|
3aded3bc5f | ||
|
|
2d4cc912b7 | ||
|
|
c0881f9292 | ||
|
|
302a77b9dd | ||
|
|
2b544ad141 | ||
|
|
36d5e057d0 | ||
|
|
96eb6a80e1 | ||
|
|
9c5f5a46b5 | ||
|
|
886f63d3e4 | ||
|
|
7748dd7cef | ||
|
|
2e0f8067aa | ||
|
|
4fadd523f1 | ||
|
|
abe54c67d8 | ||
|
|
f3f35218c3 | ||
|
|
b9eb9cb6d9 | ||
|
|
40c014b663 | ||
|
|
70f1db4e94 | ||
|
|
2fff38d51a | ||
|
|
32de74b23e | ||
|
|
c96d24cc0b | ||
|
|
824ee55d38 | ||
|
|
86e701dccc | ||
|
|
2151b97bec | ||
|
|
18312b0624 | ||
|
|
a67cf4fb85 | ||
|
|
50100c251e | ||
|
|
1070482629 | ||
|
|
d205405dcc | ||
|
|
16d9c3b93f | ||
|
|
52ecef0311 | ||
|
|
f172146f27 | ||
|
|
ee986912d4 | ||
|
|
0e04968bc2 | ||
|
|
0cf1d8d375 | ||
|
|
339121fbb1 | ||
|
|
f99ca464de | ||
|
|
5c90a7c7a7 | ||
|
|
434b56fa8c | ||
|
|
78b484e657 | ||
|
|
9d36534230 | ||
|
|
bd6243b2ac | ||
|
|
416857e129 | ||
|
|
8cd81d0441 | ||
|
|
bf0d9ab7d9 | ||
|
|
bfeb915cd2 | ||
|
|
a830420d75 | ||
|
|
b904c6d00d | ||
|
|
050050a9eb | ||
|
|
9e48dc4137 | ||
|
|
2bd9bb233c | ||
|
|
e92d437456 | ||
|
|
fd6151040e | ||
|
|
b4e3dd7b4e | ||
|
|
8667544b3a | ||
|
|
664569a52b | ||
|
|
3c9da80962 | ||
|
|
583985e7ce | ||
|
|
b17bcb6c93 | ||
|
|
4886825564 | ||
|
|
79cd3ba912 | ||
|
|
21a3d4b66b | ||
|
|
562b0a0868 | ||
|
|
25243a10ec | ||
|
|
3fdba53995 | ||
|
|
34d8b396a4 | ||
|
|
c560e1fda2 | ||
|
|
3634236214 | ||
|
|
6de0cba0b0 | ||
|
|
35f5f4851e | ||
|
|
3a8f4de3bb | ||
|
|
d67321f187 | ||
|
|
7c52d0ec19 | ||
|
|
ea08ab9758 | ||
|
|
9c06803a31 | ||
|
|
9222dcbc2e | ||
|
|
75b91dc26b | ||
|
|
4334ea295d | ||
|
|
03eeef7f52 | ||
|
|
0747137679 | ||
|
|
0152f6fa1a | ||
|
|
9f2ede0b84 | ||
|
|
748786d599 | ||
|
|
18f0fb556a | ||
|
|
685b3f86b3 | ||
|
|
524914fd35 | ||
|
|
f7d09bf173 | ||
|
|
4b113e5781 | ||
|
|
5616579131 | ||
|
|
8142801f1f | ||
|
|
35eeaf4282 | ||
|
|
8ad8b08aa4 | ||
|
|
839ca31821 | ||
|
|
03a92e5556 | ||
|
|
99fd5f937e | ||
|
|
d7ffcd903d | ||
|
|
a28e7987e6 | ||
|
|
f17c46bbed | ||
|
|
27008505e5 | ||
|
|
f9ba322547 | ||
|
|
661efcb51f | ||
|
|
5928f28699 | ||
|
|
1183d65184 | ||
|
|
fd53e49508 | ||
|
|
c484e32641 | ||
|
|
c1741c99be | ||
|
|
a03179743d | ||
|
|
83b9160ab1 | ||
|
|
83757ed390 | ||
|
|
843e943251 | ||
|
|
a0a1d3de72 | ||
|
|
e5f262869c | ||
|
|
a070a68a57 | ||
|
|
3479bbbb36 | ||
|
|
e1a1a0652f | ||
|
|
3e9b775114 | ||
|
|
57c91aca3c | ||
|
|
05a64b8d9e | ||
|
|
30780f9c5f | ||
|
|
3455df9214 | ||
|
|
27aa8f9875 | ||
|
|
05451e3d1a | ||
|
|
b9b3295b52 | ||
|
|
f62ed66e26 | ||
|
|
e2a9a989ab | ||
|
|
4af27650cd | ||
|
|
76a603104d | ||
|
|
03825469ca | ||
|
|
058cdf7a82 | ||
|
|
7f43890713 | ||
|
|
57070ef155 | ||
|
|
fc5904e743 | ||
|
|
46c0620236 | ||
|
|
1bf56b658b | ||
|
|
8cc4d3e6d5 | ||
|
|
3080c49caf | ||
|
|
ed94ec39c4 | ||
|
|
6bcdd5e6c8 | ||
|
|
cf3d763731 | ||
|
|
e558bb17cb | ||
|
|
5d76a55c46 | ||
|
|
4401824716 | ||
|
|
df23f20d31 | ||
|
|
6c37905c15 | ||
|
|
4685572def | ||
|
|
3cf1657d54 | ||
|
|
60d3c09da5 | ||
|
|
5055ddf995 | ||
|
|
d83d2f82bd | ||
|
|
5266743a0c | ||
|
|
fffb2872ef | ||
|
|
3b56fc3760 | ||
|
|
5213faa0a1 | ||
|
|
6f4e5175da | ||
|
|
f502eb0195 | ||
|
|
509c9fe2e8 | ||
|
|
13e795d25d | ||
|
|
a28ef1f176 | ||
|
|
0c5eec7e95 | ||
|
|
310032e303 | ||
|
|
32b3c72bdf | ||
|
|
c1eee2246b | ||
|
|
0d7d53ab5b | ||
|
|
2f1de4a162 | ||
|
|
cdd36b6712 | ||
|
|
6f0afe269d | ||
|
|
09321eaa93 | ||
|
|
850259290a | ||
|
|
1bafe54a75 | ||
|
|
89e373f775 | ||
|
|
74854b3dac | ||
|
|
4e5ab21a47 | ||
|
|
a0510d6a69 | ||
|
|
6ddb60d047 | ||
|
|
ad592a563c | ||
|
|
b3b67b051d | ||
|
|
8607d58e18 | ||
|
|
34e60ec5b8 | ||
|
|
d044e1a5b7 | ||
|
|
9096318968 | ||
|
|
ba6e8c4092 | ||
|
|
98b6dba05d | ||
|
|
84c4631124 | ||
|
|
a7d308c585 | ||
|
|
af0ed6135e | ||
|
|
115ca0da0f | ||
|
|
673b545a83 | ||
|
|
d030fd1aa6 | ||
|
|
7c1e40be88 | ||
|
|
b739619532 | ||
|
|
372b83d92f | ||
|
|
4e3b5419d5 | ||
|
|
c34d5a35e2 | ||
|
|
a959b5bf02 | ||
|
|
45ac2c3c51 | ||
|
|
f6bddc6f24 | ||
|
|
6b8fc3228e | ||
|
|
8cf1a95df5 | ||
|
|
b3ea2969c5 | ||
|
|
054bbb8d5a | ||
|
|
19c1b29f59 | ||
|
|
264b8c2a9e | ||
|
|
ec33b95f97 | ||
|
|
4437d5305f | ||
|
|
f20f01e22e | ||
|
|
1ee30fe5dc | ||
|
|
4dc026d7fc | ||
|
|
1e862a8ee8 | ||
|
|
5ece2d1939 | ||
|
|
146e9100be | ||
|
|
0c854edc6b | ||
|
|
07d031e7b9 | ||
|
|
705dc2ec39 | ||
|
|
3b9221c7d2 | ||
|
|
576d602ed0 | ||
|
|
9df8bd63d4 | ||
|
|
99c4802367 | ||
|
|
94dc6f2053 | ||
|
|
d4005da35c | ||
|
|
cbe2c362d5 | ||
|
|
8bf79db66a | ||
|
|
b87964f716 | ||
|
|
b0a574f578 | ||
|
|
92dc3019de | ||
|
|
d8f92cb2be | ||
|
|
bf24660ddb | ||
|
|
b66268867a | ||
|
|
9abaa243e0 | ||
|
|
7030138ff4 | ||
|
|
906dfe60f8 | ||
|
|
01f60614ef | ||
|
|
eeb5306883 | ||
|
|
24c3a3e84c | ||
|
|
8b9663aea0 | ||
|
|
b59f4e2805 | ||
|
|
6293d57fde | ||
|
|
39ac2efe26 | ||
|
|
1f449e8ce1 | ||
|
|
ad906000c7 | ||
|
|
5361f33cc1 | ||
|
|
1937f3ea22 | ||
|
|
f4e9d0c8fe | ||
|
|
9b767ee9f3 | ||
|
|
43cb91bf52 | ||
|
|
f784ee5b28 | ||
|
|
cd6f067fe5 | ||
|
|
6d366188c9 | ||
|
|
c4ca59dcb0 | ||
|
|
74675ad314 | ||
|
|
b5e4da847a | ||
|
|
232bf5a68b | ||
|
|
67958a78d3 | ||
|
|
b8cdc29d8f | ||
|
|
30e4ddb950 | ||
|
|
a61a7db275 | ||
|
|
0f5e4e7fa2 | ||
|
|
eb17eae781 | ||
|
|
740a65f880 | ||
|
|
5bd7b53e0a | ||
|
|
9538f474de | ||
|
|
8f2e78c946 | ||
|
|
2f09ff456c | ||
|
|
c8d950e04d | ||
|
|
0f6a0da7a4 | ||
|
|
4300fb225f | ||
|
|
159021e87c | ||
|
|
9dc6ea9eeb | ||
|
|
42e84e526e | ||
|
|
700ee40109 | ||
|
|
4909bd9550 | ||
|
|
e0d20bf8ff | ||
|
|
09a5284675 | ||
|
|
1112f3acdd | ||
|
|
05e3071db2 | ||
|
|
403773bc17 | ||
|
|
636df2a736 | ||
|
|
baf3093893 | ||
|
|
8d3a49a319 | ||
|
|
eced1af21d | ||
|
|
49c3c9bec1 | ||
|
|
2ffaabe594 | ||
|
|
f31dc6c6e5 | ||
|
|
f067d0e831 | ||
|
|
a892f72425 | ||
|
|
25f64a2fc4 | ||
|
|
fd1fe90731 | ||
|
|
495643f4f1 | ||
|
|
8da6b336f5 | ||
|
|
d0bb86ca8f | ||
|
|
1085809fa5 | ||
|
|
7b1857dcda | ||
|
|
6f38790d47 | ||
|
|
a81ac85af6 | ||
|
|
8f4d5b6fce | ||
|
|
8468a48eaa | ||
|
|
fc0885e5d5 | ||
|
|
e1a9e347c3 | ||
|
|
1b0e3b686d | ||
|
|
a09208582e | ||
|
|
df1de020d1 | ||
|
|
9b363368a2 | ||
|
|
9fac69675a | ||
|
|
1d2cfde7f7 | ||
|
|
452612ee00 | ||
|
|
8d74e0c3ff | ||
|
|
0a1786c89a | ||
|
|
bc07d77d06 | ||
|
|
6e38457655 | ||
|
|
d7a3aeb0a2 | ||
|
|
76541d5563 | ||
|
|
28b5d8445e | ||
|
|
d17533fd0f | ||
|
|
01d62b7aea | ||
|
|
bd97804ca6 | ||
|
|
7b0ccc9f69 | ||
|
|
fdb98fa2a9 | ||
|
|
fb684bd788 | ||
|
|
05acf4eb2a | ||
|
|
56d0383170 | ||
|
|
b31236958b | ||
|
|
3ffa46fb26 | ||
|
|
217003381a | ||
|
|
234190f493 | ||
|
|
209e3e9e14 | ||
|
|
872241f497 | ||
|
|
cb7a66c59b | ||
|
|
924383ccc8 | ||
|
|
65d1301195 | ||
|
|
57c0490d84 | ||
|
|
b927e44107 | ||
|
|
6433a67d52 | ||
|
|
1cc2cfaec7 | ||
|
|
3fa24c5d81 | ||
|
|
a5c96f41b3 | ||
|
|
9fac291df4 | ||
|
|
971b7967de | ||
|
|
b3a4adeba4 | ||
|
|
b732f1df0d | ||
|
|
4395732c5e | ||
|
|
6720d12ab8 | ||
|
|
456765e55b | ||
|
|
a6009c89d3 | ||
|
|
d767c415d1 | ||
|
|
d88f535444 | ||
|
|
0c7dd18b7c | ||
|
|
0e535123ae | ||
|
|
8ce23b80bd | ||
|
|
d96023d063 | ||
|
|
d734d1a3b3 | ||
|
|
095f075ca9 | ||
|
|
ef70e44a17 | ||
|
|
27a6f35534 | ||
|
|
47ea4218d0 | ||
|
|
1fd677df5a | ||
|
|
7c349e42fd | ||
|
|
da88ec6807 | ||
|
|
cb715c0877 | ||
|
|
97a362617d | ||
|
|
24e708b7e1 | ||
|
|
583a684b03 | ||
|
|
fe8465261f | ||
|
|
334cc231dc | ||
|
|
848d574f68 | ||
|
|
8f929c0ee3 | ||
|
|
15bd839940 | ||
|
|
0323e0d17d | ||
|
|
5fa4fa0225 | ||
|
|
986c165815 | ||
|
|
53243d1764 | ||
|
|
4aed8e6b59 | ||
|
|
16653d60ed | ||
|
|
c9be89647c | ||
|
|
406f947096 | ||
|
|
64916dafac | ||
|
|
02ca843944 | ||
|
|
3520ab6b18 | ||
|
|
30314fd532 | ||
|
|
4a3e495be7 | ||
|
|
ccfd993042 | ||
|
|
bfd2f5b7cf | ||
|
|
b7cc460844 |
23
.dockerignore
Normal file
23
.dockerignore
Normal file
@@ -0,0 +1,23 @@
|
||||
config/autoload/*local*
|
||||
data/infra
|
||||
data/cache/*
|
||||
data/log/*
|
||||
data/locks/*
|
||||
data/proxies/*
|
||||
data/migrations_template.txt
|
||||
data/GeoLite2-City.*
|
||||
data/database.sqlite
|
||||
data/shlink-tests.db
|
||||
CHANGELOG.md
|
||||
UPGRADE.md
|
||||
composer.lock
|
||||
vendor
|
||||
docs
|
||||
indocker
|
||||
docker-*
|
||||
phpstan.neon
|
||||
php*xml*
|
||||
infection.json
|
||||
**/test*
|
||||
build*
|
||||
**/.*
|
||||
15
.env.dist
15
.env.dist
@@ -1,15 +0,0 @@
|
||||
# Application
|
||||
APP_ENV=
|
||||
SECRET_KEY=
|
||||
SHORTENED_URL_SCHEMA=
|
||||
SHORTENED_URL_HOSTNAME=
|
||||
SHORTCODE_CHARS=
|
||||
|
||||
# Language
|
||||
DEFAULT_LOCALE=
|
||||
CLI_LOCALE=
|
||||
|
||||
# Database
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
DB_NAME=
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -3,13 +3,10 @@
|
||||
/docs export-ignore
|
||||
/module/CLI/test export-ignore
|
||||
/module/CLI/test-resources export-ignore
|
||||
/module/Common/test export-ignore
|
||||
/module/Common/test-db export-ignore
|
||||
/module/Core/test export-ignore
|
||||
/module/Core/test-db export-ignore
|
||||
/module/Rest/test export-ignore
|
||||
/module/Rest/test-api export-ignore
|
||||
.env.dist export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.phpstorm.meta.php export-ignore
|
||||
|
||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github: ['acelaya']
|
||||
custom: ['https://acel.me/donate']
|
||||
7
.github/ISSUE_TEMPLATE.md
vendored
7
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,6 +1,7 @@
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be required.
|
||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be expected.
|
||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
||||
Try to be polite, and understand it is impossible for a project to cover all use cases.
|
||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
-->
|
||||
|
||||
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Something on shlink is broken or not working as documented?
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be expected.
|
||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
|
||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||
-->
|
||||
|
||||
#### How Shlink is set-up
|
||||
|
||||
* Shlink Version: x.y.z
|
||||
* PHP Version: x.y.z
|
||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted swoole|Docker image
|
||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary describing the problem you are experiencing. -->
|
||||
|
||||
#### Current behavior
|
||||
|
||||
<!-- How is it actually behaving (and it shouldn't)? -->
|
||||
|
||||
#### Expected behavior
|
||||
|
||||
<!-- How did you expected to behave? -->
|
||||
|
||||
#### How to reproduce
|
||||
|
||||
<!-- Provide steps to reproduce the bug. -->
|
||||
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Do you find shlink is missing some important feature that would make it more useful?
|
||||
labels: feature
|
||||
---
|
||||
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be expected.
|
||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
|
||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||
-->
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Describe the new feature you would like to request. -->
|
||||
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Question - Support
|
||||
about: Do you have a problem setting up or using shlink?
|
||||
labels: question
|
||||
---
|
||||
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be expected.
|
||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
|
||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||
-->
|
||||
|
||||
#### How Shlink is set-up
|
||||
|
||||
* Shlink Version: x.y.z
|
||||
* PHP Version: x.y.z
|
||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted swoole|Docker image
|
||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Describe the issue you are facing here. -->
|
||||
28
.github/workflows/docker-image-build.yml
vendored
Normal file
28
.github/workflows/docker-image-build.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Build docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
version: latest
|
||||
- name: Login to docker hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build the image
|
||||
run: bash ./docker/build
|
||||
30
.github/workflows/publish-release.yml
vendored
Normal file
30
.github/workflows/publish-release.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Publish release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Use PHP 7.4
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.4' # Publish release with lowest supported PHP version
|
||||
tools: composer
|
||||
extensions: swoole-4.5.5
|
||||
- name: Generate release assets
|
||||
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||
- name: Publish release with assets
|
||||
uses: docker://antonyurchenko/git-release:latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ALLOW_TAG_PREFIX: "true"
|
||||
ALLOW_EMPTY_CHANGELOG: "true"
|
||||
with:
|
||||
args: |
|
||||
build/shlink_*_dist.zip
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,9 +1,9 @@
|
||||
.idea
|
||||
build
|
||||
!docker/build
|
||||
composer.lock
|
||||
composer.phar
|
||||
vendor/
|
||||
.env
|
||||
data/database.sqlite
|
||||
data/shlink-tests.db
|
||||
data/GeoLite2-City.mmdb
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
||||
use Laminas\ServiceManager\ServiceLocatorInterface;
|
||||
|
||||
/**
|
||||
* PhpStorm Container Interop code completion
|
||||
|
||||
@@ -6,6 +6,9 @@ checks:
|
||||
code_rating: true
|
||||
duplication: true
|
||||
build:
|
||||
dependencies:
|
||||
override:
|
||||
- composer install --no-interaction --no-scripts --ignore-platform-reqs
|
||||
nodes:
|
||||
analysis:
|
||||
tests:
|
||||
|
||||
63
.travis.yml
63
.travis.yml
@@ -1,57 +1,56 @@
|
||||
dist: bionic
|
||||
|
||||
language: php
|
||||
|
||||
branches:
|
||||
only:
|
||||
- /.*/
|
||||
|
||||
php:
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
services:
|
||||
- mysql
|
||||
- postgresql
|
||||
- docker
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache/files
|
||||
|
||||
jobs:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- php: 'nightly'
|
||||
include:
|
||||
- name: "CI - 8.0"
|
||||
php: 'nightly'
|
||||
env:
|
||||
- COMPOSER_FLAGS='--ignore-platform-reqs'
|
||||
- name: "CI - 7.4"
|
||||
php: '7.4'
|
||||
env:
|
||||
- COMPOSER_FLAGS=''
|
||||
|
||||
before_install:
|
||||
- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
- yes | pecl install swoole
|
||||
- phpenv config-rm xdebug.ini || return 0
|
||||
- sudo ./data/infra/ci/install-ms-odbc.sh
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_ms shlink_db shlink_db_postgres shlink_db_maria
|
||||
- yes | pecl install pdo_sqlsrv-5.9.0preview1 swoole-4.5.5 pcov
|
||||
|
||||
install:
|
||||
- composer self-update
|
||||
- composer install --no-interaction
|
||||
- composer install --no-interaction --prefer-dist $COMPOSER_FLAGS
|
||||
|
||||
before_script:
|
||||
- mysql -e 'CREATE DATABASE shlink_test;'
|
||||
- psql -c 'create database shlink_test;' -U postgres
|
||||
- docker-compose exec shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||
- mkdir build
|
||||
- export DOCKERFILE_CHANGED=$(git diff ${TRAVIS_COMMIT_RANGE:-origin/main} --name-only | grep Dockerfile)
|
||||
|
||||
script:
|
||||
- composer ci
|
||||
- bin/test/run-api-tests.sh
|
||||
- if [[ ! -z "${DOCKERFILE_CHANGED}" && "${TRAVIS_PHP_VERSION}" == "7.4" ]]; then docker build -t shlink-docker-image:temp . ; fi
|
||||
|
||||
after_success:
|
||||
- rm -f build/clover.xml
|
||||
- phpdbg -qrr vendor/bin/phpcov merge build --clover build/clover.xml
|
||||
- wget https://phar.phpunit.de/phpcov-7.0.2.phar
|
||||
- php phpcov-7.0.2.phar merge build --clover build/clover.xml
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover build/clover.xml
|
||||
|
||||
# Before deploying, build dist file for current travis tag
|
||||
before_deploy:
|
||||
- rm -f ocular.phar
|
||||
- ./build.sh ${TRAVIS_TAG#?}
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key:
|
||||
secure: a9dbZchocqeuOViwUeNH54bQR5Sz7rEYXx5b9WPFtnFn9LGKKUaLbA2U91UQ9QKPrcTpsALubUYbw2CnNmvCwzaY+R8lCD3gkU4ohsEnbpnw3deOeixI74sqBHJAuCH9FSaRDGILoBMtUKx2xlzIymFxkIsgIukkGbdkWHDlRWY3oTUUuw1SQ2Xk9KDsbJQtjIc1+G/O6gHaV4qv/R9W8NPmJExKTNDrAZbC1vIUnxqp4UpVo1hst8qPd1at94CndDYM5rG+7imGbdtxTxzamt819qdTO1OfvtctKawNAm7YXZrrWft6c7gI6j6SI4hxd+ZrrPBqbaRFHkZHjnNssO/yn4SaOHFFzccmu0MzvpPCf0qWZwd3sGHVYer1MnR2mHYqU84QPlW3nrHwJjkrpq3+q0JcBY6GsJs+RskHNtkMTKV05Iz6QUI5YZGwTpuXaRm036SmavjGc4IDlMaYCk/NmbB9BKpthJxLdUpczOHpnjXXHziotWD6cfEnbjU3byfD8HY5WrxSjsNT7SKmXN3hRof7bk985ewQVjGT42O3NbnfnqjQQWr/B7/zFTpLR4f526Bkq12CdCyf5lvrbq+POkLVdJ+uFfR7ds248Ue/jBQy6kM1tWmKF9QiwisFlA84eQ4CW3I93Rp97URv+AQa9zmbD0Ve3Udp+g6nF5I=
|
||||
file: "./build/shlink_${TRAVIS_TAG#?}_dist.zip"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
php: 7.2
|
||||
- provider: script
|
||||
script: bash data/travis/trigger_docker_build.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
php: 7.2
|
||||
|
||||
1308
CHANGELOG.md
1308
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
136
CONTRIBUTING.md
Normal file
136
CONTRIBUTING.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Contributing
|
||||
|
||||
This file will guide you through the process of getting to project up and running, in case you want to provide coding contributions.
|
||||
|
||||
You will also see how to ensure the code fulfills the expected code checks, and how to create a pull request.
|
||||
|
||||
## System dependencies
|
||||
|
||||
The project provides all its dependencies as docker containers through a docker-compose configuration.
|
||||
|
||||
Because of this, the only actual dependencies are [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
## Setting up the project
|
||||
|
||||
The first thing you need to do is fork the repository, and clone it in your local machine.
|
||||
|
||||
Then you will have to follow these steps:
|
||||
|
||||
* Copy all files with `.local.php.dist` extension from `config/autoload` by removing the dist extension.
|
||||
|
||||
For example the `common.local.php.dist` file should be copied as `common.local.php`.
|
||||
|
||||
* Copy the file `docker-compose.override.yml.dist` by also removing the `dist` extension.
|
||||
* Start-up the project by running `docker-compose up`.
|
||||
|
||||
The first time this command is run, it will create several containers that are used during development, so it may take some time.
|
||||
|
||||
It will also create some empty databases and install the project dependencies with composer.
|
||||
|
||||
* Run `./indocker bin/cli db:create` to create the initial database.
|
||||
* Run `./indocker bin/cli db:migrate` to get database migrations up to date.
|
||||
* Run `./indocker bin/cli api-key:generate` to get your first API key generated.
|
||||
|
||||
Once you finish this, you will have the project exposed in ports `8000` through nginx+php-fpm and `8080` through swoole.
|
||||
|
||||
> Note: The `indocker` shell script is a helper used to run commands inside the main docker container.
|
||||
|
||||
## Project structure
|
||||
|
||||
This project is structured as a modular application, using [laminas/laminas-config-aggregator](https://github.com/laminas/laminas-config-aggregator) to merge the configuration provided by every module.
|
||||
|
||||
All modules are inside the `module` folder, and each one has its own `src`, `test` and `config` folders, with the source code, tests and configuration. They also have their own `ConfigProvider` class, which is consumed by the config aggregator.
|
||||
|
||||
This is a simplified version of the project structure:
|
||||
|
||||
```
|
||||
shlink
|
||||
├── bin
|
||||
│ ├── cli
|
||||
│ ├── install
|
||||
│ └── update
|
||||
├── config
|
||||
│ ├── autoload
|
||||
│ ├── params
|
||||
│ ├── config.php
|
||||
│ └── container.php
|
||||
├── data
|
||||
│ ├── cache
|
||||
│ ├── locks
|
||||
│ ├── log
|
||||
│ ├── migrations
|
||||
│ └── proxies
|
||||
├── docs
|
||||
│ ├── async-api
|
||||
│ └── swagger
|
||||
├── module
|
||||
│ ├── CLI
|
||||
│ ├── Core
|
||||
│ └── Rest
|
||||
├── public
|
||||
├── composer.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
The purposes of every folder are:
|
||||
|
||||
* `bin`: It contains the CLI tools. The `cli` one is the main entry point to run shlink from the command line, while `install` and `update` are helper tools used to install and update shlink when not using the docker image.
|
||||
* `config`: Contains application-wide configurations, which are later merged with the ones provided by every module.
|
||||
* `data`: Common runtime-generated git-ignored assets, like logs, caches, etc.
|
||||
* `docs`: Any project documentation is stored here, like API spec definitions.
|
||||
* `module`: Contains a subfolder for every module in the project. Modules contain the source code, tests and configurations for every context in the project.
|
||||
* `public`: Few assets (like `favicon.ico` or `robots.txt`) and the web entry point are stored here. This web entry point is not used when serving the app with swoole.
|
||||
|
||||
## Project tests
|
||||
|
||||
In order to ensure stability and no regressions are introduced while developing new features, this project has different types of tests.
|
||||
|
||||
* **Unit tests**: These are the simplest to run, and usually test individual pieces of code, replacing any external dependency by mocks.
|
||||
|
||||
The code coverage of unit tests is pretty high, and only entity repositories are excluded because of their nature.
|
||||
|
||||
* **Database tests**: These are integration tests that run against a real database, and only cover entity repositories.
|
||||
|
||||
Its purpose is to verify all the database queries behave as expected and return what's expected.
|
||||
|
||||
The project provides some tooling to run them against any of the supported database engines.
|
||||
|
||||
* **API tests**: These are E2E tests that spin up an instance of the app and test it from the outside, by interacting with the REST API.
|
||||
|
||||
These are the best tests to catch regressions, and to verify everything interacts as expected.
|
||||
|
||||
They use MySQL as the database engine, and include some fixtures that ensure the same data exists at the beginning of the execution.
|
||||
|
||||
* **CLI tests**: *TBD. Once included, its purpose will be the same as API tests, but running through the command line*
|
||||
|
||||
Depending on the kind of contribution, maybe not all kinds of tests are needed, but the more you provide, the better.
|
||||
|
||||
## Running code checks
|
||||
|
||||
* Run `./indocker composer cs` to check coding styles are fulfilled.
|
||||
* Run `./indocker composer cs:fix` to fix coding styles (some may not be fixable from the CLI)
|
||||
* Run `./indocker composer stan` to statically analyze the code with [phpstan](https://phpstan.org/). This tool is the closest to "compile" PHP and verify everything would work as expected.
|
||||
* Run `./indocker composer test:unit` to run the unit tests.
|
||||
* Run `./indocker composer test:db` to run the database integration tests.
|
||||
|
||||
This command runs the same test suite against all supported database engines. If you just want to run one of them, you can add one of `:sqlite`, `:mysql`, `:maria`, `:postgres`, `:mssql` at the end of the command.
|
||||
|
||||
For example, `test:db:postgres`.
|
||||
|
||||
* Run `./indocker composer test:api` to run API E2E tests. For these, the MySQL database engine is used.
|
||||
* Run `./indocker composer infect:test` ti run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
||||
* Run `./indocker composer ci` to run all previous commands together. This command is run during the project's continuous integration.
|
||||
|
||||
> Note: Due to some limitations in the tooling used by shlink, the testing databases need to exist beforehand, both for db and api tests (except sqlite).
|
||||
>
|
||||
> However, they just need to be created empty, with no tables. Also, once created, they are automatically reset before every new execution.
|
||||
>
|
||||
> The testing database is always called `shlink_test`. You can create it using the database client of your choice. [DBeaver](https://dbeaver.io/) is a good multi-platform desktop database client which supports all the engines supported by shlink.
|
||||
|
||||
## Pull request process
|
||||
|
||||
In order to provide pull requests to this project, you should always start by creating a new branch, where you will make all desired changes.
|
||||
|
||||
The base branch should always be `develop`, and the target branch for the pull request should also be `develop`.
|
||||
|
||||
Before your branch can be merged, all the checks described in [Running code checks](#running-code-checks) have to be passing. You can verify that manually by running `./indocker composer ci`, or wait for the build to be run automatically after the pull request is created.
|
||||
77
Dockerfile
Normal file
77
Dockerfile
Normal file
@@ -0,0 +1,77 @@
|
||||
FROM php:7.4.11-alpine3.12 as base
|
||||
|
||||
ARG SHLINK_VERSION=2.4.0
|
||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||
ENV SWOOLE_VERSION 4.5.5
|
||||
ENV LC_ALL "C"
|
||||
|
||||
WORKDIR /etc/shlink
|
||||
|
||||
# Install required PHP extensions
|
||||
RUN \
|
||||
# Install mysql and calendar
|
||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar && \
|
||||
# Install sqlite
|
||||
apk add --no-cache sqlite-libs sqlite-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||
# Install postgres
|
||||
apk add --no-cache postgresql-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_pgsql && \
|
||||
# Install intl
|
||||
apk add --no-cache icu-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" intl && \
|
||||
# Install zip and gd
|
||||
apk add --no-cache libzip-dev zlib-dev libpng-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" zip gd && \
|
||||
# Install gmp
|
||||
apk add --no-cache gmp-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" gmp
|
||||
|
||||
# Install sqlsrv driver
|
||||
RUN if [ $(uname -m) == "x86_64" ]; then \
|
||||
wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
||||
pecl install pdo_sqlsrv && \
|
||||
docker-php-ext-enable pdo_sqlsrv && \
|
||||
apk del .phpize-deps && \
|
||||
rm msodbcsql17_17.5.1.1-1_amd64.apk ; \
|
||||
fi
|
||||
|
||||
# Install swoole
|
||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
|
||||
pecl install swoole-${SWOOLE_VERSION} && \
|
||||
docker-php-ext-enable swoole && \
|
||||
apk del .phpize-deps
|
||||
|
||||
|
||||
# Install shlink
|
||||
FROM base as builder
|
||||
COPY . .
|
||||
COPY --from=composer:2 /usr/bin/composer ./composer.phar
|
||||
RUN apk add --no-cache git && \
|
||||
php composer.phar install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction && \
|
||||
php composer.phar clear-cache && \
|
||||
rm -r docker composer.* && \
|
||||
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
||||
|
||||
|
||||
# Prepare final image
|
||||
FROM base
|
||||
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
||||
|
||||
COPY --from=builder /etc/shlink .
|
||||
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink
|
||||
|
||||
# Expose default swoole port
|
||||
EXPOSE 8080
|
||||
|
||||
# Expose params config dir, since the user is expected to provide custom config from there
|
||||
VOLUME /etc/shlink/config/params
|
||||
|
||||
# Copy config specific for the image
|
||||
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
|
||||
COPY docker/config/shlink_in_docker.local.php config/autoload/shlink_in_docker.local.php
|
||||
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2019 Alejandro Celaya
|
||||
Copyright (c) 2016-2020 Alejandro Celaya
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
435
README.md
435
README.md
@@ -1,37 +1,56 @@
|
||||
# Shlink
|
||||

|
||||
|
||||
[](https://travis-ci.org/shlinkio/shlink)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://travis-ci.com/shlinkio/shlink)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/)
|
||||
[](https://packagist.org/packages/shlinkio/shlink)
|
||||
[](https://github.com/shlinkio/shlink/blob/master/LICENSE)
|
||||
[](https://acel.me/donate)
|
||||
[](https://hub.docker.com/r/shlinkio/shlink/)
|
||||
[](https://github.com/shlinkio/shlink/blob/main/LICENSE)
|
||||
[](https://slnk.to/donate)
|
||||
|
||||
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain.
|
||||
|
||||
> This document references Shlink 2.x. If you are using an older version and want to upgrade, follow the [UPGRADE](UPGRADE.md) doc.
|
||||
|
||||
> If you are trying to find out how to run the project in development mode or how to provide contributions, read the [CONTRIBUTING](CONTRIBUTING.md) doc.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Download](#download)
|
||||
- [Configure](#configure)
|
||||
- [Serve](#serve)
|
||||
- [Bonus](#bonus)
|
||||
- [Update to new version](#update-to-new-version)
|
||||
- [Using a docker image](#using-a-docker-image)
|
||||
- [Using shlink](#using-shlink)
|
||||
- [Shlink CLI Help](#shlink-cli-help)
|
||||
- [Shlink CLI Help](#shlink-cli-help)
|
||||
- [Multiple domains](#multiple-domains)
|
||||
- [Management](#management)
|
||||
- [Visits](#visits)
|
||||
- [Special redirects](#special-redirects)
|
||||
|
||||
## Installation
|
||||
|
||||
First make sure the host where you are going to run shlink fulfills these requirements:
|
||||
> These are the steps needed to install Shlink if you plan to manually host it.
|
||||
>
|
||||
> Alternatively, you can use the official docker image. If that's your intention, jump directly to [Using a docker image](#using-a-docker-image)
|
||||
|
||||
* PHP 7.2 or greater with JSON, APCu, intl, curl, PDO and gd extensions enabled.
|
||||
* MySQL, PostgreSQL or SQLite.
|
||||
First, make sure the host where you are going to run shlink fulfills these requirements:
|
||||
|
||||
* PHP 7.4 or greater with JSON, curl, PDO, intl and gd extensions enabled.
|
||||
* MySQL, MariaDB, PostgreSQL, Microsoft SQL Server or SQLite.
|
||||
* The web server of your choice with PHP integration (Apache or Nginx recommended).
|
||||
|
||||
Then, you will need a built version of the project. There are a few ways to get it.
|
||||
### Download
|
||||
|
||||
In order to run Shlink, you will need a built version of the project. There are two ways to get it.
|
||||
|
||||
* **Using a dist file**
|
||||
|
||||
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
|
||||
|
||||
Just go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink_X.X.X_dist.zip` file you will find there.
|
||||
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink_x.x.x_dist.zip` file you will find there.
|
||||
|
||||
Finally, decompress the file in the location of your choice.
|
||||
|
||||
@@ -43,187 +62,172 @@ Then, you will need a built version of the project. There are a few ways to get
|
||||
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
|
||||
* Run `./build.sh 1.0.0`, replacing the version with the version number you are going to build (the version number is only used for the generated dist file).
|
||||
|
||||
After that, you will have a `shlink_x.x.x_dist.zip` dist file inside the `build` directory.
|
||||
After that, you will have a `shlink_x.x.x_dist.zip` dist file inside the `build` directory, that you need to decompress in the location fo your choice.
|
||||
|
||||
This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by [travis](https://travis-ci.org/shlinkio/shlink), attaching generated dist file to it.
|
||||
> This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by [travis](https://travis-ci.com/shlinkio/shlink), attaching the generated dist file to it.
|
||||
|
||||
Despite how you built the project, you are going to need to install it now, by following these steps:
|
||||
### Configure
|
||||
|
||||
* If you are going to use MySQL or PostgreSQL, create an empty database with the name of your choice.
|
||||
Despite how you built the project, you now need to configure it, by following these steps:
|
||||
|
||||
* If you are going to use MySQL, MariaDB, PostgreSQL or Microsoft SQL Server, create an empty database with the name of your choice.
|
||||
* Recursively grant write permissions to the `data` directory. Shlink uses it to cache some information.
|
||||
* Setup the application by running the `bin/install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
|
||||
* Expose shlink to the web, either by using a traditional web server + fast CGI approach, or by using a [swoole](https://www.swoole.co.uk/) non-blocking server.
|
||||
|
||||
* **Using a web server:**
|
||||
|
||||
For example, assuming your domain is doma.in and shlink is in the `/path/to/shlink` folder, these would be the basic configurations for Nginx and Apache.
|
||||
|
||||
*Nginx:*
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name doma.in;
|
||||
listen 80;
|
||||
root /path/to/shlink/public;
|
||||
index index.php;
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Apache:*
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName doma.in
|
||||
DocumentRoot "/path/to/shlink/public"
|
||||
|
||||
<Directory "/path/to/shlink/public">
|
||||
Options FollowSymLinks Includes ExecCGI
|
||||
AllowOverride all
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
* **Using swoole:**
|
||||
|
||||
**Important!** Swoole support is still experimental. Use it with care, and report any found issue.
|
||||
|
||||
First you need to install the swoole PHP extension with [pecl](https://pecl.php.net/package/swoole), `pecl install swoole`.
|
||||
|
||||
Once installed, it's actually pretty easy to get shlink up and running with swoole. Just run `./vendor/bin/zend-expressive-swoole start -d` and you will get shlink running on port 8080.
|
||||
|
||||
However, by doing it this way, you are loosing all the access logs, and the service won't be automatically run if the server has to be restarted.
|
||||
|
||||
For that reason, you should create a daemon script, in `/etc/init.d/shlink_swoole`, like this one, replacing `/path/to/shlink` by the path to your shlink installation:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: shlink_swoole
|
||||
# Required-Start: $local_fs $network $named $time $syslog
|
||||
# Required-Stop: $local_fs $network $named $time $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: Shlink non-blocking server with swoole
|
||||
### END INIT INFO
|
||||
|
||||
SCRIPT=/path/to/shlink/vendor/bin/zend-expressive-swoole\ start
|
||||
RUNAS=root
|
||||
|
||||
PIDFILE=/var/run/shlink_swoole.pid
|
||||
LOGDIR=/var/log/shlink
|
||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
||||
|
||||
start() {
|
||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole already running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Starting shlink with swoole' >&2
|
||||
mkdir -p "$LOGDIR"
|
||||
touch "$LOGFILE"
|
||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||
su -c "$CMD" $RUNAS > "$PIDFILE"
|
||||
echo 'Shlink started' >&2
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole not running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Stopping shlink with swoole' >&2
|
||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||
echo 'Shlink stopped' >&2
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
```
|
||||
|
||||
Then run these commands to enable the service and start it:
|
||||
|
||||
* `sudo chmod +x /etc/init.d/shlink_swoole`
|
||||
* `sudo update-rc.d shlink_swoole defaults`
|
||||
* `sudo update-rc.d shlink_swoole enable`
|
||||
* `/etc/init.d/shlink_swoole start`
|
||||
|
||||
Now again, you can access shlink on port 8080, but this time the service will be automatically run at system start-up, and all access logs will be written in `/var/log/shlink/shlink_swoole.log` (you will probably want to [rotate those logs](https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-16-04). You can find an example logrotate config file [here](data/infra/examples/shlink-daemon-logrotate.conf)).
|
||||
|
||||
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with shlink's API.
|
||||
* Finally access to [https://app.shlink.io](https://app.shlink.io) and configure your server to start creating short URLs.
|
||||
|
||||
**Bonus**
|
||||
### Serve
|
||||
|
||||
There are a couple of time-consuming tasks that shlink expects you to do manually, or at least it is recommended, since it will improve runtime performance.
|
||||
Once Shlink is configured, you need to expose it to the web, either by using a traditional web server + fast CGI approach, or by using a [swoole](https://www.swoole.co.uk/) non-blocking server.
|
||||
|
||||
Those tasks can be performed using shlink's CLI, so it should be easy to schedule them to be run in the background (for example, using cron jobs):
|
||||
* **Using a web server:**
|
||||
|
||||
* **For shlink older than 1.18.0 or not using swoole as the web server**: Resolve IP address locations: `/path/to/shlink/bin/cli visit:locate`
|
||||
For example, assuming your domain is doma.in and shlink is in the `/path/to/shlink` folder, these would be the basic configurations for Nginx and Apache.
|
||||
|
||||
If you don't run this command regularly, the stats will say all visits come from *unknown* locations.
|
||||
*Nginx:*
|
||||
|
||||
* Generate website previews: `/path/to/shlink/bin/cli short-url:process-previews`
|
||||
```nginx
|
||||
server {
|
||||
server_name doma.in;
|
||||
listen 80;
|
||||
root /path/to/shlink/public;
|
||||
index index.php;
|
||||
charset utf-8;
|
||||
|
||||
Running this will improve the performance of the `doma.in/abc123/preview` URLs, which return a preview of the site.
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
* **For shlink older than v1.17.0**: Update IP geolocation database: `/path/to/shlink/bin/cli visit:update-db`
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
When shlink is installed it downloads a fresh [GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) db file. Running this command will update this file.
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The file is updated the first Tuesday of every month, so it should be enough running this command the first Wednesday.
|
||||
*Apache:*
|
||||
|
||||
*Any of these commands accept the `-q` flag, which makes it not display any output. This is recommended when configuring the commands as cron jobs.*
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName doma.in
|
||||
DocumentRoot "/path/to/shlink/public"
|
||||
|
||||
> In future versions, it is planed that, when using **swoole** to serve shlink, some of these tasks are automatically run without blocking the request and also, without having to configure cron jobs. Probably resolving IP locations and generating previews.
|
||||
<Directory "/path/to/shlink/public">
|
||||
Options FollowSymLinks Includes ExecCGI
|
||||
AllowOverride all
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
* **Using swoole:**
|
||||
|
||||
First you need to install the swoole PHP extension with [pecl](https://pecl.php.net/package/swoole), `pecl install swoole`.
|
||||
|
||||
Once installed, it's actually pretty easy to get shlink up and running with swoole. Run `./vendor/bin/mezzio-swoole start -d` and you will get shlink running on port 8080.
|
||||
|
||||
However, by doing it this way, you are loosing all the access logs, and the service won't be automatically run if the server has to be restarted.
|
||||
|
||||
For that reason, you should create a daemon script, in `/etc/init.d/shlink_swoole`, like this one, replacing `/path/to/shlink` by the path to your shlink installation:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: shlink_swoole
|
||||
# Required-Start: $local_fs $network $named $time $syslog
|
||||
# Required-Stop: $local_fs $network $named $time $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: Shlink non-blocking server with swoole
|
||||
### END INIT INFO
|
||||
|
||||
SCRIPT=/path/to/shlink/vendor/bin/mezzio-swoole\ start
|
||||
RUNAS=root
|
||||
|
||||
PIDFILE=/var/run/shlink_swoole.pid
|
||||
LOGDIR=/var/log/shlink
|
||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
||||
|
||||
start() {
|
||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole already running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Starting shlink with swoole' >&2
|
||||
mkdir -p "$LOGDIR"
|
||||
touch "$LOGFILE"
|
||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||
su -c "$CMD" $RUNAS > "$PIDFILE"
|
||||
echo 'Shlink started' >&2
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole not running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Stopping shlink with swoole' >&2
|
||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||
echo 'Shlink stopped' >&2
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
```
|
||||
|
||||
Then run these commands to enable the service and start it:
|
||||
|
||||
* `sudo chmod +x /etc/init.d/shlink_swoole`
|
||||
* `sudo update-rc.d shlink_swoole defaults`
|
||||
* `sudo update-rc.d shlink_swoole enable`
|
||||
* `/etc/init.d/shlink_swoole start`
|
||||
|
||||
Now again, you can access shlink on port 8080, but this time the service will be automatically run at system start-up, and all access logs will be written in `/var/log/shlink/shlink_swoole.log` (you will probably want to [rotate those logs](https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-16-04). You can find an example logrotate config file [here](data/infra/examples/shlink-daemon-logrotate.conf)).
|
||||
|
||||
Finally access to [https://app.shlink.io](https://app.shlink.io) and configure your server to start creating short URLs.
|
||||
|
||||
### Bonus
|
||||
|
||||
Geo-locating visits to your short links is a time-consuming task. When serving Shlink with swoole, the geo-location task is automatically run asynchronously just after a visit to a short URL happens.
|
||||
|
||||
However, if you are not serving Shlink with swoole, you will have to schedule the geo-location task to be run regularly in the background (for example, using cron jobs):
|
||||
|
||||
The command you need to run is `/path/to/shlink/bin/cli visit:locate`, and you can optionally provide the `-q` flag to remove any output and avoid your cron logs to be polluted.
|
||||
|
||||
## Update to new version
|
||||
|
||||
When a new Shlink version is available, you don't need to repeat the entire process yourself. Instead, follow these steps:
|
||||
When a new Shlink version is available, you don't need to repeat the entire process. Instead, follow these steps:
|
||||
|
||||
1. Rename your existing Shlink directory to something else (ie. `shlink` ---> `shlink-old`).
|
||||
2. Download and extract the new version of Shlink, and set the directories name to that of the old version. (ie. `shlink`).
|
||||
3. Run the `bin/update` script in the new version's directory to migrate your configuration over.
|
||||
2. Download and extract the new version of Shlink, and set the directory name to that of the old version (ie. `shlink`).
|
||||
3. Run the `bin/update` script in the new version's directory to migrate your configuration over. You will be asked to provide the path to the old instance (ie. `shlink-old`).
|
||||
4. If you are using shlink with swoole, restart the service by running `/etc/init.d/shlink_swoole restart`.
|
||||
|
||||
The `bin/update` script will ask you for the location from previous shlink version, and use it in order to import the configuration. It will then update the database and generate some assets shlink needs to work.
|
||||
The `bin/update` will use the location from previous shlink version to import the configuration. It will then update the database and generate some assets shlink needs to work.
|
||||
|
||||
Right now, it does not import cached info (like website previews), but it will. For now you will need to regenerate them again.
|
||||
|
||||
**Important!** It is recommended that you don't skip any version when using this process. The update gets better on every version, but older versions might make assumptions.
|
||||
**Important!** It is recommended that you don't skip any version when using this process. The update tool gets better on every version, but older versions might make assumptions.
|
||||
|
||||
## Using a docker image
|
||||
|
||||
Starting with version 1.15.0, an official docker image is provided. You can find the docs on how to use it [here](https://hub.docker.com/r/shlinkio/shlink/).
|
||||
Starting with version 1.15.0, an official docker image is provided. You can learn how to use it by reading [the docs](docker/README.md).
|
||||
|
||||
The idea is that you can just generate a container using the image and provide custom config via env vars.
|
||||
|
||||
@@ -237,7 +241,7 @@ Once shlink is installed, there are two main ways to interact with it:
|
||||
|
||||
It is probably a good idea to symlink the CLI entry point (`bin/cli`) to somewhere in your path, so that you can run shlink from any directory.
|
||||
|
||||
* **The REST API**. The complete docs on how to use the API can be found [here](https://shlink.io/api-docs), and a sandbox which also documents every endpoint can be found [here](https://shlink.io/swagger-ui/index.html).
|
||||
* **The REST API**. The complete docs on how to use the API can be found [here](https://shlink.io/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
|
||||
|
||||
However, you probably don't want to consume the raw API yourself. That's why a nice [web client](https://github.com/shlinkio/shlink-web-client) is provided that can be directly used from [https://app.shlink.io](https://app.shlink.io), or you can host it yourself too.
|
||||
|
||||
@@ -259,33 +263,90 @@ Options:
|
||||
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
|
||||
|
||||
Available commands:
|
||||
help Displays help for a command
|
||||
list Lists commands
|
||||
help Displays help for a command
|
||||
list Lists commands
|
||||
api-key
|
||||
api-key:disable Disables an API key.
|
||||
api-key:generate Generates a new valid API key.
|
||||
api-key:list Lists all the available API keys.
|
||||
config
|
||||
config:generate-charset [DEPRECATED] Generates a character set sample just by shuffling the default one, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". Then it can be set in the SHORTCODE_CHARS environment variable
|
||||
config:generate-secret [DEPRECATED] Generates a random secret string that can be used for JWT token encryption
|
||||
api-key:disable Disables an API key.
|
||||
api-key:generate Generates a new valid API key.
|
||||
api-key:list Lists all the available API keys.
|
||||
db
|
||||
db:create Creates the database needed for shlink to work. It will do nothing if the database already exists
|
||||
db:migrate Runs database migrations, which will ensure the shlink database is up to date.
|
||||
db:create Creates the database needed for shlink to work. It will do nothing if the database already exists
|
||||
db:migrate Runs database migrations, which will ensure the shlink database is up to date.
|
||||
short-url
|
||||
short-url:delete [short-code:delete] Deletes a short URL
|
||||
short-url:generate [shortcode:generate|short-code:generate] Generates a short URL for provided long URL and returns it
|
||||
short-url:list [shortcode:list|short-code:list] List all short URLs
|
||||
short-url:parse [shortcode:parse|short-code:parse] Returns the long URL behind a short code
|
||||
short-url:process-previews [shortcode:process-previews|short-code:process-previews] Processes and generates the previews for every URL, improving performance for later web requests.
|
||||
short-url:visits [shortcode:visits|short-code:visits] Returns the detailed visits information for provided short code
|
||||
short-url:delete Deletes a short URL
|
||||
short-url:generate Generates a short URL for provided long URL and returns it
|
||||
short-url:list List all short URLs
|
||||
short-url:parse Returns the long URL behind a short code
|
||||
short-url:visits Returns the detailed visits information for provided short code
|
||||
tag
|
||||
tag:create Creates one or more tags.
|
||||
tag:delete Deletes one or more tags.
|
||||
tag:list Lists existing tags.
|
||||
tag:rename Renames one existing tag.
|
||||
tag:create Creates one or more tags.
|
||||
tag:delete Deletes one or more tags.
|
||||
tag:list Lists existing tags.
|
||||
tag:rename Renames one existing tag.
|
||||
visit
|
||||
visit:locate [visit:process] Resolves visits origin locations.
|
||||
visit:update-db [DEPRECATED] Updates the GeoLite2 database file used to geolocate IP addresses
|
||||
visit:locate Resolves visits origin locations.
|
||||
```
|
||||
|
||||
## Multiple domains
|
||||
|
||||
While in many cases you will just have one short domain and you'll want all your short URLs to be served from it, there are some cases in which you might want to have multiple short domains served from the same Shlink instance.
|
||||
|
||||
If that's the case, you need to understand how Shlink will behave when managing your short URLs or any of them is visited.
|
||||
|
||||
### Management
|
||||
|
||||
When you create a short URL it is possible to optionally pass a `domain` param. If you don't pass it, the short URL will be created for the default domain (the one provided during Shlink's installation or in the `SHORT_DOMAIN_HOST` env var when using the docker image).
|
||||
|
||||
However, if you pass it, the short URL will be "linked" to that domain.
|
||||
|
||||
> Note that, if the default domain is passed, Shlink will ignore it and will behave as if no `domain` param was provided.
|
||||
|
||||
The main benefit of being able to pass the domain is that Shlink will allow the same custom slug to be used in multiple short URLs, as long as the domain is different (like `example.com/my-compaign`, `another.com/my-compaign` and `foo.com/my-compaign`).
|
||||
|
||||
Then, each short URL will be tracked separately and you will be able to define specific tags and metadata for each one of them.
|
||||
|
||||
However, this has a side effect. When you try to interact with an existing short URL (editing tags, editing meta, resolving it or deleting it), either from the REST API or the CLI tool, you will have to provide the domain appropriately.
|
||||
|
||||
Let's imagine this situation. Shlink's default domain is `example.com`, and you have the next short URLs:
|
||||
|
||||
* `https://example.com/abc123` -> a regular short URL where no domain was provided.
|
||||
* `https://example.com/my-campaign` -> a regular short URL where no domain was provided, but it has a custom slug.
|
||||
* `https://another.com/my-campaign` -> a short URL where the `another.com` domain was provided, and it has a custom slug.
|
||||
* `https://another.com/def456` -> a short URL where the `another.com` domain was provided.
|
||||
|
||||
These are some of the results you will get when trying to interact with them, depending on the params you provide:
|
||||
|
||||
* Providing just the `abc123` short code -> the first URL will be matched.
|
||||
* Providing just the `my-campaign` short code -> the second URL will be matched, since you did not specify a domain, therefor, Shlink looks for the one with the short code/slug `my-campaign` which is also linked to default domain (or not linked to any domain, to be more accurate).
|
||||
* Providing the `my-campaign` short code and the `another.com` domain -> The third one will be matched.
|
||||
* Providing just the `def456` short code -> Shlink will fail/not find any short URL, since there's none with the short code `def456` linked to default domain.
|
||||
* Providing the `def456` short code and the `another.com` domain -> The fourth short URL will be matched.
|
||||
* Providing any short code and the `foo.com` domain -> Again, no short URL will be found, as there's none linked to `foo.com` domain.
|
||||
|
||||
### Visits
|
||||
|
||||
Before adding support for multiple domains, you could point as many domains as you wanted to Shlink, and they would have always worked for existing short codes/slugs.
|
||||
|
||||
In order to keep backwards compatibility, Shlink's behavior when a short URL is visited is slightly different, getting to fallback in some cases.
|
||||
|
||||
Let's continue with previous example, and also consider we have three domains that will resolve to our Shlink instance, which are `example.com`, `another.com` and `foo.com`.
|
||||
|
||||
With that in mind, this is how Shlink will behave when the next short URLs are visited:
|
||||
|
||||
* `https://another.com/abc123` -> There was no short URL specifically defined for domain `another.com` and short code `abc123`, but it exists for default domain (`example.com`), so it will fall back to it and redirect to where `example.com/abc123` is configured to redirect.
|
||||
* `https://example.com/def456` -> The fall-back does not happen from default domain to specific ones, only the other way around (like in previous case). Because of that, this one will result in a not-found URL, even though the `def456` short code exists for `another.com` domain.
|
||||
* `https://foo.com/abc123` -> This will also fall-back to `example.com/abc123`, like in the first case.
|
||||
* `https://another.com/non-existing` -> The combination of `another.com` domain with the `non-existing` slug does not exist, so Shlink will try to fall-back to the same but for default domain (`example.com`). However, since that combination does not exist either, it will result in a not-found URL.
|
||||
* Any other short URL visited exactly as it was configured will, of course, resolve as expected.
|
||||
|
||||
### Special redirects
|
||||
|
||||
It is currently possible to configure some special redirects when the base domain is visited, a URL does not match, or an invalid/disabled short URL is visited.
|
||||
|
||||
Those are configured during Shlink's installation or via env vars when using the docker image.
|
||||
|
||||
Currently those are all shared for all domains serving the same Shlink instance, but the plan is to update that and allow specific ones for every existing domain.
|
||||
|
||||
---
|
||||
|
||||
> This product includes GeoLite2 data created by MaxMind, available from [https://www.maxmind.com](https://www.maxmind.com)
|
||||
|
||||
88
UPGRADE.md
Normal file
88
UPGRADE.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Upgrading
|
||||
|
||||
## From v1.x to v2.x
|
||||
|
||||
### PHP 7.4 required
|
||||
|
||||
This new version takes advantage of several new features introduced in PHP 7.4.
|
||||
|
||||
Thanks to that, the code is more reliable and robust, and easier to maintain and improve.
|
||||
|
||||
However, that means that any previous PHP version is no longer supported.
|
||||
|
||||
### Preview generation
|
||||
|
||||
The ability to generate website previews has been completely removed and has no replacement.
|
||||
|
||||
The feature never properly worked, and it wasn't really useful. Because of that, the feature is no longer available on Shlink 2.x
|
||||
|
||||
Removing this feature has these implications:
|
||||
|
||||
* The `short-url:process-previews` CLI command no longer exists, and an error will be thrown if executed.
|
||||
* The `/{shortCode}/preview` path is no longer valid, and will return a 404 status.
|
||||
|
||||
### Removed paths
|
||||
|
||||
These routes have been removed, but have a direct replacement:
|
||||
|
||||
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
||||
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
||||
|
||||
When using the old ones, a 404 status will me returned now.
|
||||
|
||||
### Removed command and route aliases
|
||||
|
||||
All the aliases for the CLI commands in the `short-urls` namespace have been removed. If you were using any of those commands with the `shortcode` or `short-code` prefixes, make sure to update them to use the `short-urls` prefix instead.
|
||||
|
||||
The same happens for all REST endpoints starting with `/short-code`. They were previously aliased to `/short-urls` ones, but they will return a 404 now. Make sure to update them accordingly.
|
||||
|
||||
### JWT authentication removed
|
||||
|
||||
Shlink's REST API no longer accepts authentication using a JWT token. The API key has to be passed now in the `x-api-key` header.
|
||||
|
||||
Removing this feature has these implications:
|
||||
|
||||
* Shlink will no longer introspect the `Authorization` header for Bearer tokens.
|
||||
* The `POST /rest/v{version}/authenticate` endpoint no longer exists and will return a 404.
|
||||
|
||||
### API version is now required
|
||||
|
||||
Endpoints need to provide a version in the path now. Previously, not providing a version used to fall back to v1. Now, it will return a 404 status, as no route will match.
|
||||
|
||||
The only exception is the `/rest/health` endpoint, which will continue working without the version.
|
||||
|
||||
### API errors
|
||||
|
||||
Shlink v1.21.0 introduced support for API errors using the Problem Details format, as well as the v2 of the API.
|
||||
|
||||
For backwards compatibility reasons, requests performed to v1 continued to return the old `error` and `message` properties.
|
||||
|
||||
Starting with Shlink v2.0.0, both versions of the API will no longer return those two properties.
|
||||
|
||||
As a replacement, use `type` instead of `error`, and `detail` instead of `message`.
|
||||
|
||||
### Changes in models
|
||||
|
||||
The next REST API models have changed:
|
||||
|
||||
* **ShortUrl**: The `originalUrl` property was deprecated and has been removed. Use `longUrl` instead.
|
||||
* **Visit**: The `remoteAddr` property was deprecated and has been removed. It has no replacement.
|
||||
* **VisitLocation**: The `latitude` and `longitude` properties are no longer strings, but float.
|
||||
|
||||
### URL validation
|
||||
|
||||
Shlink can verify provided long URLs are valid before trying to shorten them. Starting with v2, it no longer does it by default and needs to be explicitly enabled instead of explicitly disabled.
|
||||
|
||||
### Removed config options
|
||||
|
||||
The `not_found_redirect_to` config option and the `NOT_FOUND_REDIRECT_TO` env var are no longer taken into consideration for the docker image.
|
||||
|
||||
Instead, use `invalid_short_url_redirect_to` and `INVALID_SHORT_URL_REDIRECT_TO` respectively.
|
||||
|
||||
### Migrated to Laminas
|
||||
|
||||
The project has been using Zend Framework components since the beginning. Since it has been re-branded as [Laminas](https://getlaminas.org/), this version updates to the new set of components.
|
||||
|
||||
Updating to Laminas components has these implications:
|
||||
|
||||
* If you were manually serving Shlink with swoole, the entry script has to be changed from `/path/to/shlink/vendor/bin/zend-expressive-swoole` to `/path/to/shlink/vendor/bin/mezzio-swoole`
|
||||
9
bin/cli
9
bin/cli
@@ -1,10 +1,7 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/../config/container.php';
|
||||
$container->get(CliApp::class)->run();
|
||||
$run = require __DIR__ . '/../config/run.php';
|
||||
$run(true);
|
||||
|
||||
@@ -8,5 +8,5 @@ use function chdir;
|
||||
use function dirname;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$run(false);
|
||||
[$install] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$install();
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
export APP_ENV=test
|
||||
export DB_DRIVER=mysql
|
||||
export TEST_ENV=api
|
||||
|
||||
# Try to stop server just in case it hanged in last execution
|
||||
vendor/bin/zend-expressive-swoole stop
|
||||
vendor/bin/mezzio-swoole stop
|
||||
|
||||
echo 'Starting server...'
|
||||
vendor/bin/zend-expressive-swoole start -d
|
||||
vendor/bin/mezzio-swoole start -d
|
||||
sleep 2
|
||||
|
||||
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox
|
||||
vendor/bin/zend-expressive-swoole stop
|
||||
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $*
|
||||
testsExitCode=$?
|
||||
|
||||
vendor/bin/mezzio-swoole stop
|
||||
|
||||
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
||||
exit $testsExitCode
|
||||
|
||||
@@ -8,5 +8,5 @@ use function chdir;
|
||||
use function dirname;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$run(true);
|
||||
[, $update] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$update();
|
||||
|
||||
Binary file not shown.
32
build.sh
32
build.sh
@@ -17,38 +17,16 @@ echo 'Copying project files...'
|
||||
rm -rf "${builtcontent}"
|
||||
mkdir -p "${builtcontent}"
|
||||
rsync -av * "${builtcontent}" \
|
||||
--exclude=bin/test \
|
||||
--exclude=data/infra \
|
||||
--exclude=data/travis \
|
||||
--exclude=data/cache/* \
|
||||
--exclude=data/log/* \
|
||||
--exclude=data/locks/* \
|
||||
--exclude=data/proxies/* \
|
||||
--exclude=data/migrations_template.txt \
|
||||
--exclude=data/GeoLite2-City.* \
|
||||
--exclude=data/database.sqlite \
|
||||
--exclude=data/shlink-tests.db \
|
||||
--exclude=**/.gitignore \
|
||||
--exclude=CHANGELOG.md \
|
||||
--exclude=composer.lock \
|
||||
--exclude=vendor \
|
||||
--exclude=docs \
|
||||
--exclude=indocker \
|
||||
--exclude=docker* \
|
||||
--exclude=php* \
|
||||
--exclude=infection.json \
|
||||
--exclude=phpstan.neon \
|
||||
--exclude=config/autoload/*local* \
|
||||
--exclude=config/test \
|
||||
--exclude=**/test* \
|
||||
--exclude=build* \
|
||||
--exclude=.github
|
||||
--exclude=*docker* \
|
||||
--exclude=Dockerfile \
|
||||
--include=.htaccess \
|
||||
--exclude-from=./.dockerignore
|
||||
cd "${builtcontent}"
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing dependencies with $composerBin..."
|
||||
${composerBin} self-update
|
||||
${composerBin} install --no-dev --optimize-autoloader --apcu-autoloader --no-progress --no-interaction
|
||||
${composerBin} install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction
|
||||
|
||||
# Delete development files
|
||||
echo 'Deleting dev files...'
|
||||
|
||||
168
composer.json
168
composer.json
@@ -12,73 +12,78 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2",
|
||||
"php": "^7.4",
|
||||
"ext-json": "*",
|
||||
"ext-pdo": "*",
|
||||
"acelaya/ze-content-based-error-handler": "^2.2",
|
||||
"akrabat/ip-address-middleware": "^1.0",
|
||||
"cakephp/chronos": "^1.2",
|
||||
"cocur/slugify": "^3.0",
|
||||
"doctrine/cache": "^1.6",
|
||||
"doctrine/migrations": "^2.0",
|
||||
"doctrine/orm": "^2.5",
|
||||
"endroid/qr-code": "^1.7",
|
||||
"firebase/php-jwt": "^4.0",
|
||||
"cakephp/chronos": "^2.0",
|
||||
"cocur/slugify": "^4.0",
|
||||
"doctrine/cache": "^1.9",
|
||||
"doctrine/dbal": "^2.10",
|
||||
"doctrine/migrations": "^3.0.1",
|
||||
"doctrine/orm": "^2.7",
|
||||
"endroid/qr-code": "^3.6",
|
||||
"geoip2/geoip2": "^2.9",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"lstrojny/functional-php": "^1.8",
|
||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||
"monolog/monolog": "^1.21",
|
||||
"ocramius/proxy-manager": "~2.2.2",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"laminas/laminas-config": "^3.3",
|
||||
"laminas/laminas-config-aggregator": "^1.1",
|
||||
"laminas/laminas-diactoros": "^2.1.3",
|
||||
"laminas/laminas-inputfilter": "^2.10",
|
||||
"laminas/laminas-paginator": "^2.8",
|
||||
"laminas/laminas-servicemanager": "^3.4",
|
||||
"laminas/laminas-stdlib": "^3.2",
|
||||
"lcobucci/jwt": "^4.0@alpha <4.0@beta",
|
||||
"league/uri": "^6.2",
|
||||
"lstrojny/functional-php": "^1.9",
|
||||
"mezzio/mezzio": "^3.2",
|
||||
"mezzio/mezzio-fastroute": "^3.0",
|
||||
"mezzio/mezzio-helpers": "^5.3",
|
||||
"mezzio/mezzio-platesrenderer": "^2.1",
|
||||
"mezzio/mezzio-problem-details": "^1.1",
|
||||
"mezzio/mezzio-swoole": "^2.6.4",
|
||||
"monolog/monolog": "^2.0",
|
||||
"nikolaposa/monolog-factory": "^3.0",
|
||||
"ocramius/proxy-manager": "^2.7.0",
|
||||
"phly/phly-event-dispatcher": "^1.0",
|
||||
"php-middleware/request-id": "^4.0",
|
||||
"predis/predis": "^1.1",
|
||||
"shlinkio/shlink-installer": "^1.2.1",
|
||||
"symfony/console": "^4.3",
|
||||
"symfony/filesystem": "^4.3",
|
||||
"symfony/lock": "^4.3",
|
||||
"symfony/process": "^4.3",
|
||||
"theorchard/monolog-cascade": "^0.4",
|
||||
"zendframework/zend-config": "^3.3",
|
||||
"zendframework/zend-config-aggregator": "^1.1",
|
||||
"zendframework/zend-diactoros": "^2.1.3",
|
||||
"zendframework/zend-expressive": "^3.2",
|
||||
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||
"zendframework/zend-expressive-helpers": "^5.3",
|
||||
"zendframework/zend-expressive-platesrenderer": "^2.1",
|
||||
"zendframework/zend-expressive-swoole": "^2.4",
|
||||
"zendframework/zend-i18n": "^2.9",
|
||||
"zendframework/zend-inputfilter": "^2.10",
|
||||
"zendframework/zend-paginator": "^2.8",
|
||||
"zendframework/zend-servicemanager": "^3.4",
|
||||
"zendframework/zend-stdlib": "^3.2"
|
||||
"pugx/shortid-php": "^0.5",
|
||||
"ramsey/uuid": "^3.9",
|
||||
"shlinkio/shlink-common": "^3.3.0",
|
||||
"shlinkio/shlink-config": "^1.0",
|
||||
"shlinkio/shlink-event-dispatcher": "^1.4",
|
||||
"shlinkio/shlink-importer": "^2.0.1",
|
||||
"shlinkio/shlink-installer": "^5.1.0",
|
||||
"shlinkio/shlink-ip-geolocation": "^1.5",
|
||||
"symfony/console": "^5.1",
|
||||
"symfony/filesystem": "^5.1",
|
||||
"symfony/lock": "^5.1",
|
||||
"symfony/mercure": "^0.3.0",
|
||||
"symfony/process": "^5.1",
|
||||
"symfony/string": "^5.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"devster/ubench": "^2.0",
|
||||
"doctrine/data-fixtures": "^1.3",
|
||||
"dms/phpunit-arraysubset-asserts": "^0.2.0",
|
||||
"eaglewu/swoole-ide-helper": "dev-master",
|
||||
"filp/whoops": "^2.4",
|
||||
"infection/infection": "^0.12.2",
|
||||
"phpstan/phpstan": "^0.11.2",
|
||||
"phpunit/phpcov": "^6.0",
|
||||
"phpunit/phpunit": "^8.3",
|
||||
"infection/infection": "^0.20.0",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpstan/phpstan": "^0.12.52",
|
||||
"phpunit/php-code-coverage": "^9.2",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"shlinkio/php-coding-standard": "~1.2.2",
|
||||
"symfony/dotenv": "^4.3",
|
||||
"symfony/var-dumper": "^4.3",
|
||||
"zendframework/zend-component-installer": "^2.1",
|
||||
"zendframework/zend-expressive-tooling": "^1.2"
|
||||
"shlinkio/php-coding-standard": "~2.1.1",
|
||||
"shlinkio/shlink-test-utils": "^1.5",
|
||||
"symfony/var-dumper": "^5.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src",
|
||||
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
|
||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
|
||||
"Shlinkio\\Shlink\\Common\\": "module/Common/src",
|
||||
"Shlinkio\\Shlink\\EventDispatcher\\": "module/EventDispatcher/src"
|
||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
||||
},
|
||||
"files": [
|
||||
"module/Common/functions/functions.php",
|
||||
"module/EventDispatcher/functions/functions.php"
|
||||
"module/Core/functions/functions.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
@@ -89,13 +94,11 @@
|
||||
"ShlinkioTest\\Shlink\\Core\\": [
|
||||
"module/Core/test",
|
||||
"module/Core/test-db"
|
||||
],
|
||||
"ShlinkioTest\\Shlink\\Common\\": [
|
||||
"module/Common/test",
|
||||
"module/Common/test-db"
|
||||
],
|
||||
"ShlinkioTest\\Shlink\\EventDispatcher\\": "module/EventDispatcher/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"config/test/constants.php"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"ci": [
|
||||
@@ -104,11 +107,9 @@
|
||||
"@test:ci",
|
||||
"@infect:ci"
|
||||
],
|
||||
|
||||
"cs": "phpcs",
|
||||
"cs:fix": "phpcbf",
|
||||
"stan": "phpstan analyse module/*/src/ --level=5 -c phpstan.neon",
|
||||
|
||||
"stan": "phpstan analyse module/*/src/ module/*/config config docker/config data/migrations --level=6",
|
||||
"test": [
|
||||
"@test:unit",
|
||||
"@test:db",
|
||||
@@ -116,37 +117,39 @@
|
||||
],
|
||||
"test:ci": [
|
||||
"@test:unit:ci",
|
||||
"@test:db",
|
||||
"@test:api"
|
||||
"@test:db"
|
||||
],
|
||||
"test:unit": "phpdbg -qrr vendor/bin/phpunit --order-by=random --coverage-php build/coverage-unit.cov --testdox",
|
||||
"test:unit:ci": "phpdbg -qrr vendor/bin/phpunit --order-by=random --coverage-php build/coverage-unit.cov --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/phpunit.junit.xml --testdox",
|
||||
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
|
||||
"test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
||||
"test:db": [
|
||||
"@test:db:sqlite",
|
||||
"@test:db:sqlite:ci",
|
||||
"@test:db:mysql",
|
||||
"@test:db:postgres"
|
||||
"@test:db:maria",
|
||||
"@test:db:postgres",
|
||||
"@test:db:ms"
|
||||
],
|
||||
"test:db:sqlite": "APP_ENV=test phpdbg -qrr vendor/bin/phpunit --order-by=random -c phpunit-db.xml --coverage-php build/coverage-db.cov --testdox",
|
||||
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
||||
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
||||
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
|
||||
"test:db:maria": "DB_DRIVER=maria composer test:db:sqlite",
|
||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
||||
"test:api": "bin/test/run-api-tests.sh",
|
||||
|
||||
"test:pretty": [
|
||||
"@test",
|
||||
"phpdbg -qrr vendor/bin/phpcov merge build --html build/html"
|
||||
"test:unit:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit-html",
|
||||
"infect": "infection --threads=4 --min-msi=80 --log-verbosity=default --only-covered",
|
||||
"infect:ci:base": "@infect --skip-initial-tests",
|
||||
"infect:ci": [
|
||||
"@infect:ci:base --coverage=build/coverage-unit",
|
||||
"@infect:ci:base --coverage=build/coverage-db --test-framework-options=--configuration=phpunit-db.xml"
|
||||
],
|
||||
"test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --coverage-html build/coverage --order-by=random",
|
||||
|
||||
"infect": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered",
|
||||
"infect:ci": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --coverage=build",
|
||||
"infect:show": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --show-mutations",
|
||||
"infect:test": [
|
||||
"@test:unit:ci",
|
||||
"@test:db:sqlite:ci",
|
||||
"@infect:ci"
|
||||
]
|
||||
],
|
||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"check": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test\" and \"infect\"</>",
|
||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"</>",
|
||||
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
||||
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
||||
@@ -155,19 +158,20 @@
|
||||
"test:ci": "<fg=blue;options=bold>Runs all test suites, generating all needed reports and logs for CI envs</>",
|
||||
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
|
||||
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
|
||||
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL and PostgreSQL</>",
|
||||
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB, PostgreSQL and MsSQL</>",
|
||||
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
||||
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
||||
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
|
||||
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
||||
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
||||
"test:pretty": "<fg=blue;options=bold>Runs all test suites and generates an HTML code coverage report</>",
|
||||
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||
"infect": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",
|
||||
"infect:ci": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:show": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing and shows applied mutators</>",
|
||||
"infect:test": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>"
|
||||
"infect:test": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",
|
||||
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
"sort-packages": true,
|
||||
"platform-check": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'app_options' => [
|
||||
'name' => 'Shlink',
|
||||
'version' => '%SHLINK_VERSION%',
|
||||
'secret_key' => env('SECRET_KEY', ''),
|
||||
'disable_track_param' => null,
|
||||
],
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
@@ -6,11 +7,11 @@ return [
|
||||
'ip_address_resolution' => [
|
||||
'headers_to_inspect' => [
|
||||
'CF-Connecting-IP',
|
||||
'True-Client-IP',
|
||||
'X-Real-IP',
|
||||
'Forwarded',
|
||||
'X-Forwarded-For',
|
||||
'X-Forwarded',
|
||||
'Forwarded',
|
||||
'True-Client-IP',
|
||||
'X-Real-IP',
|
||||
'X-Cluster-Client-Ip',
|
||||
'Client-Ip',
|
||||
],
|
||||
@@ -1,11 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\ConfigAggregator\ConfigAggregator;
|
||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||
|
||||
return [
|
||||
|
||||
'debug' => false,
|
||||
ConfigAggregator::ENABLE_CACHE => true,
|
||||
|
||||
// Disabling config cache for cli, ensures it's never used for swoole and also that console commands don't generate
|
||||
// a cache file that's then used by non-swoole web executions
|
||||
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
||||
|
||||
];
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\ConfigAggregator\ConfigAggregator;
|
||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||
|
||||
return [
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory;
|
||||
use Zend\Expressive;
|
||||
use Zend\Expressive\Container;
|
||||
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
use GuzzleHttp\Client;
|
||||
use Mezzio\Container;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
ImplicitOptionsMiddleware::class => EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
||||
],
|
||||
|
||||
'delegators' => [
|
||||
Expressive\Application::class => [
|
||||
Mezzio\Application::class => [
|
||||
Container\ApplicationConfigInjectionDelegator::class,
|
||||
],
|
||||
],
|
||||
|
||||
'aliases' => [
|
||||
ClientInterface::class => Client::class,
|
||||
],
|
||||
|
||||
'lazy_services' => [
|
||||
'proxies_target_dir' => 'data/proxies',
|
||||
'proxies_namespace' => 'ShlinkProxy',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
@@ -12,7 +13,7 @@ return [
|
||||
],
|
||||
|
||||
'initializers' => [
|
||||
function (ContainerInterface $container, $instance) {
|
||||
function (ContainerInterface $container, $instance): void {
|
||||
if ($instance instanceof Log\LoggerAwareInterface) {
|
||||
$instance->setLogger($container->get(Log\LoggerInterface::class));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
@@ -8,11 +9,12 @@ return [
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'proxies_dir' => 'data/proxies',
|
||||
'load_mappings_using_functional_style' => true,
|
||||
],
|
||||
'connection' => [
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'user' => '',
|
||||
'password' => '',
|
||||
'dbname' => 'shlink',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
// When running tests, any mysql-specific option can interfere with other drivers
|
||||
$driverOptions = env('APP_ENV') === 'test' ? [] : [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
'connection' => [
|
||||
'user' => 'root',
|
||||
'password' => 'root',
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => 'shlink_db',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
'driverOptions' => $driverOptions,
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
33
config/autoload/error-handler.global.php
Normal file
33
config/autoload/error-handler.global.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
|
||||
use Shlinkio\Shlink\Common\Logger;
|
||||
|
||||
return [
|
||||
|
||||
'problem-details' => [
|
||||
'default_types_map' => [
|
||||
404 => 'NOT_FOUND',
|
||||
500 => 'INTERNAL_SERVER_ERROR',
|
||||
],
|
||||
],
|
||||
|
||||
'error_handler' => [
|
||||
'listeners' => [Logger\ErrorLogger::class],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'delegators' => [
|
||||
ErrorHandler::class => [
|
||||
Logger\ErrorHandlerListenerAttachingDelegator::class,
|
||||
],
|
||||
ProblemDetailsMiddleware::class => [
|
||||
Logger\ErrorHandlerListenerAttachingDelegator::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\Expressive\Container\WhoopsErrorResponseGeneratorFactory;
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
'invokables' => [
|
||||
'Zend\Expressive\Whoops' => Whoops\Run::class,
|
||||
'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
|
||||
],
|
||||
],
|
||||
|
||||
'whoops' => [
|
||||
'json_exceptions' => [
|
||||
'display' => true,
|
||||
'show_trace' => true,
|
||||
'ajax_only' => true,
|
||||
],
|
||||
],
|
||||
|
||||
'error_handler' => [
|
||||
'plugins' => [
|
||||
'factories' => [
|
||||
'text/html' => WhoopsErrorResponseGeneratorFactory::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -1,12 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'geolite2' => [
|
||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||
'temp_dir' => sys_get_temp_dir(),
|
||||
'download_from' => 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz',
|
||||
'temp_dir' => __DIR__ . '/../../data',
|
||||
'license_key' => 'G4Lm0C60yJsnkdPi', // Deprecated. Remove hardcoded license on v3
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,47 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
use Shlinkio\Shlink\Installer\Config\Option;
|
||||
|
||||
return [
|
||||
|
||||
'installer_plugins_expected_config' => [
|
||||
Plugin\LanguageConfigCustomizer::class => [
|
||||
Plugin\LanguageConfigCustomizer::DEFAULT_LANG,
|
||||
'installer' => [
|
||||
'enabled_options' => [
|
||||
Option\Database\DatabaseDriverConfigOption::class,
|
||||
Option\Database\DatabaseNameConfigOption::class,
|
||||
Option\Database\DatabaseHostConfigOption::class,
|
||||
Option\Database\DatabasePortConfigOption::class,
|
||||
Option\Database\DatabaseUserConfigOption::class,
|
||||
Option\Database\DatabasePasswordConfigOption::class,
|
||||
Option\Database\DatabaseSqlitePathConfigOption::class,
|
||||
Option\Database\DatabaseMySqlOptionsConfigOption::class,
|
||||
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
||||
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
||||
Option\UrlShortener\ValidateUrlConfigOption::class,
|
||||
Option\Visit\VisitsWebhooksConfigOption::class,
|
||||
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
||||
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
||||
Option\Redirect\Regular404RedirectConfigOption::class,
|
||||
Option\DisableTrackParamConfigOption::class,
|
||||
Option\Visit\CheckVisitsThresholdConfigOption::class,
|
||||
Option\Visit\VisitsThresholdConfigOption::class,
|
||||
Option\BasePathConfigOption::class,
|
||||
Option\Worker\TaskWorkerNumConfigOption::class,
|
||||
Option\Worker\WebWorkerNumConfigOption::class,
|
||||
Option\RedisServersConfigOption::class,
|
||||
Option\UrlShortener\ShortCodeLengthOption::class,
|
||||
Option\Mercure\EnableMercureConfigOption::class,
|
||||
Option\Mercure\MercurePublicUrlConfigOption::class,
|
||||
Option\Mercure\MercureInternalUrlConfigOption::class,
|
||||
Option\Mercure\MercureJwtSecretConfigOption::class,
|
||||
Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
|
||||
Option\UrlShortener\IpAnonymizationConfigOption::class,
|
||||
Option\UrlShortener\RedirectStatusCodeConfigOption::class,
|
||||
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
||||
],
|
||||
|
||||
Plugin\UrlShortenerConfigCustomizer::class => [
|
||||
Plugin\UrlShortenerConfigCustomizer::SCHEMA,
|
||||
Plugin\UrlShortenerConfigCustomizer::HOSTNAME,
|
||||
Plugin\UrlShortenerConfigCustomizer::CHARS,
|
||||
Plugin\UrlShortenerConfigCustomizer::VALIDATE_URL,
|
||||
Plugin\UrlShortenerConfigCustomizer::ENABLE_NOT_FOUND_REDIRECTION,
|
||||
Plugin\UrlShortenerConfigCustomizer::NOT_FOUND_REDIRECT_TO,
|
||||
],
|
||||
|
||||
Plugin\ApplicationConfigCustomizer::class => [
|
||||
Plugin\ApplicationConfigCustomizer::SECRET,
|
||||
Plugin\ApplicationConfigCustomizer::DISABLE_TRACK_PARAM,
|
||||
Plugin\ApplicationConfigCustomizer::CHECK_VISITS_THRESHOLD,
|
||||
Plugin\ApplicationConfigCustomizer::VISITS_THRESHOLD,
|
||||
],
|
||||
|
||||
Plugin\DatabaseConfigCustomizer::class => [
|
||||
Plugin\DatabaseConfigCustomizer::DRIVER,
|
||||
Plugin\DatabaseConfigCustomizer::NAME,
|
||||
Plugin\DatabaseConfigCustomizer::USER,
|
||||
Plugin\DatabaseConfigCustomizer::PASSWORD,
|
||||
Plugin\DatabaseConfigCustomizer::HOST,
|
||||
Plugin\DatabaseConfigCustomizer::PORT,
|
||||
],
|
||||
],
|
||||
|
||||
'installation_commands' => [
|
||||
'db_create_schema' => [
|
||||
'command' => 'bin/cli db:create',
|
||||
],
|
||||
'db_migrate' => [
|
||||
'command' => 'bin/cli db:migrate',
|
||||
'installation_commands' => [
|
||||
'db_create_schema' => [
|
||||
'command' => 'bin/cli db:create',
|
||||
],
|
||||
'db_migrate' => [
|
||||
'command' => 'bin/cli db:migrate',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
use Shlinkio\Shlink\Common\Lock\RetryLockStoreDelegatorFactory;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||
use Symfony\Component\Lock;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
|
||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
||||
|
||||
return [
|
||||
|
||||
@@ -17,18 +20,21 @@ return [
|
||||
'factories' => [
|
||||
Lock\Store\FlockStore::class => ConfigAbstractFactory::class,
|
||||
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
||||
Lock\Factory::class => ConfigAbstractFactory::class,
|
||||
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
||||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||
'lock_store' => Lock\Store\FlockStore::class,
|
||||
'lock_store' => 'local_lock_store',
|
||||
|
||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||
],
|
||||
'delegators' => [
|
||||
Lock\Store\RedisStore::class => [
|
||||
RetryLockStoreDelegatorFactory::class,
|
||||
],
|
||||
Lock\Factory::class => [
|
||||
Lock\LockFactory::class => [
|
||||
LoggerAwareDelegatorFactory::class,
|
||||
],
|
||||
],
|
||||
@@ -37,7 +43,8 @@ return [
|
||||
ConfigAbstractFactory::class => [
|
||||
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
||||
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
||||
Lock\Factory::class => ['lock_store'],
|
||||
Lock\LockFactory::class => ['lock_store'],
|
||||
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,72 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Formatter;
|
||||
use Monolog\Handler;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Processor;
|
||||
use MonologFactory\DiContainerLoggerFactory;
|
||||
use PhpMiddleware\RequestId;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
use const PHP_EOL;
|
||||
|
||||
$processors = [
|
||||
'exception_with_new_line' => [
|
||||
'name' => Common\Logger\Processor\ExceptionWithNewLineProcessor::class,
|
||||
],
|
||||
'psr3' => [
|
||||
'name' => Processor\PsrLogMessageProcessor::class,
|
||||
],
|
||||
'request_id' => RequestId\MonologProcessor::class,
|
||||
];
|
||||
$formatter = [
|
||||
'name' => Formatter\LineFormatter::class,
|
||||
'params' => [
|
||||
'format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%' . PHP_EOL,
|
||||
'allow_inline_line_breaks' => true,
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'logger' => [
|
||||
'formatters' => [
|
||||
'dashed' => [
|
||||
'format' => '[%datetime%] %channel%.%level_name% - %message%' . PHP_EOL,
|
||||
'include_stacktraces' => true,
|
||||
'Shlink' => [
|
||||
'name' => 'Shlink',
|
||||
'handlers' => [
|
||||
'shlink_handler' => [
|
||||
'name' => Handler\RotatingFileHandler::class,
|
||||
'params' => [
|
||||
'level' => Logger::INFO,
|
||||
'filename' => 'data/log/shlink_log.log',
|
||||
'max_files' => 30,
|
||||
'file_permission' => 0666,
|
||||
],
|
||||
'formatter' => $formatter,
|
||||
],
|
||||
],
|
||||
'processors' => $processors,
|
||||
],
|
||||
|
||||
'handlers' => [
|
||||
'shlink_rotating_handler' => [
|
||||
'class' => RotatingFileHandler::class,
|
||||
'level' => Logger::INFO,
|
||||
'filename' => 'data/log/shlink_log.log',
|
||||
'max_files' => 30,
|
||||
'formatter' => 'dashed',
|
||||
],
|
||||
'access_handler' => [
|
||||
'class' => StreamHandler::class,
|
||||
'level' => Logger::INFO,
|
||||
'stream' => 'php://stdout',
|
||||
],
|
||||
],
|
||||
|
||||
'processors' => [
|
||||
'exception_with_new_line' => [
|
||||
'class' => Common\Logger\Processor\ExceptionWithNewLineProcessor::class,
|
||||
],
|
||||
'psr3' => [
|
||||
'class' => Processor\PsrLogMessageProcessor::class,
|
||||
],
|
||||
],
|
||||
|
||||
'loggers' => [
|
||||
'Shlink' => [
|
||||
'handlers' => ['shlink_rotating_handler'],
|
||||
'processors' => ['exception_with_new_line', 'psr3'],
|
||||
],
|
||||
'Access' => [
|
||||
'handlers' => ['access_handler'],
|
||||
'processors' => ['exception_with_new_line', 'psr3'],
|
||||
'Access' => [
|
||||
'name' => 'Access',
|
||||
'handlers' => [
|
||||
'access_handler' => [
|
||||
'name' => Handler\StreamHandler::class,
|
||||
'params' => [
|
||||
'level' => Logger::INFO,
|
||||
'stream' => 'php://stdout',
|
||||
],
|
||||
'formatter' => $formatter,
|
||||
],
|
||||
],
|
||||
'processors' => $processors,
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
'Logger_Shlink' => Common\Logger\LoggerFactory::class,
|
||||
'Logger_Access' => Common\Logger\LoggerFactory::class,
|
||||
'Logger_Shlink' => [DiContainerLoggerFactory::class, 'Shlink'],
|
||||
'Logger_Access' => [DiContainerLoggerFactory::class, 'Access'],
|
||||
],
|
||||
'aliases' => [
|
||||
'logger' => 'Logger_Shlink',
|
||||
Logger::class => 'Logger_Shlink',
|
||||
LoggerInterface::class => 'Logger_Shlink',
|
||||
],
|
||||
],
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'mezzio-swoole' => [
|
||||
'swoole-http-server' => [
|
||||
'logger' => [
|
||||
'logger-name' => 'Logger_Access',
|
||||
'format' => '%h %l %u "%r" %>s %b',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
@@ -7,34 +8,28 @@ use Monolog\Logger;
|
||||
$isSwoole = extension_loaded('swoole');
|
||||
|
||||
// For swoole, send logs to standard output
|
||||
$logger = $isSwoole ? [
|
||||
'handlers' => [
|
||||
'shlink_rotating_handler' => [
|
||||
'level' => Logger::EMERGENCY, // This basically disables regular file logs
|
||||
],
|
||||
'shlink_stdout_handler' => [
|
||||
'class' => StreamHandler::class,
|
||||
$handler = $isSwoole
|
||||
? [
|
||||
'name' => StreamHandler::class,
|
||||
'params' => [
|
||||
'level' => Logger::DEBUG,
|
||||
'stream' => 'php://stdout',
|
||||
'formatter' => 'dashed',
|
||||
],
|
||||
],
|
||||
|
||||
'loggers' => [
|
||||
'Shlink' => [
|
||||
'handlers' => ['shlink_stdout_handler'],
|
||||
],
|
||||
],
|
||||
] : [
|
||||
'handlers' => [
|
||||
'shlink_rotating_handler' => [
|
||||
]
|
||||
: [
|
||||
'params' => [
|
||||
'level' => Logger::DEBUG,
|
||||
],
|
||||
],
|
||||
];
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'logger' => $logger,
|
||||
'logger' => [
|
||||
'Shlink' => [
|
||||
'handlers' => [
|
||||
'shlink_handler' => $handler,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
36
config/autoload/mercure.global.php
Normal file
36
config/autoload/mercure.global.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
||||
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
||||
use Symfony\Component\Mercure\Publisher;
|
||||
use Symfony\Component\Mercure\PublisherInterface;
|
||||
|
||||
return [
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => null,
|
||||
'internal_hub_url' => null,
|
||||
'jwt_secret' => null,
|
||||
'jwt_issuer' => 'Shlink',
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'delegators' => [
|
||||
LcobucciJwtProvider::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
Publisher::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
],
|
||||
'lazy_services' => [
|
||||
'class_map' => [
|
||||
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
||||
Publisher::class => PublisherInterface::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
13
config/autoload/mercure.local.php.dist
Normal file
13
config/autoload/mercure.local.php.dist
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => 'http://localhost:8001',
|
||||
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
||||
'jwt_secret' => 'mercure_jwt_key',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,58 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Zend\Expressive;
|
||||
use Zend\Stratigility\Middleware\ErrorHandler;
|
||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||
use Mezzio\Helper;
|
||||
use Mezzio\ProblemDetails;
|
||||
use Mezzio\Router;
|
||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'pre-routing' => [
|
||||
'error-handler' => [
|
||||
'middleware' => [
|
||||
Helper\ContentLengthMiddleware::class,
|
||||
ErrorHandler::class,
|
||||
Expressive\Helper\ContentLengthMiddleware::class,
|
||||
Common\Middleware\CloseDbConnectionMiddleware::class,
|
||||
],
|
||||
'priority' => 12,
|
||||
],
|
||||
'pre-routing-rest' => [
|
||||
'error-handler-rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Rest\Middleware\PathVersionMiddleware::class,
|
||||
Rest\Middleware\ShortUrl\ShortCodePathMiddleware::class,
|
||||
Rest\Middleware\CrossDomainMiddleware::class,
|
||||
RequestIdMiddleware::class,
|
||||
ProblemDetails\ProblemDetailsMiddleware::class,
|
||||
],
|
||||
],
|
||||
|
||||
'pre-routing' => [
|
||||
'middleware' => [
|
||||
Common\Middleware\CloseDbConnectionMiddleware::class,
|
||||
],
|
||||
'priority' => 11,
|
||||
],
|
||||
|
||||
'routing' => [
|
||||
'middleware' => [
|
||||
Expressive\Router\Middleware\RouteMiddleware::class,
|
||||
Router\Middleware\RouteMiddleware::class,
|
||||
Router\Middleware\ImplicitHeadMiddleware::class,
|
||||
],
|
||||
'priority' => 10,
|
||||
],
|
||||
|
||||
'rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Rest\Middleware\CrossDomainMiddleware::class,
|
||||
Expressive\Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||
Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||
Rest\Middleware\BodyParserMiddleware::class,
|
||||
Rest\Middleware\AuthenticationMiddleware::class,
|
||||
],
|
||||
'priority' => 5,
|
||||
],
|
||||
|
||||
'post-routing' => [
|
||||
'dispatch' => [
|
||||
'middleware' => [
|
||||
Expressive\Router\Middleware\DispatchMiddleware::class,
|
||||
|
||||
// Only if a not found error is triggered, set-up the locale to be used
|
||||
Common\Middleware\LocaleMiddleware::class,
|
||||
Core\Response\NotFoundHandler::class,
|
||||
Router\Middleware\DispatchMiddleware::class,
|
||||
],
|
||||
],
|
||||
|
||||
'not-found-rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
ProblemDetails\ProblemDetailsNotFoundHandler::class,
|
||||
],
|
||||
],
|
||||
'not-found' => [
|
||||
'middleware' => [
|
||||
Core\ErrorHandler\NotFoundRedirectHandler::class,
|
||||
Core\ErrorHandler\NotFoundTemplateHandler::class,
|
||||
],
|
||||
'priority' => 1,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'preview_generation' => [
|
||||
'files_location' => 'data/cache',
|
||||
],
|
||||
|
||||
];
|
||||
13
config/autoload/redirects.global.php
Normal file
13
config/autoload/redirects.global.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'not_found_redirects' => [
|
||||
'invalid_short_url' => null,
|
||||
'regular_404' => null,
|
||||
'base_url' => null,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,13 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'redis' => [
|
||||
'servers' => 'tcp://shlink_redis:6379',
|
||||
// 'servers' => [
|
||||
// 'tcp://shlink_redis:6379',
|
||||
// ],
|
||||
'cache' => [
|
||||
'redis' => [
|
||||
'servers' => 'tcp://shlink_redis:6379',
|
||||
// 'servers' => [
|
||||
// 'tcp://shlink_redis:6379',
|
||||
// ],
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
|
||||
38
config/autoload/request_id.global.php
Normal file
38
config/autoload/request_id.global.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use PhpMiddleware\RequestId;
|
||||
|
||||
return [
|
||||
|
||||
'request_id' => [
|
||||
'allow_override' => true,
|
||||
'header_name' => 'X-Request-Id',
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
RequestId\Generator\RamseyUuid4StaticGenerator::class => InvokableFactory::class,
|
||||
RequestId\RequestIdProviderFactory::class => ConfigAbstractFactory::class,
|
||||
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
||||
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
RequestId\RequestIdProviderFactory::class => [
|
||||
RequestId\Generator\RamseyUuid4StaticGenerator::class,
|
||||
'config.request_id.allow_override',
|
||||
'config.request_id.header_name',
|
||||
],
|
||||
RequestId\RequestIdMiddleware::class => [
|
||||
RequestId\RequestIdProviderFactory::class,
|
||||
'config.request_id.header_name',
|
||||
],
|
||||
RequestId\MonologProcessor::class => [RequestId\RequestIdMiddleware::class],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\Expressive\Router\FastRouteRouter;
|
||||
use Mezzio\Router\FastRouteRouter;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'base_path' => '',
|
||||
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
||||
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<?php
|
||||
use Zend\Expressive\Router\FastRouteRouter;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Mezzio\Router\FastRouteRouter;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
// 'base_path' => '',
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
||||
],
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'enable_coroutine' => true,
|
||||
'mezzio-swoole' => [
|
||||
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
||||
'enable_coroutine' => false,
|
||||
|
||||
'swoole-http-server' => [
|
||||
'host' => '0.0.0.0',
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\Expressive\Swoole\HotCodeReload\FileWatcher\InotifyFileWatcher;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'mezzio-swoole' => [
|
||||
'hot-code-reload' => [
|
||||
'enable' => true,
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
InotifyFileWatcher::class => InvokableFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common;
|
||||
|
||||
return [
|
||||
|
||||
'translator' => [
|
||||
'locale' => Common\env('DEFAULT_LOCALE', 'en'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,23 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
||||
|
||||
return [
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => env('SHORTENED_URL_SCHEMA', 'http'),
|
||||
'hostname' => env('SHORTENED_URL_HOSTNAME'),
|
||||
],
|
||||
'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortenerOptions::DEFAULT_CHARS),
|
||||
'validate_url' => true,
|
||||
'not_found_short_url' => [
|
||||
'enable_redirection' => false,
|
||||
'redirect_to' => null,
|
||||
'schema' => 'https',
|
||||
'hostname' => '',
|
||||
],
|
||||
'validate_url' => false,
|
||||
'anonymize_remote_addr' => true,
|
||||
'visits_webhooks' => [],
|
||||
'default_short_codes_length' => DEFAULT_SHORT_CODES_LENGTH,
|
||||
'redirect_status_code' => DEFAULT_REDIRECT_STATUS_CODE,
|
||||
'redirect_cache_lifetime' => DEFAULT_REDIRECT_CACHE_LIFETIME,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
14
config/autoload/url-shortener.local.php.dist
Normal file
14
config/autoload/url-shortener.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => 'http',
|
||||
'hostname' => 'localhost:8080',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'wkhtmltopdf' => [
|
||||
'images' => [
|
||||
'binary' => __DIR__ . '/../../bin/wkhtmltoimage',
|
||||
'type' => 'jpg',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/** @var ContainerInterface|ServiceManager $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
$em = $container->get(EntityManager::class);
|
||||
return (function () {
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
$em = $container->get(EntityManager::class);
|
||||
|
||||
return ConsoleRunner::createHelperSet($em);
|
||||
return ConsoleRunner::createHelperSet($em);
|
||||
})();
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Acelaya\ExpressiveErrorHandler;
|
||||
use Zend\ConfigAggregator;
|
||||
use Zend\Expressive;
|
||||
use Laminas\ConfigAggregator;
|
||||
use Laminas\Diactoros;
|
||||
use Mezzio;
|
||||
use Mezzio\ProblemDetails;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return (new ConfigAggregator\ConfigAggregator([
|
||||
Expressive\ConfigProvider::class,
|
||||
Expressive\Router\ConfigProvider::class,
|
||||
Expressive\Router\FastRouteRouter\ConfigProvider::class,
|
||||
Expressive\Plates\ConfigProvider::class,
|
||||
Expressive\Swoole\ConfigProvider::class,
|
||||
ExpressiveErrorHandler\ConfigProvider::class,
|
||||
Mezzio\ConfigProvider::class,
|
||||
Mezzio\Router\ConfigProvider::class,
|
||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||
Mezzio\Plates\ConfigProvider::class,
|
||||
Mezzio\Swoole\ConfigProvider::class,
|
||||
ProblemDetails\ConfigProvider::class,
|
||||
Diactoros\ConfigProvider::class,
|
||||
Common\ConfigProvider::class,
|
||||
Config\ConfigProvider::class,
|
||||
Importer\ConfigProvider::class,
|
||||
IpGeolocation\ConfigProvider::class,
|
||||
EventDispatcher\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
CLI\ConfigProvider::class,
|
||||
Rest\ConfigProvider::class,
|
||||
EventDispatcher\ConfigProvider::class,
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||
env('APP_ENV') === 'test'
|
||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
||||
: new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
||||
: new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
||||
], 'data/cache/app_config.php', [
|
||||
Core\SimplifiedConfigParser::class,
|
||||
Core\Config\SimplifiedConfigParser::class,
|
||||
Core\Config\BasePathPrefixer::class,
|
||||
Core\Config\DeprecatedConfigParser::class,
|
||||
]))->getMergedConfig();
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
use Laminas\ServiceManager\ServiceManager;
|
||||
use Symfony\Component\Lock;
|
||||
|
||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
// If the Dotenv class exists, load env vars and enable errors
|
||||
if (class_exists(Dotenv::class)) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
$dotenv = new Dotenv();
|
||||
$dotenv->load(__DIR__ . '/../.env');
|
||||
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
||||
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
||||
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
||||
}
|
||||
|
||||
// Build container
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$container = new ServiceManager($config['dependencies']);
|
||||
$container->setService('config', $config);
|
||||
return $container;
|
||||
return (function () {
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$container = new ServiceManager($config['dependencies']);
|
||||
$container->setService('config', $config);
|
||||
|
||||
return $container;
|
||||
})();
|
||||
|
||||
15
config/run.php
Normal file
15
config/run.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Mezzio\Application;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
|
||||
return function (bool $isCli = false): void {
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
$app = $container->get($isCli ? CliApp::class : Application::class);
|
||||
|
||||
$app->run();
|
||||
};
|
||||
@@ -1,27 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common;
|
||||
namespace Shlinkio\Shlink\TestUtils;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function file_exists;
|
||||
use function touch;
|
||||
use function register_shutdown_function;
|
||||
use function sprintf;
|
||||
|
||||
// Create an empty .env file
|
||||
if (! file_exists('.env')) {
|
||||
touch('.env');
|
||||
}
|
||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = require __DIR__ . '/../container.php';
|
||||
$testHelper = $container->get(TestHelper::class);
|
||||
$testHelper = $container->get(Helper\TestHelper::class);
|
||||
$config = $container->get('config');
|
||||
$em = $container->get(EntityManager::class);
|
||||
$httpClient = $container->get('shlink_test_api_client');
|
||||
|
||||
// Start code coverage collecting on swoole process, and stop it when process shuts down
|
||||
$httpClient->request('GET', sprintf('http://%s:%s/api-tests/start-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT));
|
||||
register_shutdown_function(function () use ($httpClient): void {
|
||||
$httpClient->request(
|
||||
'GET',
|
||||
sprintf('http://%s:%s/api-tests/stop-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
||||
);
|
||||
});
|
||||
|
||||
$testHelper->createTestDb();
|
||||
ApiTest\ApiTestCase::setApiClient($container->get('shlink_test_api_client'));
|
||||
ApiTest\ApiTestCase::setSeedFixturesCallback(function () use ($testHelper, $em, $config) {
|
||||
$testHelper->seedFixtures($em, $config['data_fixtures'] ?? []);
|
||||
});
|
||||
ApiTest\ApiTestCase::setApiClient($httpClient);
|
||||
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common;
|
||||
namespace Shlinkio\Shlink\TestUtils;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function file_exists;
|
||||
use function touch;
|
||||
|
||||
// Create an empty .env file
|
||||
if (! file_exists('.env')) {
|
||||
touch('.env');
|
||||
}
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = require __DIR__ . '/../container.php';
|
||||
$container->get(TestHelper::class)->createTestDb();
|
||||
$container->get(Helper\TestHelper::class)->createTestDb();
|
||||
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));
|
||||
|
||||
8
config/test/constants.php
Normal file
8
config/test/constants.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink;
|
||||
|
||||
const SWOOLE_TESTING_HOST = '127.0.0.1';
|
||||
const SWOOLE_TESTING_PORT = 9999;
|
||||
@@ -1,54 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink;
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use Laminas\Stdlib\Glob;
|
||||
use PDO;
|
||||
use Zend\ConfigAggregator\ConfigAggregator;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
use PHPUnit\Runner\Version;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||
use SebastianBergmann\CodeCoverage\Filter;
|
||||
use SebastianBergmann\CodeCoverage\Report\PHP;
|
||||
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
||||
|
||||
use function Laminas\Stratigility\middleware;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
use function sprintf;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
$swooleTestingHost = '127.0.0.1';
|
||||
$swooleTestingPort = 9999;
|
||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
||||
|
||||
$buildDbConnection = function () {
|
||||
$isApiTest = env('TEST_ENV') === 'api';
|
||||
if ($isApiTest) {
|
||||
$filter = new Filter();
|
||||
foreach (Glob::glob(__DIR__ . '/../../module/*/src') as $item) {
|
||||
$filter->includeDirectory($item);
|
||||
}
|
||||
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
||||
}
|
||||
|
||||
$buildDbConnection = function (): array {
|
||||
$driver = env('DB_DRIVER', 'sqlite');
|
||||
$isCi = env('TRAVIS', false);
|
||||
$getMysqlHost = fn (string $driver) => sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria');
|
||||
$getCiMysqlPort = fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
||||
|
||||
switch ($driver) {
|
||||
case 'sqlite':
|
||||
return [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
||||
];
|
||||
case 'mysql':
|
||||
return [
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db',
|
||||
'user' => 'root',
|
||||
'password' => $isCi ? '' : 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
];
|
||||
case 'postgres':
|
||||
return [
|
||||
'driver' => 'pdo_pgsql',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
||||
'user' => 'postgres',
|
||||
'password' => $isCi ? '' : 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
$driverConfigMap = [
|
||||
'sqlite' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
||||
],
|
||||
'mysql' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => $isCi ? '127.0.0.1' : $getMysqlHost($driver),
|
||||
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
||||
'user' => 'root',
|
||||
'password' => 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||||
],
|
||||
],
|
||||
'postgres' => [
|
||||
'driver' => 'pdo_pgsql',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
||||
'port' => $isCi ? '5433' : '5432',
|
||||
'user' => 'postgres',
|
||||
'password' => 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
'mssql' => [
|
||||
'driver' => 'pdo_sqlsrv',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_ms',
|
||||
'user' => 'sa',
|
||||
'password' => 'Passw0rd!',
|
||||
'dbname' => 'shlink_test',
|
||||
],
|
||||
];
|
||||
$driverConfigMap['maria'] = $driverConfigMap['mysql'];
|
||||
|
||||
return $driverConfigMap[$driver] ?? [];
|
||||
};
|
||||
|
||||
return [
|
||||
@@ -61,30 +90,66 @@ return [
|
||||
'schema' => 'http',
|
||||
'hostname' => 'doma.in',
|
||||
],
|
||||
'validate_url' => true,
|
||||
],
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'mezzio-swoole' => [
|
||||
'enable_coroutine' => false,
|
||||
'swoole-http-server' => [
|
||||
'host' => $swooleTestingHost,
|
||||
'port' => $swooleTestingPort,
|
||||
'host' => SWOOLE_TESTING_HOST,
|
||||
'port' => SWOOLE_TESTING_PORT,
|
||||
'process-name' => 'shlink_test',
|
||||
'options' => [
|
||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
||||
'worker_num' => 1,
|
||||
'task_worker_num' => 1,
|
||||
'enable_coroutine' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'routes' => !$isApiTest ? [] : [
|
||||
[
|
||||
'name' => 'start_collecting_coverage',
|
||||
'path' => '/api-tests/start-coverage',
|
||||
'middleware' => middleware(static function () use (&$coverage) {
|
||||
if ($coverage) {
|
||||
$coverage->start('API tests');
|
||||
}
|
||||
return new EmptyResponse();
|
||||
}),
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'dump_coverage',
|
||||
'path' => '/api-tests/stop-coverage',
|
||||
'middleware' => middleware(static function () use (&$coverage) {
|
||||
if ($coverage) {
|
||||
$basePath = __DIR__ . '/../../build/coverage-api';
|
||||
$coverage->stop();
|
||||
(new PHP())->process($coverage, $basePath . '.cov');
|
||||
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
||||
}
|
||||
|
||||
return new EmptyResponse();
|
||||
}),
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
],
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => null,
|
||||
'internal_hub_url' => null,
|
||||
'jwt_secret' => null,
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'services' => [
|
||||
'shlink_test_api_client' => new Client([
|
||||
'base_uri' => sprintf('http://%s:%s/', $swooleTestingHost, $swooleTestingPort),
|
||||
'base_uri' => sprintf('http://%s:%s/', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
||||
'http_errors' => false,
|
||||
]),
|
||||
],
|
||||
'factories' => [
|
||||
Common\TestHelper::class => InvokableFactory::class,
|
||||
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
9
data/infra/ci/install-ms-odbc.sh
Executable file
9
data/infra/ci/install-ms-odbc.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
||||
curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
|
||||
apt-get update
|
||||
ACCEPT_EULA=Y apt-get install msodbcsql17
|
||||
apt-get install unixodbc-dev
|
||||
2
data/infra/database_maria/.gitignore
vendored
Executable file
2
data/infra/database_maria/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -11,7 +11,7 @@ server {
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# Description: Shlink non-blocking server with swoole
|
||||
### END INIT INFO
|
||||
|
||||
SCRIPT=/path/to/shlink/vendor/bin/zend-expressive-swoole\ start
|
||||
SCRIPT=/path/to/shlink/vendor/bin/mezzio-swoole\ start
|
||||
RUNAS=root
|
||||
|
||||
PIDFILE=/var/run/shlink_swoole.pid
|
||||
|
||||
17
data/infra/mercure_proxy_vhost.conf
Normal file
17
data/infra/mercure_proxy_vhost.conf
Normal file
@@ -0,0 +1,17 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
|
||||
error_log /home/shlink/www/data/infra/nginx/mercure_proxy.error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://shlink_mercure;
|
||||
proxy_read_timeout 24h;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
## Be sure to set USE_FORWARDED_HEADERS=1 to allow the hub to use those headers ##
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,38 @@
|
||||
FROM php:7.3.1-fpm-alpine3.8
|
||||
FROM php:7.4.11-fpm-alpine3.12
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.16
|
||||
ENV APCU_BC_VERSION 1.0.4
|
||||
ENV XDEBUG_VERSION "2.7.0RC1"
|
||||
ENV APCU_VERSION 5.1.18
|
||||
ENV APCU_BC_VERSION 1.0.5
|
||||
|
||||
RUN apk update
|
||||
|
||||
# Install common php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install iconv
|
||||
RUN docker-php-ext-install mbstring
|
||||
RUN docker-php-ext-install calendar
|
||||
|
||||
RUN apk add --no-cache --virtual sqlite-libs
|
||||
RUN apk add --no-cache --virtual sqlite-dev
|
||||
RUN apk add --no-cache oniguruma-dev
|
||||
RUN docker-php-ext-install mbstring
|
||||
|
||||
RUN apk add --no-cache sqlite-libs
|
||||
RUN apk add --no-cache sqlite-dev
|
||||
RUN docker-php-ext-install pdo_sqlite
|
||||
|
||||
RUN apk add --no-cache --virtual icu-dev
|
||||
RUN apk add --no-cache icu-dev
|
||||
RUN docker-php-ext-install intl
|
||||
|
||||
RUN apk add --no-cache --virtual libzip-dev zlib-dev
|
||||
RUN apk add --no-cache libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache --virtual libpng-dev
|
||||
RUN apk add --no-cache libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
RUN apk add --no-cache gmp-dev
|
||||
RUN docker-php-ext-install gmp
|
||||
|
||||
# Install APCu extension
|
||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
||||
@@ -53,20 +57,17 @@ RUN rm /tmp/apcu_bc.tar.gz
|
||||
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
||||
RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||
|
||||
# Install xdebug
|
||||
ADD https://pecl.php.net/get/xdebug-$XDEBUG_VERSION /tmp/xdebug.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/xdebug\
|
||||
&& tar xf /tmp/xdebug.tar.gz -C /usr/src/php/ext/xdebug --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure xdebug\
|
||||
&& docker-php-ext-install xdebug
|
||||
# cleanup
|
||||
RUN rm /tmp/xdebug.tar.gz
|
||||
# Install pcov and sqlsrv driver
|
||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||
pecl install pdo_sqlsrv pcov && \
|
||||
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||
apk del .phpize-deps && \
|
||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
||||
|
||||
# Install composer
|
||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
RUN chmod +x composer.phar
|
||||
RUN mv composer.phar /usr/local/bin/composer
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
# Make home directory writable by anyone
|
||||
RUN chmod 777 /home
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
date.timezone = Europe/Madrid
|
||||
display_errors=On
|
||||
error_reporting=-1
|
||||
memory_limit=-1
|
||||
log_errors_max_len=0
|
||||
zend.assertions=1
|
||||
assert.exception=1
|
||||
pcov.enabled=1
|
||||
pcov.directory=module
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
FROM php:7.3.1-cli-alpine3.8
|
||||
FROM php:7.4.11-alpine3.12
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.16
|
||||
ENV APCU_BC_VERSION 1.0.4
|
||||
ENV APCU_VERSION 5.1.18
|
||||
ENV APCU_BC_VERSION 1.0.5
|
||||
ENV INOTIFY_VERSION 2.0.0
|
||||
ENV SWOOLE_VERSION 4.5.5
|
||||
|
||||
RUN apk update
|
||||
|
||||
# Install common php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install iconv
|
||||
RUN docker-php-ext-install mbstring
|
||||
RUN docker-php-ext-install calendar
|
||||
|
||||
RUN apk add --no-cache --virtual sqlite-libs
|
||||
RUN apk add --no-cache --virtual sqlite-dev
|
||||
RUN apk add --no-cache oniguruma-dev
|
||||
RUN docker-php-ext-install mbstring
|
||||
|
||||
RUN apk add --no-cache sqlite-libs
|
||||
RUN apk add --no-cache sqlite-dev
|
||||
RUN docker-php-ext-install pdo_sqlite
|
||||
|
||||
RUN apk add --no-cache --virtual icu-dev
|
||||
RUN apk add --no-cache icu-dev
|
||||
RUN docker-php-ext-install intl
|
||||
|
||||
RUN apk add --no-cache --virtual libzip-dev zlib-dev
|
||||
RUN apk add --no-cache libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache --virtual libpng-dev
|
||||
RUN apk add --no-cache libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
RUN apk add --no-cache gmp-dev
|
||||
RUN docker-php-ext-install gmp
|
||||
|
||||
# Install APCu extension
|
||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
||||
@@ -63,17 +69,17 @@ RUN docker-php-ext-configure inotify\
|
||||
# cleanup
|
||||
RUN rm /tmp/inotify.tar.gz
|
||||
|
||||
# Install swoole
|
||||
# First line fixes an error when installing pecl extensions. Found in https://github.com/docker-library/php/issues/233
|
||||
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \
|
||||
pecl install swoole && \
|
||||
docker-php-ext-enable swoole && \
|
||||
apk del .phpize-deps
|
||||
# Install swoole, pcov and mssql driver
|
||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||
pecl install swoole-${SWOOLE_VERSION} pdo_sqlsrv pcov && \
|
||||
docker-php-ext-enable swoole pdo_sqlsrv pcov && \
|
||||
apk del .phpize-deps && \
|
||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
||||
|
||||
# Install composer
|
||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
RUN chmod +x composer.phar
|
||||
RUN mv composer.phar /usr/local/bin/composer
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
# Make home directory writable by anyone
|
||||
RUN chmod 777 /home
|
||||
@@ -89,4 +95,4 @@ CMD \
|
||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||
# When restarting the container, swoole might think it is already in execution
|
||||
# This forces the app to be started every second until the exit code is 0
|
||||
until php ./vendor/bin/zend-expressive-swoole start; do sleep 1 ; done
|
||||
until php ./vendor/bin/mezzio-swoole start; do sleep 1 ; done
|
||||
|
||||
14
data/infra/swoole_proxy_vhost.conf
Normal file
14
data/infra/swoole_proxy_vhost.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
|
||||
error_log /home/shlink/www/data/infra/nginx/swoole_proxy.error.log;
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass http://shlink_swoole:8080;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
@@ -29,12 +30,12 @@ class Version20160820191203 extends AbstractMigration
|
||||
private function createTagsTable(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('tags');
|
||||
$table->addColumn('id', Type::BIGINT, [
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('name', Type::STRING, [
|
||||
$table->addColumn('name', Types::STRING, [
|
||||
'length' => 255,
|
||||
'notnull' => true,
|
||||
]);
|
||||
@@ -46,11 +47,11 @@ class Version20160820191203 extends AbstractMigration
|
||||
private function createShortUrlsInTagsTable(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('short_urls_in_tags');
|
||||
$table->addColumn('short_url_id', Type::BIGINT, [
|
||||
$table->addColumn('short_url_id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('tag_id', Type::BIGINT, [
|
||||
$table->addColumn('tag_id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
@@ -23,10 +24,10 @@ class Version20171021093246 extends AbstractMigration
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->addColumn('valid_since', Type::DATETIME, [
|
||||
$shortUrls->addColumn('valid_since', Types::DATETIME_MUTABLE, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$shortUrls->addColumn('valid_until', Type::DATETIME, [
|
||||
$shortUrls->addColumn('valid_until', Types::DATETIME_MUTABLE, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,7 @@ class Version20171022064541 extends AbstractMigration
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->addColumn('max_visits', Type::INTEGER, [
|
||||
$shortUrls->addColumn('max_visits', Types::INTEGER, [
|
||||
'unsigned' => true,
|
||||
'notnull' => false,
|
||||
]);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
@@ -16,7 +17,6 @@ final class Version20180801183328 extends AbstractMigration
|
||||
private const OLD_SIZE = 10;
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
@@ -25,7 +25,6 @@ final class Version20180801183328 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
@@ -34,8 +33,6 @@ final class Version20180801183328 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @param int $size
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function setSize(Schema $schema, int $size): void
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
@@ -7,7 +8,7 @@ use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use PDO;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
|
||||
/**
|
||||
@@ -16,7 +17,6 @@ use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
final class Version20180913205455 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
@@ -24,7 +24,6 @@ final class Version20180913205455 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
@@ -60,13 +59,12 @@ final class Version20180913205455 extends AbstractMigration
|
||||
|
||||
try {
|
||||
return (string) IpAddress::fromString($addr)->getObfuscatedCopy();
|
||||
} catch (WrongIpException $e) {
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
@@ -18,7 +19,6 @@ final class Version20180915110857 extends AbstractMigration
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
@@ -38,7 +38,7 @@ final class Version20180915110857 extends AbstractMigration
|
||||
[
|
||||
'onDelete' => self::ON_DELETE_MAP[$foreignTable],
|
||||
'onUpdate' => 'RESTRICT',
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
@@ -7,7 +8,7 @@ use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,6 @@ final class Version20181020060559 extends AbstractMigration
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
@@ -35,7 +35,7 @@ final class Version20181020060559 extends AbstractMigration
|
||||
{
|
||||
foreach ($columnNames as $name) {
|
||||
if (! $visitLocations->hasColumn($name)) {
|
||||
$visitLocations->addColumn($name, Type::STRING, ['notnull' => false]);
|
||||
$visitLocations->addColumn($name, Types::STRING, ['notnull' => false]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
37
data/migrations/Version20190824075137.php
Normal file
37
data/migrations/Version20190824075137.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20190824075137 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->getRefererColumn($schema)->setLength(1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->getRefererColumn($schema)->setLength(256);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function getRefererColumn(Schema $schema): Column
|
||||
{
|
||||
return $schema->getTable('visits')->getColumn('referer');
|
||||
}
|
||||
}
|
||||
55
data/migrations/Version20190930165521.php
Normal file
55
data/migrations/Version20190930165521.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20190930165521 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if ($shortUrls->hasColumn('domain_id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domains = $schema->createTable('domains');
|
||||
$domains->addColumn('id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$domains->addColumn('authority', Types::STRING, [
|
||||
'length' => 512,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$domains->addUniqueIndex(['authority']);
|
||||
$domains->setPrimaryKey(['id']);
|
||||
|
||||
$shortUrls->addColumn('domain_id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => false,
|
||||
]);
|
||||
$shortUrls->addForeignKeyConstraint('domains', ['domain_id'], ['id'], [
|
||||
'onDelete' => 'RESTRICT',
|
||||
'onUpdate' => 'RESTRICT',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$schema->getTable('short_urls')->dropColumn('domain_id');
|
||||
$schema->dropTable('domains');
|
||||
}
|
||||
}
|
||||
49
data/migrations/Version20191001201532.php
Normal file
49
data/migrations/Version20191001201532.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function array_reduce;
|
||||
|
||||
final class Version20191001201532 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if ($shortUrls->hasIndex('unique_short_code_plus_domain')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Index|null $shortCodesIndex */
|
||||
$shortCodesIndex = array_reduce($shortUrls->getIndexes(), function (?Index $found, Index $current) {
|
||||
[$column] = $current->getColumns();
|
||||
return $column === 'short_code' ? $current : $found;
|
||||
});
|
||||
if ($shortCodesIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->dropIndex($shortCodesIndex->getName());
|
||||
$shortUrls->addUniqueIndex(['short_code', 'domain_id'], 'unique_short_code_plus_domain');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
|
||||
$shortUrls->dropIndex('unique_short_code_plus_domain');
|
||||
$shortUrls->addUniqueIndex(['short_code']);
|
||||
}
|
||||
}
|
||||
37
data/migrations/Version20191020074522.php
Normal file
37
data/migrations/Version20191020074522.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20191020074522 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->getOriginalUrlColumn($schema)->setLength(2048);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->getOriginalUrlColumn($schema)->setLength(1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function getOriginalUrlColumn(Schema $schema): Column
|
||||
{
|
||||
return $schema->getTable('short_urls')->getColumn('original_url');
|
||||
}
|
||||
}
|
||||
96
data/migrations/Version20200105165647.php
Normal file
96
data/migrations/Version20200105165647.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function Functional\some;
|
||||
|
||||
final class Version20200105165647 extends AbstractMigration
|
||||
{
|
||||
private const COLUMNS = ['lat' => 'latitude', 'lon' => 'longitude'];
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function preUp(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf(some(
|
||||
self::COLUMNS,
|
||||
fn (string $v, string $newColName) => $visitLocations->hasColumn($newColName),
|
||||
), 'New columns already exist');
|
||||
|
||||
foreach (self::COLUMNS as $columnName) {
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($columnName, ':zeroValue')
|
||||
->where($qb->expr()->orX(
|
||||
$qb->expr()->eq($columnName, ':emptyString'),
|
||||
$qb->expr()->isNull($columnName),
|
||||
))
|
||||
->setParameters([
|
||||
'zeroValue' => '0',
|
||||
'emptyString' => '',
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
foreach (self::COLUMNS as $newName => $oldName) {
|
||||
$visitLocations->addColumn($newName, Types::FLOAT, [
|
||||
'default' => '0.0',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$platformName = $this->connection->getDatabasePlatform()->getName();
|
||||
$castType = $platformName === 'postgres' ? 'DOUBLE PRECISION' : 'DECIMAL(9,2)';
|
||||
|
||||
foreach (self::COLUMNS as $newName => $oldName) {
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($newName, 'CAST(' . $oldName . ' AS ' . $castType . ')')
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
public function preDown(Schema $schema): void
|
||||
{
|
||||
foreach (self::COLUMNS as $newName => $oldName) {
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($oldName, $newName)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
foreach (self::COLUMNS as $colName => $oldName) {
|
||||
$visitLocations->dropColumn($colName);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
data/migrations/Version20200106215144.php
Normal file
47
data/migrations/Version20200106215144.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function Functional\none;
|
||||
|
||||
final class Version20200106215144 extends AbstractMigration
|
||||
{
|
||||
private const COLUMNS = ['latitude', 'longitude'];
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf(none(
|
||||
self::COLUMNS,
|
||||
fn (string $oldColName) => $visitLocations->hasColumn($oldColName),
|
||||
), 'Old columns do not exist');
|
||||
|
||||
foreach (self::COLUMNS as $colName) {
|
||||
$visitLocations->dropColumn($colName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
foreach (self::COLUMNS as $colName) {
|
||||
$visitLocations->addColumn($colName, Types::STRING, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
data/migrations/Version20200110182849.php
Normal file
53
data/migrations/Version20200110182849.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function Functional\each;
|
||||
use function Functional\partial_left;
|
||||
|
||||
final class Version20200110182849 extends AbstractMigration
|
||||
{
|
||||
private const DEFAULT_EMPTY_VALUE = '';
|
||||
private const COLUMN_DEFAULTS_MAP = [
|
||||
'visits' => [
|
||||
'referer',
|
||||
'user_agent',
|
||||
],
|
||||
'visit_locations' => [
|
||||
'timezone',
|
||||
'country_code',
|
||||
'country_name',
|
||||
'region_name',
|
||||
'city_name',
|
||||
],
|
||||
];
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
each(
|
||||
self::COLUMN_DEFAULTS_MAP,
|
||||
fn (array $columns, string $tableName) =>
|
||||
each($columns, partial_left([$this, 'setDefaultValueForColumnInTable'], $tableName)),
|
||||
);
|
||||
}
|
||||
|
||||
public function setDefaultValueForColumnInTable(string $tableName, string $columnName): void
|
||||
{
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update($tableName)
|
||||
->set($columnName, ':emptyValue')
|
||||
->setParameter('emptyValue', self::DEFAULT_EMPTY_VALUE)
|
||||
->where($qb->expr()->isNull($columnName))
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// No need (and no way) to undo this migration
|
||||
}
|
||||
}
|
||||
45
data/migrations/Version20200323190014.php
Normal file
45
data/migrations/Version20200323190014.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20200323190014 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf($visitLocations->hasColumn('is_empty'));
|
||||
|
||||
$visitLocations->addColumn('is_empty', Types::BOOLEAN, ['default' => false]);
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set('is_empty', ':isEmpty')
|
||||
->where($qb->expr()->eq('country_code', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('country_name', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('region_name', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('city_name', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('timezone', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('lat', 0))
|
||||
->andWhere($qb->expr()->eq('lon', 0))
|
||||
->setParameter('isEmpty', true)
|
||||
->setParameter('emptyString', '')
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf(!$visitLocations->hasColumn('is_empty'));
|
||||
|
||||
$visitLocations->dropColumn('is_empty');
|
||||
}
|
||||
}
|
||||
27
data/migrations/Version20200503170404.php
Normal file
27
data/migrations/Version20200503170404.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20200503170404 extends AbstractMigration
|
||||
{
|
||||
private const INDEX_NAME = 'IDX_visits_date';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visits = $schema->getTable('visits');
|
||||
$this->skipIf($visits->hasIndex(self::INDEX_NAME));
|
||||
$visits->addIndex(['date'], self::INDEX_NAME);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visits = $schema->getTable('visits');
|
||||
$this->skipIf(! $visits->hasIndex(self::INDEX_NAME));
|
||||
$visits->dropIndex(self::INDEX_NAME);
|
||||
}
|
||||
}
|
||||
44
data/migrations/Version20201023090929.php
Normal file
44
data/migrations/Version20201023090929.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20201023090929 extends AbstractMigration
|
||||
{
|
||||
private const IMPORT_SOURCE_COLUMN = 'import_source';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf($shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN));
|
||||
|
||||
$shortUrls->addColumn(self::IMPORT_SOURCE_COLUMN, Types::STRING, [
|
||||
'length' => 255,
|
||||
'notnull' => false,
|
||||
]);
|
||||
$shortUrls->addColumn('import_original_short_code', Types::STRING, [
|
||||
'length' => 255,
|
||||
'notnull' => false,
|
||||
]);
|
||||
|
||||
$shortUrls->addUniqueIndex(
|
||||
[self::IMPORT_SOURCE_COLUMN, 'import_original_short_code', 'domain_id'],
|
||||
'unique_imports',
|
||||
);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf(! $shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN));
|
||||
|
||||
$shortUrls->dropColumn(self::IMPORT_SOURCE_COLUMN);
|
||||
$shortUrls->dropColumn('import_original_short_code');
|
||||
$shortUrls->dropIndex('unique_imports');
|
||||
}
|
||||
}
|
||||
89
data/migrations/Version20201102113208.php
Normal file
89
data/migrations/Version20201102113208.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Doctrine\DBAL\Driver\Result;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20201102113208 extends AbstractMigration
|
||||
{
|
||||
private const API_KEY_COLUMN = 'author_api_key_id';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf($shortUrls->hasColumn(self::API_KEY_COLUMN));
|
||||
|
||||
$shortUrls->addColumn(self::API_KEY_COLUMN, Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => false,
|
||||
]);
|
||||
|
||||
$shortUrls->addForeignKeyConstraint('api_keys', [self::API_KEY_COLUMN], ['id'], [
|
||||
'onDelete' => 'SET NULL',
|
||||
'onUpdate' => 'RESTRICT',
|
||||
], 'FK_' . self::API_KEY_COLUMN);
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
// If there's only one API key and it's active, link all existing URLs with it
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('api_keys')
|
||||
->where($qb->expr()->eq('enabled', ':enabled'))
|
||||
->andWhere($qb->expr()->or(
|
||||
$qb->expr()->isNull('expiration_date'),
|
||||
$qb->expr()->gt('expiration_date', ':expiration'),
|
||||
))
|
||||
->setParameters([
|
||||
'enabled' => true,
|
||||
'expiration' => Chronos::now()->toDateTimeString(),
|
||||
]);
|
||||
|
||||
/** @var Result $result */
|
||||
$result = $qb->execute();
|
||||
$id = $this->resolveOneApiKeyId($result);
|
||||
if ($id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('short_urls')
|
||||
->set(self::API_KEY_COLUMN, ':apiKeyId')
|
||||
->setParameter('apiKeyId', $id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|int|null
|
||||
*/
|
||||
private function resolveOneApiKeyId(Result $result)
|
||||
{
|
||||
$results = [];
|
||||
while ($row = $result->fetchAssociative()) {
|
||||
// As soon as we have to iterate more than once, then we cannot resolve a single API key
|
||||
if (! empty($results)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$results[] = $row['id'] ?? null;
|
||||
}
|
||||
|
||||
return $results[0] ?? null;
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf(! $shortUrls->hasColumn(self::API_KEY_COLUMN));
|
||||
|
||||
$shortUrls->removeForeignKey('FK_' . self::API_KEY_COLUMN);
|
||||
$shortUrls->dropColumn(self::API_KEY_COLUMN);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace <namespace>;
|
||||
@@ -6,7 +7,7 @@ namespace <namespace>;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version<version> extends AbstractMigration
|
||||
final class <className> extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Get latest commit in master, in plain text
|
||||
LATEST_MASTER_COMMIT=$(curl -H "Accept: application/vnd.github.sha" -X GET https://api.github.com/repos/shlinkio/shlink-docker-image/commits/master)
|
||||
|
||||
# Create new tag and a ref to the tag, which will trigger image build on it
|
||||
curl -u acelaya:${GITHUB_OAUTH_KEY} \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{ \"tag\": \"${TRAVIS_TAG}\", \"message\": \"${TRAVIS_TAG}\", \"object\": \"${LATEST_MASTER_COMMIT}\", \"type\": \"commit\" }" \
|
||||
-X POST https://api.github.com/repos/shlinkio/shlink-docker-image/git/tags
|
||||
curl -u acelaya:${GITHUB_OAUTH_KEY} \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{ \"ref\": \"refs/tags/${TRAVIS_TAG}\", \"sha\": \"${LATEST_MASTER_COMMIT}\" }" \
|
||||
-X POST https://api.github.com/repos/shlinkio/shlink-docker-image/git/refs
|
||||
|
||||
# Trigger image build for "latest
|
||||
curl -H "Content-Type: application/json" \
|
||||
--data '{ "docker_tag": "latest" }' \
|
||||
-X POST https://registry.hub.docker.com/u/shlinkio/shlink/trigger/${DOCKER_TRIGGER_TOKEN}/
|
||||
14
docker-compose.ci.yml
Normal file
14
docker-compose.ci.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
shlink_db:
|
||||
environment:
|
||||
MYSQL_DATABASE: shlink_test
|
||||
|
||||
shlink_db_postgres:
|
||||
environment:
|
||||
POSTGRES_DB: shlink_test
|
||||
|
||||
shlink_db_maria:
|
||||
environment:
|
||||
MYSQL_DATABASE: shlink_test
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user