mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 15:23:12 +08:00
Compare commits
1423 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8acb5de68 | ||
|
|
53b9e3ddc1 | ||
|
|
68e1c61e7f | ||
|
|
8605b35b57 | ||
|
|
36680e82aa | ||
|
|
83b7d5a5f1 | ||
|
|
fe41e9d573 | ||
|
|
d76e6647d2 | ||
|
|
6f17f70137 | ||
|
|
ef01754ad5 | ||
|
|
eab9347522 | ||
|
|
59bcd62717 | ||
|
|
3f01fad12f | ||
|
|
c7f0d14c1b | ||
|
|
2408829627 | ||
|
|
8d244c8d34 | ||
|
|
42af057316 | ||
|
|
8f68078835 | ||
|
|
0c34032fd3 | ||
|
|
20f457a3e9 | ||
|
|
39693ca1fe | ||
|
|
784908420e | ||
|
|
9685929824 | ||
|
|
fe4b2c4ae4 | ||
|
|
5f87bb13f8 | ||
|
|
a87f6c6709 | ||
|
|
da3ee6b65e | ||
|
|
c5eda37bda | ||
|
|
1966367caf | ||
|
|
eed7b6e565 | ||
|
|
0e54ed691d | ||
|
|
997289da02 | ||
|
|
c841e57db5 | ||
|
|
f5138385be | ||
|
|
63ceba199d | ||
|
|
e6ee4ceae2 | ||
|
|
19a9d815eb | ||
|
|
5b78b363f0 | ||
|
|
b078c00492 | ||
|
|
e712efd008 | ||
|
|
ab27c0ce53 | ||
|
|
d97cabbe79 | ||
|
|
c3c7ffad25 | ||
|
|
fe4329d730 | ||
|
|
c53ba7b119 | ||
|
|
025eec6c70 | ||
|
|
40e1670314 | ||
|
|
2bca260627 | ||
|
|
463d8e8950 | ||
|
|
e2eed8a728 | ||
|
|
f97effcfe0 | ||
|
|
2cf21ab3bd | ||
|
|
7daa602630 | ||
|
|
7b637d6a61 | ||
|
|
a4f979be08 | ||
|
|
8852739111 | ||
|
|
2099ea16ec | ||
|
|
a739eb6d60 | ||
|
|
529ddacafe | ||
|
|
f71c95b74a | ||
|
|
8260a0843b | ||
|
|
bfbeb7b1fb | ||
|
|
df70810aa6 | ||
|
|
aca5804f98 | ||
|
|
b7f7288a4b | ||
|
|
d54a2bde0f | ||
|
|
679bb8d357 | ||
|
|
ca515998e4 | ||
|
|
c5b6d203f5 | ||
|
|
86159c5d86 | ||
|
|
846802c003 | ||
|
|
e9ec32b3c3 | ||
|
|
4882bec118 | ||
|
|
89ff259be0 | ||
|
|
60ece7fbf7 | ||
|
|
0c110f574a | ||
|
|
dbca5b2a7e | ||
|
|
3088298e6b | ||
|
|
a9c6a12182 | ||
|
|
fa5b512629 | ||
|
|
5c2061a6e6 | ||
|
|
cf0fc956c9 | ||
|
|
a0517dfbeb | ||
|
|
39c71638e6 | ||
|
|
672b728379 | ||
|
|
750a546faf | ||
|
|
a41835573b | ||
|
|
2650cb89b5 | ||
|
|
4a122e0209 | ||
|
|
ce4bf62d75 | ||
|
|
40bbcb3250 | ||
|
|
905f51fbd0 | ||
|
|
cd4fe4362b | ||
|
|
ed7be6eb99 | ||
|
|
555007ab16 | ||
|
|
bd31b99324 | ||
|
|
60237c3c0b | ||
|
|
eb21833d94 | ||
|
|
763002ae14 | ||
|
|
ae2dc39a78 | ||
|
|
fe4ced2709 | ||
|
|
9075d68b7c | ||
|
|
759c0ea957 | ||
|
|
67b393d4a3 | ||
|
|
de71821759 | ||
|
|
0c2bcaee34 | ||
|
|
1613975e0e | ||
|
|
be82204df2 | ||
|
|
14c2ff5545 | ||
|
|
d7d0e11f2c | ||
|
|
6654f45cb8 | ||
|
|
23f92179ad | ||
|
|
7377917642 | ||
|
|
0f796859f2 | ||
|
|
6383230678 | ||
|
|
51536f8746 | ||
|
|
e3b6c061c4 | ||
|
|
4bd3fa74d1 | ||
|
|
71553988d5 | ||
|
|
761b24e614 | ||
|
|
10974902b5 | ||
|
|
474407dbc2 | ||
|
|
95d84f354d | ||
|
|
db47a9a253 | ||
|
|
709a4639b3 | ||
|
|
28b9cd02ef | ||
|
|
af9ea13933 | ||
|
|
bd2cd18916 | ||
|
|
23138dc0b4 | ||
|
|
a2f9742cfc | ||
|
|
6378e614b0 | ||
|
|
b116a57aa7 | ||
|
|
a03f32f521 | ||
|
|
b9180be685 | ||
|
|
334aee64ad | ||
|
|
16bd368a58 | ||
|
|
3266a0f85c | ||
|
|
4629f1b03f | ||
|
|
fbd0c6cbea | ||
|
|
8260051c30 | ||
|
|
c061c9c3ff | ||
|
|
8961191b2e | ||
|
|
fc0d99be41 | ||
|
|
6834e72c4a | ||
|
|
efe655f880 | ||
|
|
3d5ddce621 | ||
|
|
a3de3e15cb | ||
|
|
619999d4f8 | ||
|
|
7acf27dd38 | ||
|
|
ba517eeeb5 | ||
|
|
fdd3e24967 | ||
|
|
a570ce202a | ||
|
|
0a220bbc7a | ||
|
|
e0e511f56d | ||
|
|
d375dece0e | ||
|
|
f801f265ed | ||
|
|
1b4fc89b07 | ||
|
|
3ac2b77bf0 | ||
|
|
b2ca4ad66b | ||
|
|
25a7c7bc7f | ||
|
|
6b009a4de4 | ||
|
|
0b80a86e88 | ||
|
|
b03f24d59a | ||
|
|
78ea13d366 | ||
|
|
8c2bdfba1c | ||
|
|
3289968a93 | ||
|
|
73ae754aa7 | ||
|
|
20a6e7e210 | ||
|
|
4cf433a994 | ||
|
|
e36c4d397c | ||
|
|
26037327f9 | ||
|
|
da6aa1d697 | ||
|
|
dada6aa3d1 | ||
|
|
fa5ebb1677 | ||
|
|
f071df325d | ||
|
|
3c042c4011 | ||
|
|
7e8109caa3 | ||
|
|
d3add6d8e4 | ||
|
|
1b089749c0 | ||
|
|
791d6b7e57 | ||
|
|
233bb603cf | ||
|
|
db8a816524 | ||
|
|
eff50ca202 | ||
|
|
ceabb5ab2c | ||
|
|
122c2fd5e6 | ||
|
|
cd27a72982 | ||
|
|
19b0f0d7dc | ||
|
|
6ce2049935 | ||
|
|
53b937be63 | ||
|
|
71c8f99dab | ||
|
|
9eb3fca726 | ||
|
|
019bd4dec8 | ||
|
|
be1ce06c00 | ||
|
|
074bfe3db2 | ||
|
|
34e72b42dc | ||
|
|
97d24d76d8 | ||
|
|
4d1af867a4 | ||
|
|
fc6b4c12b2 | ||
|
|
405c6de591 | ||
|
|
47bfa5fcc0 | ||
|
|
67d91d5fc5 | ||
|
|
f832c56adb | ||
|
|
1aa9ae680e | ||
|
|
c4b30db82d | ||
|
|
abd9f3c6be | ||
|
|
3de3594282 | ||
|
|
ed5816d464 | ||
|
|
3d43bdbb49 | ||
|
|
1ab492ce5b | ||
|
|
de30c6ad79 | ||
|
|
f5a48ff98d | ||
|
|
8493ee5b83 | ||
|
|
52a6d55e5d | ||
|
|
7142295aa5 | ||
|
|
8b65be26a6 | ||
|
|
60f5deb494 | ||
|
|
0fc09e6dd3 | ||
|
|
0c4ccf4e3e | ||
|
|
a0e79bf446 | ||
|
|
aa356ad7c7 | ||
|
|
9e0e384d46 | ||
|
|
a20b99e643 | ||
|
|
fe4237b2b1 | ||
|
|
4146835f6f | ||
|
|
5201ea4516 | ||
|
|
fba7b36245 | ||
|
|
353ac0fc0c | ||
|
|
00002b1e24 | ||
|
|
aa32830671 | ||
|
|
12b8100d89 | ||
|
|
72e56d271d | ||
|
|
2b69f5eff4 | ||
|
|
f224bb98c4 | ||
|
|
ec17eb3fbc | ||
|
|
358b600713 | ||
|
|
aacb5c39ba | ||
|
|
9ae8804095 | ||
|
|
2d51bd895d | ||
|
|
18f656fed2 | ||
|
|
eea76999b2 | ||
|
|
bd495adf22 | ||
|
|
e8c7bee924 | ||
|
|
24b06c24dc | ||
|
|
6fdd764a35 | ||
|
|
2400d1f265 | ||
|
|
cdef430b0b | ||
|
|
6074e4ae2c | ||
|
|
6ada704bc3 | ||
|
|
e8f7daac6f | ||
|
|
404455928e | ||
|
|
bca3e62ced | ||
|
|
e79391907a | ||
|
|
54a23cc7fa | ||
|
|
e8ebe77923 | ||
|
|
68e0aa1ea9 | ||
|
|
ceaf64c9b3 | ||
|
|
e015b1bec5 | ||
|
|
ca9726c997 | ||
|
|
85c79abd30 | ||
|
|
54c1c7ad84 | ||
|
|
af15e31b42 | ||
|
|
99b4f9f4dd | ||
|
|
9a0e5ea626 | ||
|
|
984205e02c | ||
|
|
e11bf6ac67 | ||
|
|
e029d91544 | ||
|
|
011856cbfa | ||
|
|
9ce8164013 | ||
|
|
dca6b7bbf5 | ||
|
|
2511ec3395 | ||
|
|
9f6ffc7186 | ||
|
|
622f0217fa | ||
|
|
09eba49bab | ||
|
|
c20c3801a8 | ||
|
|
f8208b7288 | ||
|
|
3db8a65ddb | ||
|
|
0495b6f298 | ||
|
|
52c55f385d | ||
|
|
fe28d6fba0 | ||
|
|
0294e49d4a | ||
|
|
cbaf51d3ef | ||
|
|
efb604a381 | ||
|
|
da87f05126 | ||
|
|
21534b78cb | ||
|
|
ab65593f7d | ||
|
|
3a82691503 | ||
|
|
430e2ff0b5 | ||
|
|
7d572e7988 | ||
|
|
1449e24b66 | ||
|
|
6a671760da | ||
|
|
613bdd82b0 | ||
|
|
01bae358f9 | ||
|
|
3a8e560dc5 | ||
|
|
a0c538d9ee | ||
|
|
07c30f86e9 | ||
|
|
c22e38f9a0 | ||
|
|
4d082a87a1 | ||
|
|
1b6512fc8d | ||
|
|
7502e8a1e4 | ||
|
|
5a25211371 | ||
|
|
6983f9b2bf | ||
|
|
5affe64b61 | ||
|
|
c52f3c396b | ||
|
|
e1ebbaa52f | ||
|
|
7abe6af5ec | ||
|
|
c98ea6055b | ||
|
|
3e3d255edf | ||
|
|
816d4851e7 | ||
|
|
9e32886f60 | ||
|
|
79af315b9f | ||
|
|
4110c702c0 | ||
|
|
57eb29c3c8 | ||
|
|
5267c4eee6 | ||
|
|
1453ebe8ca | ||
|
|
3b5cea5768 | ||
|
|
a89f67348d | ||
|
|
af1ae0399c | ||
|
|
5bf84144e7 | ||
|
|
d9adff5749 | ||
|
|
a1cd8baf3e | ||
|
|
87cadce0ac | ||
|
|
f22f50afa2 | ||
|
|
d0fa6f7e03 | ||
|
|
d29c58dce5 | ||
|
|
9ea8f3b590 | ||
|
|
ffffc68144 | ||
|
|
086de9f2a0 | ||
|
|
1b731aa4a3 | ||
|
|
12913f6b90 | ||
|
|
1d4186392c | ||
|
|
48d3ab0cb4 | ||
|
|
a9d04729eb | ||
|
|
7adf2292bd | ||
|
|
c8f55f9c05 | ||
|
|
93de62f81d | ||
|
|
9766231d41 | ||
|
|
9df80e5bec | ||
|
|
81b00e4302 | ||
|
|
25ac7c31c4 | ||
|
|
11c6c9a2b8 | ||
|
|
066268765a | ||
|
|
356b33ced0 | ||
|
|
77088d55f9 | ||
|
|
8d965655a8 | ||
|
|
3ace4952e6 | ||
|
|
299f9f3a10 | ||
|
|
0e6790cdab | ||
|
|
1f90af3aec | ||
|
|
cdb18a5baf | ||
|
|
8adb6596fb | ||
|
|
dd6bcd68cc | ||
|
|
1c9ce0ede0 | ||
|
|
6b409b06cc | ||
|
|
361e864415 | ||
|
|
d5606114cd | ||
|
|
afca66d655 | ||
|
|
33a6c9fda7 | ||
|
|
a198484ab6 | ||
|
|
dd5bce9694 | ||
|
|
bef17ff76d | ||
|
|
7202605fc8 | ||
|
|
747dac531d | ||
|
|
07d24f70e1 | ||
|
|
d0546a2ea2 | ||
|
|
9e9621e7b2 | ||
|
|
d39f3b4265 | ||
|
|
223339cd61 | ||
|
|
dc430bae10 | ||
|
|
661b07e12f | ||
|
|
0727c7bdfb | ||
|
|
b4c52116b4 | ||
|
|
89dc6108b7 | ||
|
|
492eba3a8b | ||
|
|
77fee1390f | ||
|
|
bfb54189b8 | ||
|
|
fb43885d85 | ||
|
|
7c1f705e64 | ||
|
|
fe1fa7689a | ||
|
|
8b79eee081 | ||
|
|
4a3e04ced9 | ||
|
|
61618250ec | ||
|
|
60c0ca3ae5 | ||
|
|
3436405c55 | ||
|
|
d43c3ec865 | ||
|
|
545da96d15 | ||
|
|
f53305c404 | ||
|
|
199d976e3d | ||
|
|
a1366f0ef1 | ||
|
|
91192a8a8f | ||
|
|
c6f16b0558 | ||
|
|
0d37eb65c9 | ||
|
|
f7e3a74794 | ||
|
|
976b07cd61 | ||
|
|
cff9cd5fb8 | ||
|
|
f0fd947046 | ||
|
|
7f4ada9c4b | ||
|
|
db4ef328b1 | ||
|
|
b438802e71 | ||
|
|
632a19ceeb | ||
|
|
629f8ece7a | ||
|
|
9215f9beb5 | ||
|
|
154431e86c | ||
|
|
8cfb14198b | ||
|
|
2ed475fc76 | ||
|
|
34512da2fb | ||
|
|
5b3c6f7752 | ||
|
|
f4dd27ca3f | ||
|
|
ce47d8c591 | ||
|
|
b941ee9aa9 | ||
|
|
45de3f0128 | ||
|
|
41d3826c1a | ||
|
|
f2ff6e6a70 | ||
|
|
e47c90c645 | ||
|
|
d2fef20239 | ||
|
|
3b359cfc4f | ||
|
|
acfc5a4676 | ||
|
|
a6b1647f27 | ||
|
|
d5851bbb6a | ||
|
|
397bbe2655 | ||
|
|
95d8d3ef72 | ||
|
|
1b51a1aedd | ||
|
|
ff75b3cd1f | ||
|
|
2abcaf02e2 | ||
|
|
d0c9f5a776 | ||
|
|
a46d510e2b | ||
|
|
2d861b4077 | ||
|
|
470c62d993 | ||
|
|
364734094b | ||
|
|
a667c957ee | ||
|
|
dc648b0142 | ||
|
|
1d14140986 | ||
|
|
2b693dc492 | ||
|
|
38bea6c086 | ||
|
|
cbdc5f121e | ||
|
|
562763199a | ||
|
|
107c09604a | ||
|
|
2b0567b368 | ||
|
|
d00a56bec0 | ||
|
|
ead8cc6cec | ||
|
|
806ff9daaf | ||
|
|
b3863a3e10 | ||
|
|
5559107776 | ||
|
|
af1cf806f0 | ||
|
|
0cf33c6119 | ||
|
|
b38b8a3365 | ||
|
|
e998c8434d | ||
|
|
4b90cf93d3 | ||
|
|
3dd4e33758 | ||
|
|
6caeb11598 | ||
|
|
11a383b7e5 | ||
|
|
fd2a2530b1 | ||
|
|
2f42b2d072 | ||
|
|
775f58f972 | ||
|
|
5c0abb3d96 | ||
|
|
3dc46bc5a3 | ||
|
|
e2871fc048 | ||
|
|
44e3f9b49f | ||
|
|
d3f4263639 | ||
|
|
9dec05f62d | ||
|
|
0447aa07fa | ||
|
|
0e25af790d | ||
|
|
d8484e777f | ||
|
|
665a3dbcbf | ||
|
|
103af2e2c1 | ||
|
|
d0daeb0078 | ||
|
|
a9aa49c2e5 | ||
|
|
aad24389a7 | ||
|
|
4b4f6f3201 | ||
|
|
81f82d3b73 | ||
|
|
4103ccf791 | ||
|
|
e2ed11f960 | ||
|
|
8e1cd67a3d | ||
|
|
18b4caa55e | ||
|
|
30207ce0c2 | ||
|
|
0f37f1cb23 | ||
|
|
99a905cdee | ||
|
|
6eac079440 | ||
|
|
4a1e7b8d5a | ||
|
|
5e781a9010 | ||
|
|
277d817429 | ||
|
|
970f202757 | ||
|
|
2c6b2b47a4 | ||
|
|
5c8be4b21f | ||
|
|
558a4a2b30 | ||
|
|
203ad7d594 | ||
|
|
04cf1aed9c | ||
|
|
8c14526f85 | ||
|
|
1ff241411b | ||
|
|
351e36b273 | ||
|
|
ca06040efc | ||
|
|
2102cc4e9a | ||
|
|
14d3493db8 | ||
|
|
d082d208e1 | ||
|
|
959efd17c8 | ||
|
|
30a7c55e84 | ||
|
|
2aec759857 | ||
|
|
54dcaaac0c | ||
|
|
8e5730f374 | ||
|
|
cb1705b6e8 | ||
|
|
0bcefda60d | ||
|
|
966620f840 | ||
|
|
bd3bb67949 | ||
|
|
69f4daa9d2 | ||
|
|
ec11155c9c | ||
|
|
c48a3a24f7 | ||
|
|
1b8bc9f0ff | ||
|
|
5bf25c7eca | ||
|
|
5a7f0ad340 | ||
|
|
8a93922da0 | ||
|
|
295de5be8e | ||
|
|
5c114b584d | ||
|
|
dad58b7610 | ||
|
|
23c1dadb4c | ||
|
|
05332e0606 | ||
|
|
453842246f | ||
|
|
38280b9027 | ||
|
|
7d7c0011bb | ||
|
|
de2d87a6d9 | ||
|
|
537152450f | ||
|
|
87f6b19207 | ||
|
|
064fef5d8a | ||
|
|
6aebaa94af | ||
|
|
a1a6ac9c08 | ||
|
|
0d936425c2 | ||
|
|
00f867c6ee | ||
|
|
bfea3f35f0 | ||
|
|
3f3cf5e20e | ||
|
|
0786a962e7 | ||
|
|
f7c0486101 | ||
|
|
2e3798b282 | ||
|
|
181740c3e9 | ||
|
|
23c51a1d5f | ||
|
|
15ce529c09 | ||
|
|
0fd941401b | ||
|
|
808ae6a442 | ||
|
|
ada8d18fa1 | ||
|
|
9752abff19 | ||
|
|
ee43e68a57 | ||
|
|
348ac78f5a | ||
|
|
0b22fb933c | ||
|
|
cbd4b4849f | ||
|
|
f8a48c16f0 | ||
|
|
8cc4e4bfca | ||
|
|
6c01bb87bf | ||
|
|
02d5a6f15e | ||
|
|
f361403888 | ||
|
|
3a4550fe24 | ||
|
|
5e722c830f | ||
|
|
5a56982ad9 | ||
|
|
13d70cd12a | ||
|
|
bb87bdce8a | ||
|
|
cc7ded1be7 | ||
|
|
d8735e6a91 | ||
|
|
813ae71aad | ||
|
|
1a75bd87d8 | ||
|
|
bdc89e2056 | ||
|
|
bf09990f9c | ||
|
|
81ba8dc518 | ||
|
|
e519aaaf1e | ||
|
|
5a90a5e6c7 | ||
|
|
b855ea92a9 | ||
|
|
7e74d06cdd | ||
|
|
1e7602bc36 | ||
|
|
7477e672fe | ||
|
|
4a4522dfa3 | ||
|
|
8afe058cfc | ||
|
|
e13103a925 | ||
|
|
8e167ff174 | ||
|
|
c0dcd31819 | ||
|
|
a83ae996db | ||
|
|
a66ddabe8a | ||
|
|
cdab1e9cae | ||
|
|
f2140d1eb0 | ||
|
|
4a3fa85b5f | ||
|
|
ade23a9650 | ||
|
|
fc547e6c47 | ||
|
|
f532b5edee | ||
|
|
da76eb5cf4 | ||
|
|
ac89f352ce | ||
|
|
198b2a2ace | ||
|
|
93a3d78111 | ||
|
|
494997d021 | ||
|
|
eb1345e5c3 | ||
|
|
dc8f5d002d | ||
|
|
9030e5e6eb | ||
|
|
2b827baeed | ||
|
|
cc6fa312f0 | ||
|
|
b8eba5b643 | ||
|
|
0c3f98cc37 | ||
|
|
cd35770d26 | ||
|
|
bd3a59e9ca | ||
|
|
ff50d601b3 | ||
|
|
a4fde0f9e6 | ||
|
|
c7a621cb31 | ||
|
|
6f62d62909 | ||
|
|
c3aa2df4e9 | ||
|
|
f4fbf2da75 | ||
|
|
288de8acaa | ||
|
|
750e6cff45 | ||
|
|
f49e94052d | ||
|
|
ceb642b745 | ||
|
|
ed1d886f01 | ||
|
|
db98d811b0 | ||
|
|
14ba11e1ab | ||
|
|
483bdddb18 | ||
|
|
d16fda3f16 | ||
|
|
c718b94937 | ||
|
|
bb21ab073f | ||
|
|
3ffe530461 | ||
|
|
95cf0d86bc | ||
|
|
9899a5fc56 | ||
|
|
952648185c | ||
|
|
69740493b7 | ||
|
|
994a28f31d | ||
|
|
b0a8a03f0a | ||
|
|
36e740f4cc | ||
|
|
a5874a3f80 | ||
|
|
0c95b978b4 | ||
|
|
e21f9dd1fb | ||
|
|
74a08b86ce | ||
|
|
8212d3c540 | ||
|
|
1ed6458b39 | ||
|
|
60c8f23a63 | ||
|
|
5e627641ea | ||
|
|
abc954aa47 | ||
|
|
3bfa27e682 | ||
|
|
4b7e122254 | ||
|
|
cfd3c13751 | ||
|
|
6a1ee2b894 | ||
|
|
cbec4a4e81 | ||
|
|
c7d8c1cab5 | ||
|
|
c39e1e649d | ||
|
|
95ab64ba77 | ||
|
|
1f8fcdb0f3 | ||
|
|
fb26a8ae50 | ||
|
|
42dbeaa1a5 | ||
|
|
3305f4c03a | ||
|
|
f5beec70c8 | ||
|
|
c2cd21c15e | ||
|
|
633e389275 | ||
|
|
f5aaf298e1 | ||
|
|
7db6136436 | ||
|
|
ce7296eebb | ||
|
|
c6226547f7 | ||
|
|
e7ec8f0489 | ||
|
|
dc466f238b | ||
|
|
f164656874 | ||
|
|
ef3c59152f | ||
|
|
14c6ead389 | ||
|
|
b0d33f3a85 | ||
|
|
066cc20ee6 | ||
|
|
0f51b5b1ce | ||
|
|
ebcf3e0119 | ||
|
|
6ee248d656 | ||
|
|
8a46b410f6 | ||
|
|
cd06cea153 | ||
|
|
8393d44c50 | ||
|
|
3e8ce80f80 | ||
|
|
80e033c91d | ||
|
|
a7dd441333 | ||
|
|
48efaa9fd7 | ||
|
|
92e831175f | ||
|
|
9b75e076b5 | ||
|
|
2c5d6d1651 | ||
|
|
c5cf116f33 | ||
|
|
66a4a9bce6 | ||
|
|
7e7ef64c79 | ||
|
|
9a31f53d4d | ||
|
|
60d6314262 | ||
|
|
eff7445804 | ||
|
|
2bfe21aef4 | ||
|
|
6ae0c7dcfc | ||
|
|
883ac1007a | ||
|
|
ff6747dab5 | ||
|
|
555e6f804c | ||
|
|
98c5c7990f | ||
|
|
27dcdb517d | ||
|
|
916d75d161 | ||
|
|
57bd16f4f5 | ||
|
|
444a1756a2 | ||
|
|
0c97c8f04f | ||
|
|
de81e81ecb | ||
|
|
40a7d5a112 | ||
|
|
7c06633a67 | ||
|
|
9abf611d63 | ||
|
|
565fe4c348 | ||
|
|
7b43403b1c | ||
|
|
9f25979b4c | ||
|
|
20f70b8b07 | ||
|
|
8fbf05acd4 | ||
|
|
6860855c71 | ||
|
|
b78660c685 | ||
|
|
6a40bbdcb5 | ||
|
|
5a1a4f5594 | ||
|
|
2ac7be4363 | ||
|
|
4ef5ab7a90 | ||
|
|
192308a6a3 | ||
|
|
c9ce111643 | ||
|
|
32fda231ad | ||
|
|
e4d4686717 | ||
|
|
ca6c6a1b6e | ||
|
|
806c4ce168 | ||
|
|
9d14597be0 | ||
|
|
dc68bb907c | ||
|
|
e4598c058a | ||
|
|
377562cdff | ||
|
|
969fcccc1f | ||
|
|
4c00764146 | ||
|
|
e98ee64695 | ||
|
|
51c7d0ed3e | ||
|
|
db93498ee6 | ||
|
|
b3af493758 | ||
|
|
7b9ebbbb5f | ||
|
|
ea735fc0a0 | ||
|
|
06227e97d0 | ||
|
|
dbc50b6d4f | ||
|
|
8b75ad1e7f | ||
|
|
8f3c740b57 | ||
|
|
24a6a0c23f | ||
|
|
267d72a76c | ||
|
|
021cecc216 | ||
|
|
4642480bbb | ||
|
|
4d48482d1e | ||
|
|
2054784a4a | ||
|
|
57d816b862 | ||
|
|
32bb66c42b | ||
|
|
e4d15e64b6 | ||
|
|
b11daeae7d | ||
|
|
8e78f8527e | ||
|
|
bc385744db | ||
|
|
02fd28edec | ||
|
|
95770ac104 | ||
|
|
2eeb762cd9 | ||
|
|
de5666d262 | ||
|
|
934d266880 | ||
|
|
b8fa234dbb | ||
|
|
bceea090ed | ||
|
|
8efda2ef56 | ||
|
|
f86cda6730 | ||
|
|
43f59a19fb | ||
|
|
eabaa94e06 | ||
|
|
20575a2b0f | ||
|
|
0096a778ac | ||
|
|
050f83e3bb | ||
|
|
32f7b4fbf6 | ||
|
|
265e8cdeaf | ||
|
|
fe5460e0c5 | ||
|
|
d4cad337fc | ||
|
|
0af6ecbd34 | ||
|
|
6466045363 | ||
|
|
67c7e503d9 | ||
|
|
01e06f0503 | ||
|
|
d6e155d874 | ||
|
|
5a2350bac1 | ||
|
|
2b97f9ac9e | ||
|
|
090b215179 | ||
|
|
32f483c333 | ||
|
|
655652f94f | ||
|
|
53b84c147c | ||
|
|
d8b4827601 | ||
|
|
5737acf759 | ||
|
|
58262e8604 | ||
|
|
b9e5eaf689 | ||
|
|
6d78cd59e9 | ||
|
|
aa00e33b6d | ||
|
|
4ef04c641e | ||
|
|
bfcccd8c33 | ||
|
|
f7d3c73c4a | ||
|
|
bfdece1c23 | ||
|
|
a68f450d36 | ||
|
|
d1df225e47 | ||
|
|
9c6ba4bc61 | ||
|
|
c01121d61a | ||
|
|
e0f0bb5523 | ||
|
|
a3b7742992 | ||
|
|
4b5fa6ddad | ||
|
|
b6aca82da6 | ||
|
|
8ee3bb4d58 | ||
|
|
46bea241e6 | ||
|
|
5e6d2881bc | ||
|
|
cd19876419 | ||
|
|
f82e103bc5 | ||
|
|
3ff4ac84c4 | ||
|
|
bf0c679a48 | ||
|
|
96c5bc164a | ||
|
|
73aead01b4 | ||
|
|
e19b3cc45d | ||
|
|
a1cab4ca7d | ||
|
|
4b89687e45 | ||
|
|
1c861fecfc | ||
|
|
a12c9f54c4 | ||
|
|
69d72e754f | ||
|
|
db3c5a3031 | ||
|
|
6327ed814a | ||
|
|
9fa32b5b6b | ||
|
|
663ae9f6bb | ||
|
|
70c73bc5d6 | ||
|
|
05d73552cf | ||
|
|
70384237c1 | ||
|
|
36e4a0dd32 | ||
|
|
3ef02d46c0 | ||
|
|
e6ce84aa14 | ||
|
|
348e34d52e | ||
|
|
2e6b3c0561 | ||
|
|
7280b48cdc | ||
|
|
2803f65479 | ||
|
|
3535688c3b | ||
|
|
9cff570c45 | ||
|
|
15c028e151 | ||
|
|
f0dc32b6e5 | ||
|
|
d423d18249 | ||
|
|
8a8e3c3fc8 | ||
|
|
111fc3c37d | ||
|
|
e9a5284dde | ||
|
|
b277f431c2 | ||
|
|
c8b8947b1f | ||
|
|
9a78d1585d | ||
|
|
09414a8834 | ||
|
|
1efa973507 | ||
|
|
e23cd6a856 | ||
|
|
743bb7a6ee | ||
|
|
086efe3c63 | ||
|
|
d751df70fd | ||
|
|
334d95c843 | ||
|
|
5ddac7866b | ||
|
|
a896fbbb90 | ||
|
|
a478699fe8 | ||
|
|
6387e50276 | ||
|
|
28c06de685 | ||
|
|
823573cea7 | ||
|
|
5d0f306bcc | ||
|
|
f30e922074 | ||
|
|
96ff0bffda | ||
|
|
d9b675fc8b | ||
|
|
104b7390da | ||
|
|
7b4456e73f | ||
|
|
86230d9bf3 | ||
|
|
1f8994ca8b | ||
|
|
f7b6f4ba19 | ||
|
|
74ea5969be | ||
|
|
c4718e7523 | ||
|
|
5de706e0fe | ||
|
|
77d06b4b03 | ||
|
|
b4d137375a | ||
|
|
0621ae7735 | ||
|
|
3a6a1f25a7 | ||
|
|
731dc64f44 | ||
|
|
d72b9cf646 | ||
|
|
0f0c4dc549 | ||
|
|
ea0820d881 | ||
|
|
312f20d2f1 | ||
|
|
f8289fa4be | ||
|
|
554209d644 | ||
|
|
4ce44034cb | ||
|
|
221b62ea57 | ||
|
|
0a5c265b12 | ||
|
|
9b55389538 | ||
|
|
60a8d6e986 | ||
|
|
d7523bcb57 | ||
|
|
562110fac4 | ||
|
|
d104265f04 | ||
|
|
4439685403 | ||
|
|
9feb72235a | ||
|
|
c372a498cc | ||
|
|
be35349350 | ||
|
|
771fd74978 | ||
|
|
3ba9ee7bf1 | ||
|
|
a0062a62e8 | ||
|
|
0f2bd77ebc | ||
|
|
744b368cc1 | ||
|
|
a03c4519c9 | ||
|
|
66327881d5 | ||
|
|
b93b14986e | ||
|
|
1ade4e9917 | ||
|
|
65f2ab6720 | ||
|
|
7d38ba12bd | ||
|
|
8128e85b6b | ||
|
|
3d99819be4 | ||
|
|
a2ca1618ea | ||
|
|
b244c56862 | ||
|
|
c931874bac | ||
|
|
1b168ac3d2 | ||
|
|
0fc123b249 | ||
|
|
c622804950 | ||
|
|
e093480a5b | ||
|
|
1498b72966 | ||
|
|
51e1c7cd50 | ||
|
|
40040b627f | ||
|
|
b752f8a357 | ||
|
|
5b93cf42b1 | ||
|
|
fa8145df9f | ||
|
|
5ddb6a7f99 | ||
|
|
8ad34357d3 | ||
|
|
81eb2684bf | ||
|
|
d2c0745efa | ||
|
|
3f2d38a86a | ||
|
|
4df4db05f4 | ||
|
|
6526fda960 | ||
|
|
32fdb257a3 | ||
|
|
9247cd874e | ||
|
|
4ceb42b88d | ||
|
|
3d99fc1708 | ||
|
|
656346bd04 | ||
|
|
6b5217ece2 | ||
|
|
0a2b388f6b | ||
|
|
25b3de84ec | ||
|
|
5c4e348078 | ||
|
|
2ac84ac8c4 | ||
|
|
f0249346b0 | ||
|
|
86651d7992 | ||
|
|
5cd5fb0071 | ||
|
|
e3bf046c30 | ||
|
|
d9af0a5547 | ||
|
|
ede7551856 | ||
|
|
a2030b6c27 | ||
|
|
9a951589dc | ||
|
|
c766cfad89 | ||
|
|
bd25572e08 | ||
|
|
4e00c950cc | ||
|
|
d932f0a204 | ||
|
|
08507272ed | ||
|
|
9c48e6578d | ||
|
|
db6c83eefd | ||
|
|
cc68cb944f | ||
|
|
a0d8d237d7 | ||
|
|
7d6d8e3a68 | ||
|
|
cc42f037c7 | ||
|
|
f4623ed028 | ||
|
|
bec467c703 | ||
|
|
bd09b1571a | ||
|
|
3ed6953d0b | ||
|
|
2fc6fb0a9a | ||
|
|
4b73bd907e | ||
|
|
a18486cc2e | ||
|
|
82f4e22f69 | ||
|
|
3497165ebd | ||
|
|
d5794a3dcb | ||
|
|
bd9ec53e7b | ||
|
|
5d98316c4e | ||
|
|
dcf2526aad | ||
|
|
85dd023c0e | ||
|
|
1fbcb44136 | ||
|
|
ab9042db24 | ||
|
|
b01487ac91 | ||
|
|
5278d7668c | ||
|
|
f7215fc2c5 | ||
|
|
d2e0413a48 | ||
|
|
0e165bc7e0 | ||
|
|
55e7f7ccb0 | ||
|
|
15061d3e0d | ||
|
|
36be44e7b5 | ||
|
|
1b4e62b823 | ||
|
|
12b07bb0ac | ||
|
|
f5666c9451 | ||
|
|
23cffce861 | ||
|
|
a1fb44f2a6 | ||
|
|
4d5dd8c8de | ||
|
|
1c492881e1 | ||
|
|
d310c53cce | ||
|
|
2289eebd91 | ||
|
|
e259bd62ab | ||
|
|
9f512705fa | ||
|
|
383fde488b | ||
|
|
b54350674c | ||
|
|
1e2b88496c | ||
|
|
919b567d46 | ||
|
|
da65c05c4f | ||
|
|
2f8ca6cf11 | ||
|
|
7121ff340a | ||
|
|
37f4d18d34 | ||
|
|
a8b424003c | ||
|
|
de4e677f18 | ||
|
|
bc632fd644 | ||
|
|
d386e1405c | ||
|
|
608742c2e2 | ||
|
|
71e91a541f | ||
|
|
ed18f10b94 | ||
|
|
4330a09793 | ||
|
|
16873201e9 | ||
|
|
2640c7b43c | ||
|
|
7824dddef7 | ||
|
|
7192480751 | ||
|
|
1da66f272c | ||
|
|
0ef1e347e7 | ||
|
|
bfba05c863 | ||
|
|
71f85350da | ||
|
|
8b54098299 | ||
|
|
356e68ca3e | ||
|
|
430c407106 | ||
|
|
31a7212a71 | ||
|
|
36a172308a | ||
|
|
e20df481a4 | ||
|
|
8fa0c95f5a | ||
|
|
4b4a859722 | ||
|
|
9cddedcdba | ||
|
|
01aebd90d5 | ||
|
|
c00105607c | ||
|
|
79ff12a1b0 | ||
|
|
e30c9c86ff | ||
|
|
c61e1e1c0e | ||
|
|
85bc5ce595 | ||
|
|
ef12e90ae7 | ||
|
|
6b0f6e4541 | ||
|
|
cdfd14e63f | ||
|
|
977058d219 | ||
|
|
c58fa586e1 | ||
|
|
1cd6fdeede | ||
|
|
09f25d78b7 | ||
|
|
82091c7951 | ||
|
|
1081211439 | ||
|
|
7e90fd45a7 | ||
|
|
08f4a424e6 | ||
|
|
063ee9c195 | ||
|
|
3f2bd657e1 | ||
|
|
903ef8e249 | ||
|
|
07b12fac3c | ||
|
|
56a2253535 | ||
|
|
752ded2f80 | ||
|
|
248d5e2fe5 | ||
|
|
158e981970 | ||
|
|
96d07c4b4e | ||
|
|
28afb8944f | ||
|
|
0d59ebfe55 | ||
|
|
bc38ecf6de | ||
|
|
755a52b78e | ||
|
|
4c008f1672 | ||
|
|
eb268fb856 | ||
|
|
b0e390ced1 | ||
|
|
741e8f625c | ||
|
|
17eb6dc4ce | ||
|
|
db997fe6f5 | ||
|
|
3b1fc2a27d | ||
|
|
0cbd965010 | ||
|
|
f3c3979eec | ||
|
|
bf26f5baa1 | ||
|
|
164462d536 | ||
|
|
239af85dd4 | ||
|
|
f585cfe02e | ||
|
|
ef54caab85 | ||
|
|
aaaa3010ab | ||
|
|
cfdf866c3f | ||
|
|
2a1a386b9c | ||
|
|
a4de8cee7d | ||
|
|
a9d6c463ed | ||
|
|
b8a725d60c | ||
|
|
927fb51313 | ||
|
|
76aa6502db | ||
|
|
7d908b6545 | ||
|
|
83a29d6ed0 | ||
|
|
55ddc4ae75 | ||
|
|
088e361228 | ||
|
|
80012b8ee8 | ||
|
|
a61235a5d1 | ||
|
|
823242a6c2 | ||
|
|
0670a4dc3c | ||
|
|
f57303f8c0 | ||
|
|
2eff9929d8 | ||
|
|
92d7dc2595 | ||
|
|
4a5cc9a986 | ||
|
|
da9896a28b | ||
|
|
b5b3a50bb2 | ||
|
|
ea99b88c44 | ||
|
|
45d162e71a | ||
|
|
8132113ed9 | ||
|
|
eef49478fc | ||
|
|
60cdd8b198 | ||
|
|
47d86b58a3 | ||
|
|
e6663aeb20 | ||
|
|
b321af6d03 | ||
|
|
78038b3141 | ||
|
|
89fd782dd3 | ||
|
|
37c68c39b0 | ||
|
|
1309290a2f | ||
|
|
3e2701f136 | ||
|
|
5ad1a12457 | ||
|
|
2e8f5202d0 | ||
|
|
6b6d751d54 | ||
|
|
a9704c6e2f | ||
|
|
e3ff447152 | ||
|
|
c5fc8fbf00 | ||
|
|
da9e9df4ba | ||
|
|
1c75519f9b | ||
|
|
fca19f265b | ||
|
|
75dab92225 | ||
|
|
9e9d213f20 | ||
|
|
c49a0ca040 | ||
|
|
1f2e16184c | ||
|
|
7a19b8765d | ||
|
|
a639a4eb94 | ||
|
|
c9ff2b3834 | ||
|
|
95e51665b1 | ||
|
|
91da241434 | ||
|
|
5bec9f5b65 | ||
|
|
34bb023b7d | ||
|
|
2be0050f3d | ||
|
|
ff1af82ffd | ||
|
|
13cc70e6d4 | ||
|
|
fa5934b8b6 | ||
|
|
c8eb956778 | ||
|
|
5283ee2c6b | ||
|
|
c56d56d38c | ||
|
|
ea05259bbe | ||
|
|
f17873b527 | ||
|
|
f827186c77 | ||
|
|
380915948b | ||
|
|
14eeb91c58 | ||
|
|
01dceca9ef | ||
|
|
ba32366b0b | ||
|
|
bef1b13a33 | ||
|
|
caa1ae0de8 | ||
|
|
b0c4582f3f | ||
|
|
a8b68f07b5 | ||
|
|
b5710f87e2 | ||
|
|
041f231ff2 | ||
|
|
01b3c504f8 | ||
|
|
f821dea06c | ||
|
|
4b67d41362 | ||
|
|
19834f6715 | ||
|
|
262a06f624 | ||
|
|
a01e0ba337 | ||
|
|
364be2420b | ||
|
|
29cdfaed39 | ||
|
|
24f7fb9c4f | ||
|
|
68c601a5a8 | ||
|
|
8aa6bdb934 | ||
|
|
4a1e7b761a | ||
|
|
25ee9b5daf | ||
|
|
fff10ebee4 | ||
|
|
65797b61a0 | ||
|
|
3e565d3830 | ||
|
|
dc08286a72 | ||
|
|
940383646b | ||
|
|
6e1d6ab795 | ||
|
|
df53e6c6f2 | ||
|
|
7e6882960e | ||
|
|
ecf22ae4b6 | ||
|
|
90551ff3bc | ||
|
|
598f2d8622 | ||
|
|
f3b4e94def | ||
|
|
6eb3dae8c3 | ||
|
|
09029dff37 | ||
|
|
9e7f2aea0d | ||
|
|
850a5b412c | ||
|
|
84331135f7 | ||
|
|
202a7327d3 | ||
|
|
f42e2d87b3 | ||
|
|
22124aced7 | ||
|
|
40676f2167 | ||
|
|
d7b4720327 | ||
|
|
3a4a2e4483 | ||
|
|
71a83aa384 | ||
|
|
291393eeeb | ||
|
|
ea06c369b0 | ||
|
|
625c870417 | ||
|
|
a9e9f89799 | ||
|
|
f2210ca0cb | ||
|
|
1a42ca9239 | ||
|
|
53726bc679 | ||
|
|
d8a7f3e08c | ||
|
|
ac5a22a3d0 | ||
|
|
5dc2c1640a | ||
|
|
7fe7354a27 | ||
|
|
ac85b913c2 | ||
|
|
0e58d1a242 | ||
|
|
5040f5b177 | ||
|
|
77deb9c111 | ||
|
|
74bafefa68 | ||
|
|
d564404bfe | ||
|
|
b2658073b3 | ||
|
|
63bd95a123 | ||
|
|
40105d7aaf | ||
|
|
c78991761f | ||
|
|
b7a0d319b3 | ||
|
|
55bfa9776a | ||
|
|
d3a4ed607c | ||
|
|
8c79619ff2 | ||
|
|
6bedca4ee6 | ||
|
|
9857f105ec | ||
|
|
7ac1c32ad6 | ||
|
|
6e9fa6553d | ||
|
|
55ea8a6912 | ||
|
|
179ddc5bd7 | ||
|
|
bfd886604e | ||
|
|
f34033aa9c | ||
|
|
e54745b250 | ||
|
|
1975a35837 | ||
|
|
5db66dcf0e | ||
|
|
cfdf2f9480 | ||
|
|
c13adb04ef | ||
|
|
4f1ab977a1 | ||
|
|
fe59a5ad86 | ||
|
|
a72dc16d85 | ||
|
|
74108a19e5 | ||
|
|
abe0fc16df | ||
|
|
39bda5113b | ||
|
|
49ea5cc78b | ||
|
|
8acde332b2 | ||
|
|
600f7a7388 | ||
|
|
fd007ea4a9 | ||
|
|
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 |
@@ -1,3 +1,4 @@
|
|||||||
|
bin/rr
|
||||||
config/autoload/*local*
|
config/autoload/*local*
|
||||||
data/infra
|
data/infra
|
||||||
data/cache/*
|
data/cache/*
|
||||||
@@ -5,10 +6,11 @@ data/log/*
|
|||||||
data/locks/*
|
data/locks/*
|
||||||
data/proxies/*
|
data/proxies/*
|
||||||
data/migrations_template.txt
|
data/migrations_template.txt
|
||||||
data/GeoLite2-City.*
|
data/GeoLite2-City*
|
||||||
data/database.sqlite
|
data/database.sqlite
|
||||||
data/shlink-tests.db
|
data/shlink-tests.db
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
CONTRIBUTING.md
|
||||||
UPGRADE.md
|
UPGRADE.md
|
||||||
composer.lock
|
composer.lock
|
||||||
vendor
|
vendor
|
||||||
@@ -17,7 +19,8 @@ indocker
|
|||||||
docker-*
|
docker-*
|
||||||
phpstan.neon
|
phpstan.neon
|
||||||
php*xml*
|
php*xml*
|
||||||
infection.json
|
infection*
|
||||||
**/test*
|
**/test*
|
||||||
build*
|
build*
|
||||||
**/.*
|
**/.*
|
||||||
|
!config/roadrunner/.rr.yml
|
||||||
|
|||||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -10,7 +10,6 @@
|
|||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
.phpstorm.meta.php export-ignore
|
.phpstorm.meta.php export-ignore
|
||||||
.scrutinizer.yml export-ignore
|
|
||||||
.travis.yml export-ignore
|
.travis.yml export-ignore
|
||||||
build.sh export-ignore
|
build.sh export-ignore
|
||||||
CHANGELOG.md export-ignore
|
CHANGELOG.md export-ignore
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE.md
vendored
5
.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.
|
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 required.
|
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.
|
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.
|
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||||
-->
|
-->
|
||||||
|
|||||||
7
.github/ISSUE_TEMPLATE/Bug.md
vendored
7
.github/ISSUE_TEMPLATE/Bug.md
vendored
@@ -5,9 +5,10 @@ labels: bug
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Before opening an issue, just take into account that this is a completely free of charge open source project.
|
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 required.
|
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.
|
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.
|
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).
|
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||||
@@ -17,7 +18,7 @@ With that said, please fill in the information requested next. More information
|
|||||||
|
|
||||||
* Shlink Version: x.y.z
|
* Shlink Version: x.y.z
|
||||||
* PHP 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
|
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
|
||||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
||||||
|
|
||||||
#### Summary
|
#### Summary
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
5
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
@@ -5,9 +5,10 @@ labels: feature
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Before opening an issue, just take into account that this is a completely free of charge open source project.
|
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 required.
|
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.
|
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.
|
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).
|
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||||
|
|||||||
7
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
7
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
@@ -5,9 +5,10 @@ labels: question
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Before opening an issue, just take into account that this is a completely free of charge open source project.
|
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 required.
|
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.
|
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.
|
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).
|
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||||
@@ -17,7 +18,7 @@ With that said, please fill in the information requested next. More information
|
|||||||
|
|
||||||
* Shlink Version: x.y.z
|
* Shlink Version: x.y.z
|
||||||
* PHP 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
|
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
|
||||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
||||||
|
|
||||||
#### Summary
|
#### Summary
|
||||||
|
|||||||
47
.github/actions/ci-setup/action.yml
vendored
Normal file
47
.github/actions/ci-setup/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: CI setup
|
||||||
|
description: 'Sets up the environment to run CI actions for Shlink'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
install-deps:
|
||||||
|
description: 'Tells if dependencies should be installed with composer. Default value is "yes"'
|
||||||
|
required: true
|
||||||
|
default: 'yes'
|
||||||
|
php-version:
|
||||||
|
description: 'The PHP version to be setup'
|
||||||
|
required: true
|
||||||
|
php-extensions:
|
||||||
|
description: 'The PHP extensions to install'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
extensions-cache-key:
|
||||||
|
description: 'The key used to cache PHP extensions. If empty value is provided, extension caching is disabled'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Setup cache environment
|
||||||
|
id: extcache
|
||||||
|
uses: shivammathur/cache-extensions@v1
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
key: ${{ inputs.extensions-cache-key }}
|
||||||
|
- name: Cache extensions
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.extcache.outputs.dir }}
|
||||||
|
key: ${{ steps.extcache.outputs.key }}
|
||||||
|
restore-keys: ${{ steps.extcache.outputs.key }}
|
||||||
|
- name: Use PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
tools: composer
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
coverage: pcov
|
||||||
|
ini-values: pcov.directory=module
|
||||||
|
- name: Install dependencies
|
||||||
|
if: ${{ inputs.install-deps == 'yes' }}
|
||||||
|
run: composer install --no-interaction --prefer-dist
|
||||||
|
shell: bash
|
||||||
44
.github/workflows/ci-db-tests.yml
vendored
Normal file
44
.github/workflows/ci-db-tests.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: Database tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of sqlite:ci, mysql, maria, postgres or ms
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
db-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: [ '8.1' ]
|
||||||
|
env:
|
||||||
|
LC_ALL: C
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Install MSSQL ODBC
|
||||||
|
if: ${{ inputs.platform == 'ms' }}
|
||||||
|
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
||||||
|
- name: Start database server
|
||||||
|
if: ${{ inputs.platform != 'sqlite:ci' }}
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ inputs.platform }}
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-4.11.1, pdo_sqlsrv-5.10.1
|
||||||
|
extensions-cache-key: db-tests-extensions-${{ matrix.php-version }}-${{ inputs.platform }}
|
||||||
|
- name: Create test database
|
||||||
|
if: ${{ inputs.platform == 'ms' }}
|
||||||
|
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||||
|
- name: Run tests
|
||||||
|
run: composer test:db:${{ inputs.platform }}
|
||||||
|
- name: Upload code coverage
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: ${{ matrix.php-version == '8.1' && inputs.platform == 'sqlite:ci' }}
|
||||||
|
with:
|
||||||
|
name: coverage-db
|
||||||
|
path: |
|
||||||
|
build/coverage-db
|
||||||
|
build/coverage-db.cov
|
||||||
45
.github/workflows/ci-mutation-tests.yml
vendored
Normal file
45
.github/workflows/ci-mutation-tests.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Mutation tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, db, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mutation-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: [ '8.1' ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-4.11.1
|
||||||
|
extensions-cache-key: mutation-tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: build
|
||||||
|
- name: Resolve infection args
|
||||||
|
id: infection_args
|
||||||
|
run: echo "::set-output name=args::--logger-github=false"
|
||||||
|
# TODO Try to filter mutation tests to improve execution times. Investigate why --git-diff-lines --git-diff-base=develop does not work
|
||||||
|
# run: |
|
||||||
|
# BRANCH="${GITHUB_REF#refs/heads/}" |
|
||||||
|
# if [[ $BRANCH == 'main' || $BRANCH == 'develop' ]]; then
|
||||||
|
# echo "::set-output name=args::--logger-github=false"
|
||||||
|
# else
|
||||||
|
# echo "::set-output name=args::--logger-github=false --git-diff-lines --git-diff-base=develop"
|
||||||
|
# fi;
|
||||||
|
shell: bash
|
||||||
|
- if: ${{ inputs.test-group == 'unit' }}
|
||||||
|
run: composer infect:ci:unit -- ${{ steps.infection_args.outputs.args }}
|
||||||
|
env:
|
||||||
|
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
|
||||||
|
- if: ${{ inputs.test-group != 'unit' }}
|
||||||
|
run: composer infect:ci:${{ inputs.test-group }} -- ${{ steps.infection_args.outputs.args }}
|
||||||
37
.github/workflows/ci-tests.yml
vendored
Normal file
37
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.1']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Start postgres database server
|
||||||
|
if: ${{ inputs.test-group == 'api' }}
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||||
|
- name: Start maria database server
|
||||||
|
if: ${{ inputs.test-group == 'cli' }}
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-4.11.1
|
||||||
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- run: composer test:${{ inputs.test-group }}:ci
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: ${{ matrix.php-version == '8.1' }}
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: |
|
||||||
|
build/coverage-${{ inputs.test-group }}
|
||||||
|
build/coverage-${{ inputs.test-group }}.cov
|
||||||
175
.github/workflows/ci.yml
vendored
Normal file
175
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
name: Continuous integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request: null
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
- 2.x
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
static-analysis:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.1']
|
||||||
|
command: ['cs', 'stan', 'swagger:validate']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-4.11.1
|
||||||
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
|
||||||
|
- run: composer ${{ matrix.command }}
|
||||||
|
|
||||||
|
unit-tests:
|
||||||
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: unit
|
||||||
|
|
||||||
|
cli-tests:
|
||||||
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: cli
|
||||||
|
|
||||||
|
openswoole-api-tests:
|
||||||
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: api
|
||||||
|
|
||||||
|
roadrunner-api-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: [ '8.1' ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||||
|
- uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
tools: composer
|
||||||
|
- run: composer install --no-interaction --prefer-dist
|
||||||
|
- run: ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr
|
||||||
|
- run: composer test:api:rr
|
||||||
|
|
||||||
|
sqlite-db-tests:
|
||||||
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
|
with:
|
||||||
|
platform: 'sqlite:ci'
|
||||||
|
|
||||||
|
mysql-db-tests:
|
||||||
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
|
with:
|
||||||
|
platform: 'mysql'
|
||||||
|
|
||||||
|
maria-db-tests:
|
||||||
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
|
with:
|
||||||
|
platform: 'maria'
|
||||||
|
|
||||||
|
postgres-db-tests:
|
||||||
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
|
with:
|
||||||
|
platform: 'postgres'
|
||||||
|
|
||||||
|
ms-db-tests:
|
||||||
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
|
with:
|
||||||
|
platform: 'ms'
|
||||||
|
|
||||||
|
unit-mutation-tests:
|
||||||
|
needs:
|
||||||
|
- unit-tests
|
||||||
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: unit
|
||||||
|
|
||||||
|
db-mutation-tests:
|
||||||
|
needs:
|
||||||
|
- sqlite-db-tests
|
||||||
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: db
|
||||||
|
|
||||||
|
api-mutation-tests:
|
||||||
|
needs:
|
||||||
|
- openswoole-api-tests
|
||||||
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: api
|
||||||
|
|
||||||
|
cli-mutation-tests:
|
||||||
|
needs:
|
||||||
|
- cli-tests
|
||||||
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: cli
|
||||||
|
|
||||||
|
upload-coverage:
|
||||||
|
needs:
|
||||||
|
- unit-tests
|
||||||
|
- openswoole-api-tests
|
||||||
|
- cli-tests
|
||||||
|
- sqlite-db-tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.1']
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Use PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
coverage: pcov
|
||||||
|
ini-values: pcov.directory=module
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: build
|
||||||
|
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
||||||
|
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
|
||||||
|
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
||||||
|
- run: mv build/coverage-cli/coverage-cli.cov build/coverage-cli.cov
|
||||||
|
- run: wget https://phar.phpunit.de/phpcov-8.2.1.phar
|
||||||
|
- run: php phpcov-8.2.1.phar merge build --clover build/clover.xml
|
||||||
|
- name: Publish coverage
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
|
with:
|
||||||
|
file: ./build/clover.xml
|
||||||
|
|
||||||
|
delete-artifacts:
|
||||||
|
needs:
|
||||||
|
- unit-mutation-tests
|
||||||
|
- db-mutation-tests
|
||||||
|
- api-mutation-tests
|
||||||
|
- cli-mutation-tests
|
||||||
|
- upload-coverage
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: geekyeggo/delete-artifact@v1
|
||||||
|
with:
|
||||||
|
name: |
|
||||||
|
coverage-unit
|
||||||
|
coverage-db
|
||||||
|
coverage-api
|
||||||
|
coverage-cli
|
||||||
|
|
||||||
|
build-docker-image:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 100
|
||||||
|
- uses: marceloprado/has-changed-path@v1
|
||||||
|
id: changed-dockerfile
|
||||||
|
with:
|
||||||
|
paths: ./Dockerfile
|
||||||
|
- if: ${{ steps.changed-dockerfile.outputs.changed == 'true' }}
|
||||||
|
run: docker build -t shlink-docker-image:temp .
|
||||||
|
- if: ${{ steps.changed-dockerfile.outputs.changed != 'true' }}
|
||||||
|
run: echo "Dockerfile didn't change. Skipped"
|
||||||
27
.github/workflows/docker-image-build.yml
vendored
Normal file
27
.github/workflows/docker-image-build.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Build and publish docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-openswool:
|
||||||
|
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
image-name: shlinkio/shlink
|
||||||
|
version-arg-name: SHLINK_VERSION
|
||||||
|
|
||||||
|
build-roadrunner:
|
||||||
|
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
image-name: shlinkio/shlink
|
||||||
|
version-arg-name: SHLINK_VERSION
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
tags-suffix: roadrunner
|
||||||
|
extra-build-args: |
|
||||||
|
SHLINK_RUNTIME=rr
|
||||||
59
.github/workflows/publish-release.yml
vendored
Normal file
59
.github/workflows/publish-release.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: Publish release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.1']
|
||||||
|
swoole: ['yes', 'no']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-4.11.1
|
||||||
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
|
install-deps: 'no'
|
||||||
|
- if: ${{ matrix.swoole == 'yes' }}
|
||||||
|
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||||
|
- if: ${{ matrix.swoole == 'no' }}
|
||||||
|
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||||
|
path: build
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: ['build']
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: build
|
||||||
|
- name: Publish release with assets
|
||||||
|
uses: docker://antonyurchenko/git-release:latest
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ALLOW_EMPTY_CHANGELOG: "true"
|
||||||
|
with:
|
||||||
|
args: |
|
||||||
|
build/*/shlink*_dist.zip
|
||||||
|
|
||||||
|
delete-artifacts:
|
||||||
|
needs: ['publish']
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.1']
|
||||||
|
swoole: ['yes', 'no']
|
||||||
|
steps:
|
||||||
|
- uses: geekyeggo/delete-artifact@v1
|
||||||
|
with:
|
||||||
|
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||||
36
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
36
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Publish swagger spec
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.1']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Determine version
|
||||||
|
id: determine_version
|
||||||
|
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
|
||||||
|
shell: bash
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-4.11.1
|
||||||
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
|
- run: composer swagger:inline
|
||||||
|
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
||||||
|
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
|
||||||
|
- name: Publish spec
|
||||||
|
uses: JamesIves/github-pages-deploy-action@4.1.7
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
||||||
|
repository-name: 'shlinkio/shlink-open-api-specs'
|
||||||
|
branch: main
|
||||||
|
folder: ${{ steps.determine_version.outputs.version }}
|
||||||
|
target-folder: specs/${{ steps.determine_version.outputs.version }}
|
||||||
|
clean: false
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,4 +1,7 @@
|
|||||||
.idea
|
.idea
|
||||||
|
bin/.rr.*
|
||||||
|
bin/rr
|
||||||
|
config/roadrunner/.pid
|
||||||
build
|
build
|
||||||
!docker/build
|
!docker/build
|
||||||
composer.lock
|
composer.lock
|
||||||
@@ -6,8 +9,9 @@ composer.phar
|
|||||||
vendor/
|
vendor/
|
||||||
data/database.sqlite
|
data/database.sqlite
|
||||||
data/shlink-tests.db
|
data/shlink-tests.db
|
||||||
data/GeoLite2-City.mmdb
|
data/GeoLite2-City.*
|
||||||
data/GeoLite2-City.mmdb.*
|
|
||||||
docs/swagger-ui*
|
docs/swagger-ui*
|
||||||
|
docs/mercure.html
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
docs/swagger/swagger-inlined.json
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
tools:
|
|
||||||
external_code_coverage:
|
|
||||||
timeout: 600
|
|
||||||
checks:
|
|
||||||
php:
|
|
||||||
code_rating: true
|
|
||||||
duplication: true
|
|
||||||
build:
|
|
||||||
dependencies:
|
|
||||||
override:
|
|
||||||
- composer install --no-interaction --no-scripts --ignore-platform-reqs
|
|
||||||
nodes:
|
|
||||||
analysis:
|
|
||||||
tests:
|
|
||||||
override:
|
|
||||||
- php-scrutinizer-run
|
|
||||||
63
.travis.yml
63
.travis.yml
@@ -1,63 +0,0 @@
|
|||||||
language: php
|
|
||||||
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- /.*/
|
|
||||||
|
|
||||||
php:
|
|
||||||
- '7.4'
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.composer/cache/files
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- 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 swoole-4.4.18
|
|
||||||
- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
|
||||||
- phpenv config-rm xdebug.ini || return 0
|
|
||||||
|
|
||||||
install:
|
|
||||||
- composer self-update
|
|
||||||
- composer install --no-interaction --prefer-dist
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- 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/master} --name-only | grep Dockerfile)
|
|
||||||
|
|
||||||
script:
|
|
||||||
- composer ci
|
|
||||||
- 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
|
|
||||||
- wget https://phar.phpunit.de/phpcov-7.0.2.phar
|
|
||||||
- phpdbg -qrr 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
|
|
||||||
- if [[ ! -z $TRAVIS_TAG && "${TRAVIS_PHP_VERSION}" == "7.4" ]]; then ./build.sh ${TRAVIS_TAG#?} ; fi
|
|
||||||
|
|
||||||
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.4'
|
|
||||||
- provider: script
|
|
||||||
script: bash ./docker/build
|
|
||||||
on:
|
|
||||||
all_branches: true
|
|
||||||
condition: $TRAVIS_PULL_REQUEST == 'false'
|
|
||||||
php: '7.4'
|
|
||||||
1960
CHANGELOG.md
1960
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
144
CONTRIBUTING.md
Normal file
144
CONTRIBUTING.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# 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 openswoole.
|
||||||
|
|
||||||
|
> Note: The `indocker` shell script is a helper tool 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
|
||||||
|
├── config
|
||||||
|
│ ├── autoload
|
||||||
|
│ ├── params
|
||||||
|
│ ├── config.php
|
||||||
|
│ └── container.php
|
||||||
|
├── data
|
||||||
|
│ ├── cache
|
||||||
|
│ ├── locks
|
||||||
|
│ ├── log
|
||||||
|
│ ├── migrations
|
||||||
|
│ └── proxies
|
||||||
|
├── docs
|
||||||
|
│ ├── adr
|
||||||
|
│ ├── 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.
|
||||||
|
* `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 or architectural decision records.
|
||||||
|
* `module`: Contains a sub-folder 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 openswoole.
|
||||||
|
|
||||||
|
## 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 components which work closer to the database, like entity repositories, are excluded because of their nature.
|
||||||
|
|
||||||
|
* **Database tests**: These are integration tests that run against a real database, and only cover components which work closer to the database.
|
||||||
|
|
||||||
|
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 with openswoole, and test it from the outside by interacting with the REST API.
|
||||||
|
|
||||||
|
These are the best tests to catch regressions, and to verify everything behaves as expected.
|
||||||
|
|
||||||
|
They use Postgres as the database engine, and include some fixtures that ensure the same data exists at the beginning of the execution.
|
||||||
|
|
||||||
|
Since the app instance is run on a process different from the one running the tests, when a test fails it might not be obvious why. To help debugging that, the app will dump all its logs inside `data/log/api-tests`, where you will find the `shlink.log` and `access.log` files.
|
||||||
|
|
||||||
|
* **CLI tests**: These are E2E tests too, but they test console commands instead of REST endpoints.
|
||||||
|
|
||||||
|
They use Maria DB as the database engine, and include the same fixtures as the API tests, that ensure the same data exists at the beginning of the execution.
|
||||||
|
|
||||||
|
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 in parallel. 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 Postgres database engine is used.
|
||||||
|
* Run `./indocker composer test:cli` to run CLI E2E tests. For these, the Maria DB database engine is used.
|
||||||
|
* Run `./indocker composer infect:test` to 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, parallelizing non-conflicting tasks as much as possible.
|
||||||
|
|
||||||
|
## Pull request process
|
||||||
|
|
||||||
|
**Important!**: Before starting to work on a pull request, make sure you always [open an issue](https://github.com/shlinkio/shlink/issues/new/choose) first.
|
||||||
|
|
||||||
|
This is important because any contribution needs to be discussed first. Maybe there's someone else already working on something similar, or there are other considerations to have in mind.
|
||||||
|
|
||||||
|
Once everything is clear, to provide a pull request 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.
|
||||||
|
|
||||||
|
## Architectural Decision Records
|
||||||
|
|
||||||
|
The project includes logs for some architectural decisions, using the [adr](https://adr.github.io/) proposal.
|
||||||
|
|
||||||
|
If you are curious or want to understand why something has been built in some specific way, [take a look at them](docs/adr).
|
||||||
83
Dockerfile
83
Dockerfile
@@ -1,44 +1,53 @@
|
|||||||
FROM php:7.4.5-alpine3.11 as base
|
FROM php:8.1.9-alpine3.16 as base
|
||||||
|
|
||||||
ARG SHLINK_VERSION=2.1.4
|
ARG SHLINK_VERSION=latest
|
||||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||||
ENV SWOOLE_VERSION 4.4.18
|
ARG SHLINK_RUNTIME=openswoole
|
||||||
|
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
|
||||||
|
ENV OPENSWOOLE_VERSION 4.11.1
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||||
|
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
||||||
ENV LC_ALL "C"
|
ENV LC_ALL "C"
|
||||||
|
|
||||||
WORKDIR /etc/shlink
|
WORKDIR /etc/shlink
|
||||||
|
|
||||||
|
# Install required PHP extensions
|
||||||
RUN \
|
RUN \
|
||||||
# Install mysql and calendar
|
# Temp install dev dependencies needed to compile the extensions
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar && \
|
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev && \
|
||||||
# Install sqlite
|
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
||||||
apk add --no-cache sqlite-libs sqlite-dev && \
|
apk add --no-cache sqlite-libs && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||||
# Install postgres
|
# Remove temp dev extensions, and install prod equivalents that are required at runtime
|
||||||
apk add --no-cache postgresql-dev && \
|
apk del .dev-deps && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_pgsql && \
|
apk add --no-cache postgresql icu libzip libpng
|
||||||
# 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 swoole 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 swoole-${SWOOLE_VERSION} pdo_sqlsrv && \
|
|
||||||
docker-php-ext-enable swoole pdo_sqlsrv && \
|
|
||||||
apk del .phpize-deps && \
|
|
||||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
|
||||||
|
|
||||||
|
# Install openswoole and sqlsrv driver for x86_64 builds
|
||||||
|
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
||||||
|
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||||
|
pecl install openswoole-${OPENSWOOLE_VERSION} && \
|
||||||
|
docker-php-ext-enable openswoole ; \
|
||||||
|
fi; \
|
||||||
|
if [ $(uname -m) == "x86_64" ]; then \
|
||||||
|
wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
apk add --no-cache --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \
|
||||||
|
docker-php-ext-enable pdo_sqlsrv && \
|
||||||
|
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
|
||||||
|
fi; \
|
||||||
|
apk del .phpize-deps
|
||||||
|
|
||||||
# Install shlink
|
# Install shlink
|
||||||
FROM base as builder
|
FROM base as builder
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=composer:1.10.1 /usr/bin/composer ./composer.phar
|
COPY --from=composer:2 /usr/bin/composer ./composer.phar
|
||||||
RUN apk add --no-cache git && \
|
RUN apk add --no-cache git && \
|
||||||
php composer.phar install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction && \
|
php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction && \
|
||||||
|
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||||
|
php composer.phar remove spiral/roadrunner spiral/roadrunner-jobs --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interactionc ; \
|
||||||
|
elif [ $SHLINK_RUNTIME == 'rr' ]; then \
|
||||||
|
php composer.phar remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction ; \
|
||||||
|
fi; \
|
||||||
php composer.phar clear-cache && \
|
php composer.phar clear-cache && \
|
||||||
rm -r docker composer.* && \
|
rm -r docker composer.* && \
|
||||||
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
||||||
@@ -49,17 +58,27 @@ FROM base
|
|||||||
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
||||||
|
|
||||||
COPY --from=builder /etc/shlink .
|
COPY --from=builder /etc/shlink .
|
||||||
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink
|
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink && \
|
||||||
|
if [ "$SHLINK_RUNTIME" == 'rr' ]; then \
|
||||||
|
php ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr ; \
|
||||||
|
fi;
|
||||||
|
|
||||||
# Expose swoole port
|
# Expose default port
|
||||||
EXPOSE 8080
|
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 config specific for the image
|
||||||
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
|
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/shlink_in_docker.local.php config/autoload/shlink_in_docker.local.php
|
||||||
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
||||||
|
|
||||||
|
# Change the ownership of /etc/shlink/data to be writable, then change the user to non-root
|
||||||
|
# FIXME Disabled for now, as it conflicts with ENABLE_PERIODIC_VISIT_LOCATE, which is used to configure a cron as root.
|
||||||
|
# Ref: https://github.com/shlinkio/shlink/issues/1132
|
||||||
|
#RUN chown 1001 /etc/shlink/data
|
||||||
|
#RUN chown 1001 /etc/shlink/data/locks
|
||||||
|
#RUN chown 1001 /etc/shlink/data/proxies
|
||||||
|
#RUN chown 1001 /etc/shlink/data/cache
|
||||||
|
#RUN chown 1001 /etc/shlink/data/log
|
||||||
|
#USER 1001
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016-2020 Alejandro Celaya
|
Copyright (c) 2016-2021 Alejandro Celaya
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
336
README.md
336
README.md
@@ -1,44 +1,48 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://travis-ci.org/shlinkio/shlink)
|
[](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Continuous+integration%22)
|
||||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
[](https://app.codecov.io/gh/shlinkio/shlink)
|
||||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
[](https://dashboard.stryker-mutator.io/reports/github.com/shlinkio/shlink/develop)
|
||||||
[](https://packagist.org/packages/shlinkio/shlink)
|
[](https://packagist.org/packages/shlinkio/shlink)
|
||||||
[](https://hub.docker.com/r/shlinkio/shlink/)
|
[](https://hub.docker.com/r/shlinkio/shlink/)
|
||||||
[](https://github.com/shlinkio/shlink/blob/master/LICENSE)
|
[](https://github.com/shlinkio/shlink/blob/main/LICENSE)
|
||||||
|
[](https://twitter.com/shlinkio)
|
||||||
[](https://slnk.to/donate)
|
[](https://slnk.to/donate)
|
||||||
|
|
||||||
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain.
|
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
|
||||||
|
|
||||||
> This document references Shlink 2.x. If you are using an older version and want to upgrade, follow the [UPGRADE](UPGRADE.md) doc.
|
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [Full documentation](#full-documentation)
|
||||||
|
- [Docker image](#docker-image)
|
||||||
|
- [Self-hosted](#self-hosted)
|
||||||
- [Download](#download)
|
- [Download](#download)
|
||||||
- [Configure](#configure)
|
- [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)
|
- [Using shlink](#using-shlink)
|
||||||
- [Shlink CLI Help](#shlink-cli-help)
|
- [Contributing](#contributing)
|
||||||
- [Multiple domains](#multiple-domains)
|
|
||||||
- [Management](#management)
|
|
||||||
- [Visits](#visits)
|
|
||||||
- [Special redirects](#special-redirects)
|
|
||||||
|
|
||||||
## Installation
|
## Full documentation
|
||||||
|
|
||||||
> These are the steps needed to install Shlink if you plan to manually host it.
|
This document contains the very basics to get started with Shlink. If you want to learn everything you can do with it, visit the [full searchable documentation](https://shlink.io/documentation/).
|
||||||
>
|
|
||||||
> Alternatively, you can use the official docker image. If that's your intention, jump directly to [Using a docker image](#using-a-docker-image)
|
## Docker image
|
||||||
|
|
||||||
|
You can learn how to use the official docker image by reading [the docs](https://shlink.io/documentation/install-docker-image/).
|
||||||
|
|
||||||
|
The idea is that you can just generate a container using the image and provide the custom config via env vars.
|
||||||
|
|
||||||
|
## Self hosted
|
||||||
|
|
||||||
First, make sure the host where you are going to run shlink fulfills these requirements:
|
First, make sure the host where you are going to run shlink fulfills these requirements:
|
||||||
|
|
||||||
* PHP 7.4 or greater with JSON, curl, PDO and gd extensions enabled.
|
* PHP 8.1
|
||||||
* MySQL, MariaDB, PostgreSQL, Microsoft SQL Server or SQLite.
|
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
||||||
* The web server of your choice with PHP integration (Apache or Nginx recommended).
|
* apcu extension is recommended if you don't plan to use openswoole.
|
||||||
|
* xml extension is required if you want to generate QR codes in svg format.
|
||||||
|
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||||
|
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
|
||||||
|
* You will also need the corresponding pdo variation for the database you are planning to use: `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv` or `pdo_sqlite`.
|
||||||
|
* The [openswoole](https://openswoole.com/) PHP extension (if you plan to serve Shlink with openswoole) or the web server of your choice with PHP integration (like Apache or Nginx).
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ In order to run Shlink, you will need a built version of the project. There are
|
|||||||
|
|
||||||
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
|
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
|
||||||
|
|
||||||
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*_dist.zip` file that suits your needs. You will find one for every supported PHP version and with/without openswoole integration.
|
||||||
|
|
||||||
Finally, decompress the file in the location of your choice.
|
Finally, decompress the file in the location of your choice.
|
||||||
|
|
||||||
@@ -58,11 +62,13 @@ In order to run Shlink, you will need a built version of the project. There are
|
|||||||
|
|
||||||
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
||||||
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
|
* 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).
|
* Run `./build.sh 3.0.0`, replacing the version with the version number you are going to build (the version number is used as part of the generated dist file name, and to set the value returned when running `shlink -V` from the command line).
|
||||||
|
|
||||||
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.
|
After that, you will have a dist file inside the `build` directory, that you need to decompress in the location of 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 the generated dist file to it.
|
> **Note**
|
||||||
|
>
|
||||||
|
> This is the process used when releasing new Shlink versions. After tagging the new version with git, the GitHub release is automatically created by a [GitHub workflow](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Publish+release%22), attaching the generated dist file to it.
|
||||||
|
|
||||||
### Configure
|
### Configure
|
||||||
|
|
||||||
@@ -70,280 +76,28 @@ Despite how you built the project, you now need to configure it, by following th
|
|||||||
|
|
||||||
* If you are going to use MySQL, MariaDB, PostgreSQL or Microsoft SQL Server, create an empty database with the name of your choice.
|
* 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.
|
* 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.**
|
* Set up the application by running the `vendor/bin/shlink-installer 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.**
|
||||||
* 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.
|
* 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.
|
||||||
|
|
||||||
### Serve
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
* **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.4-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:**
|
|
||||||
|
|
||||||
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. 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 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` 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.
|
|
||||||
|
|
||||||
**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 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.
|
|
||||||
|
|
||||||
## Using shlink
|
## Using shlink
|
||||||
|
|
||||||
Once shlink is installed, there are two main ways to interact with it:
|
Once shlink is installed, there are two main ways to interact with it:
|
||||||
|
|
||||||
* **The command line**. Try running `bin/cli` and see all the [available commands](#shlink-cli-help).
|
* **The command line**: Try running `bin/cli` to see all the available commands.
|
||||||
|
|
||||||
All of those commands can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
|
All of them can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
|
||||||
|
|
||||||
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.
|
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/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
|
* **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.
|
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 hosted by yourself.
|
||||||
|
|
||||||
Both the API and CLI allow you to do the same operations, except for API key management, which can be done from the command line interface only.
|
Both the API and CLI allow you to do mostly the same operations, except for API key management, which can be done from the command line interface only.
|
||||||
|
|
||||||
### Shlink CLI Help
|
## Contributing
|
||||||
|
|
||||||
```
|
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.
|
||||||
Usage:
|
|
||||||
command [options] [arguments]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Display this help message
|
|
||||||
-q, --quiet Do not output any message
|
|
||||||
-V, --version Display this application version
|
|
||||||
--ansi Force ANSI output
|
|
||||||
--no-ansi Disable ANSI output
|
|
||||||
-n, --no-interaction Do not ask any interactive question
|
|
||||||
-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
|
|
||||||
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.
|
|
||||||
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.
|
|
||||||
short-url
|
|
||||||
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.
|
|
||||||
visit
|
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
50
UPGRADE.md
50
UPGRADE.md
@@ -1,5 +1,53 @@
|
|||||||
# Upgrading
|
# Upgrading
|
||||||
|
|
||||||
|
## From v2.x to v3.x
|
||||||
|
|
||||||
|
### Changes in REST API
|
||||||
|
|
||||||
|
* The `type` property returned when trying to delete a URL that reached the visits threshold, when using the `DELETE /short-urls/{shortCode}` endpoint, is now `INVALID_SHORT_URL_DELETION` instead of `INVALID_SHORTCODE_DELETION`.
|
||||||
|
* The `INVALID_AUTHORIZATION` error no longer includes the `expectedTypes` property. Use `expectedHeaders` one instead.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint no longer allows ordering by `visitsCount`, `visitCount` or `originalUrl`. Use `visits` instead of the first two, and `longUrl` instead of the last one.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint no longer allows providing the ordering params with array notation, as in `/shortUrls?orderBy[longUrl]=DESC`. Instead, use the following notation `/shortUrls?orderBy=longUrl-DESC`.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint now has a default ordering of newest-to-oldest. Use `/shortUrls?orderBy=dateCreated-ASC` in order to keep the oldest-to-newest behavior.
|
||||||
|
* Requests expecting a body no longer support url-encoded payloads. Instead, always use JSON bodies with `Content-Type: application/json`.
|
||||||
|
* The next endpoints have been removed:
|
||||||
|
* `PUT /rest/v2/short-urls/{shortCode}/tags`: Use the `PATCH /rest/v2/short-urls/{shortCode}` endpoint to set the short URL tags.
|
||||||
|
* `POST /rest/v2/tags`: Use `POST /rest/v2/short-urls` or `PATCH /rest/v2/short-urls/{shortCodes}` to create new tags already attached to a short URL. Creating orphan tags makes no sense.
|
||||||
|
|
||||||
|
### Changes in CLI
|
||||||
|
|
||||||
|
* The next commands have been removed:
|
||||||
|
* `short-url:generate`: Use `short-url:create` instead.
|
||||||
|
* `tag:create`: Creating orphan tags makes no sense.
|
||||||
|
* Params in camelCase format are no longer supported. They all have an equivalent kebab-case replacement. (for example, from `--startDate` to `--start-date`).
|
||||||
|
* The `short-url:create` command no longer accepts the `--no-validate-url` flag. Now URLs are never validated, unless `--validate-url` is passed.
|
||||||
|
* The CLI installer tool entry-points have changed.
|
||||||
|
* `bin/install`: replaced by `vendor/bin/shlink-installer install`
|
||||||
|
* `bin/update`: replaced by `vendor/bin/shlink-installer update`
|
||||||
|
* `bin/set-option`: replaced by `vendor/bin/shlink-installer set-option`
|
||||||
|
|
||||||
|
### Changes in config
|
||||||
|
|
||||||
|
* The next env vars have been removed:
|
||||||
|
* `INVALID_SHORT_URL_REDIRECT_TO`: Replaced by `DEFAULT_INVALID_SHORT_URL_REDIRECT`.
|
||||||
|
* `REGULAR_404_REDIRECT_TO`: Replaced by `DEFAULT_REGULAR_404_REDIRECT`.
|
||||||
|
* `BASE_URL_REDIRECT_TO`: Replaced by `DEFAULT_BASE_URL_REDIRECT`.
|
||||||
|
* `SHORT_DOMAIN_HOST`: Replaced by `DEFAULT_DOMAIN`.
|
||||||
|
* `SHORT_DOMAIN_SCHEMA`: Replaced by `IS_HTTPS_ENABLED`.
|
||||||
|
* `USE_HTTPS`: Replaced by `IS_HTTPS_ENABLED`.
|
||||||
|
* `VALIDATE_URLS`: There's no replacement. URLs are not validated, unless explicitly requested during creation or edition.
|
||||||
|
* The next env vars behavior has changed:
|
||||||
|
* `DELETE_SHORT_URL_THRESHOLD`: Now, if this env var is not provided, the "visits threshold" won't be checked at all when deleting short URLs. Make sure you explicitly provide a value if you want to enable this feature.
|
||||||
|
* Environment variables now have precedence over configuration set via the installer tool.
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
* A default GeoLite2 license key is no longer provided. If you don't provide your own as explained in [the docs](https://shlink.io/documentation/geolite-license-key/), Shlink will not try to update the file anymore.
|
||||||
|
* The docker image no longer accepts providing configuration via json files mounted in the `config/params` folder. Only env vars are supported now.
|
||||||
|
* If you were manually serving Shlink with swoole, the entry script has to be changed from `/path/to/shlink/vendor/bin/mezzio-swoole start` to `/path/to/shlink/vendor/bin/laminas mezzio:swoole:start`
|
||||||
|
* The `GET /{shortCode}/qr-code/{size}` url has been removed. Use `GET /{shortCode}/qr-code?size={size}` instead.
|
||||||
|
* Regular swoole extension is no longer supported. Use openswoole instead, as a direct replacement. In most of the cases you just need to uninstall one and install the other, the rest is transparent.
|
||||||
|
|
||||||
## From v1.x to v2.x
|
## From v1.x to v2.x
|
||||||
|
|
||||||
### PHP 7.4 required
|
### PHP 7.4 required
|
||||||
@@ -28,7 +76,7 @@ These routes have been removed, but have a direct replacement:
|
|||||||
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
||||||
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
* `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.
|
When using the old ones, a 404 status will be returned now.
|
||||||
|
|
||||||
### Removed command and route aliases
|
### Removed command and route aliases
|
||||||
|
|
||||||
|
|||||||
7
bin/cli
7
bin/cli
@@ -3,5 +3,8 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
$run = require __DIR__ . '/../config/run.php';
|
use Symfony\Component\Console\Application;
|
||||||
$run(true);
|
|
||||||
|
/** @var Application $app */
|
||||||
|
$app = require __DIR__ . '/../config/cli-app.php';
|
||||||
|
$app->run();
|
||||||
|
|||||||
12
bin/doctrine
Executable file
12
bin/doctrine
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||||
|
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||||
|
|
||||||
|
/** @var EntityManager $app */
|
||||||
|
$em = require __DIR__ . '/../config/entity-manager.php';
|
||||||
|
ConsoleRunner::run(new SingleManagerProvider($em));
|
||||||
12
bin/install
12
bin/install
@@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[$install] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$install();
|
|
||||||
32
bin/roadrunner-worker.php
Normal file
32
bin/roadrunner-worker.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Mezzio\Application;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Shlinkio\Shlink\EventDispatcher\RoadRunner\RoadRunnerTaskConsumerToListener;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
|
||||||
|
(static function (): void {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
$rrMode = env('RR_MODE');
|
||||||
|
|
||||||
|
if ($rrMode === 'http') {
|
||||||
|
// This was spin-up as a web worker
|
||||||
|
$app = $container->get(Application::class);
|
||||||
|
$worker = $container->get(PSR7Worker::class);
|
||||||
|
|
||||||
|
while ($req = $worker->waitRequest()) {
|
||||||
|
try {
|
||||||
|
$worker->respond($app->handle($req));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$worker->getWorker()->error((string) $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,18 +1,38 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
export APP_ENV=test
|
export APP_ENV=test
|
||||||
export DB_DRIVER=mysql
|
export TEST_ENV=api
|
||||||
|
export TEST_RUNTIME="${TEST_RUNTIME:-"openswoole"}"
|
||||||
|
export DB_DRIVER="${DB_DRIVER:-"postgres"}"
|
||||||
|
export GENERATE_COVERAGE="${GENERATE_COVERAGE:-"no"}"
|
||||||
|
|
||||||
|
# Reset logs
|
||||||
|
OUTPUT_LOGS=data/log/api-tests/output.log
|
||||||
|
rm -rf data/log/api-tests
|
||||||
|
mkdir data/log/api-tests
|
||||||
|
touch $OUTPUT_LOGS
|
||||||
|
|
||||||
# Try to stop server just in case it hanged in last execution
|
# Try to stop server just in case it hanged in last execution
|
||||||
vendor/bin/mezzio-swoole stop
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||||
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f
|
||||||
|
|
||||||
echo 'Starting server...'
|
echo 'Starting server...'
|
||||||
vendor/bin/mezzio-swoole start -d
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:start -d
|
||||||
sleep 2
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -c=config/roadrunner/.rr.dev.yml \
|
||||||
|
-o=http.address=0.0.0.0:9999 \
|
||||||
|
-o=logs.encoding=json \
|
||||||
|
-o=logs.channels.http.encoding=json \
|
||||||
|
-o=logs.channels.server.encoding=json \
|
||||||
|
-o=logs.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.http.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.server.output="${PWD}/${OUTPUT_LOGS}" &
|
||||||
|
sleep 2 # Let's give the server a couple of seconds to start
|
||||||
|
|
||||||
phpdbg -qrr vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always $*
|
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $*
|
||||||
testsExitCode=$?
|
testsExitCode=$?
|
||||||
|
|
||||||
vendor/bin/mezzio-swoole stop
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||||
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -c config/roadrunner/.rr.dev.yml -o=http.address=0.0.0.0:9999
|
||||||
|
|
||||||
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
||||||
exit $testsExitCode
|
exit $testsExitCode
|
||||||
|
|||||||
12
bin/update
12
bin/update
@@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[, $update] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$update();
|
|
||||||
36
build.sh
36
build.sh
@@ -1,32 +1,46 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ "$#" -ne 1 ]]; then
|
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || ([ "$#" == 2 ] && [ "$2" != "--no-swoole" ]); then
|
||||||
echo "Usage:" >&2
|
echo "Usage:" >&2
|
||||||
echo " $0 {version}" >&2
|
echo " $0 {version} [--no-swoole]" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
version=$1
|
version=$1
|
||||||
builtcontent="./build/shlink_${version}_dist"
|
noSwoole=$2
|
||||||
|
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
||||||
|
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_openswoole"
|
||||||
|
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
||||||
|
builtContent="./build/${distId}"
|
||||||
projectdir=$(pwd)
|
projectdir=$(pwd)
|
||||||
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
||||||
|
|
||||||
# Copy project content to temp dir
|
# Copy project content to temp dir
|
||||||
echo 'Copying project files...'
|
echo 'Copying project files...'
|
||||||
rm -rf "${builtcontent}"
|
rm -rf "${builtContent}"
|
||||||
mkdir -p "${builtcontent}"
|
mkdir -p "${builtContent}"
|
||||||
rsync -av * "${builtcontent}" \
|
rsync -av * "${builtContent}" \
|
||||||
--exclude=*docker* \
|
--exclude=*docker* \
|
||||||
--exclude=Dockerfile \
|
--exclude=Dockerfile \
|
||||||
--include=.htaccess \
|
--include=.htaccess \
|
||||||
|
--include=config/roadrunner/.rr.yml \
|
||||||
--exclude-from=./.dockerignore
|
--exclude-from=./.dockerignore
|
||||||
cd "${builtcontent}"
|
cd "${builtContent}"
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
echo "Installing dependencies with $composerBin..."
|
echo "Installing dependencies with $composerBin..."
|
||||||
|
composerFlags="--optimize-autoloader --no-progress --no-interaction"
|
||||||
${composerBin} self-update
|
${composerBin} self-update
|
||||||
${composerBin} install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction
|
${composerBin} install --no-dev --prefer-dist $composerFlags
|
||||||
|
|
||||||
|
if [[ $noSwoole ]]; then
|
||||||
|
# If generating a dist not for openswoole, uninstall mezzio-swoole
|
||||||
|
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
|
||||||
|
else
|
||||||
|
# If generating a dist for openswoole, uninstall RoadRunner
|
||||||
|
${composerBin} remove spiral/roadrunner spiral/roadrunner-jobs --with-all-dependencies --update-no-dev $composerFlags
|
||||||
|
fi
|
||||||
|
|
||||||
# Delete development files
|
# Delete development files
|
||||||
echo 'Deleting dev files...'
|
echo 'Deleting dev files...'
|
||||||
@@ -38,9 +52,9 @@ sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
|||||||
# Compressing file
|
# Compressing file
|
||||||
echo 'Compressing files...'
|
echo 'Compressing files...'
|
||||||
cd "${projectdir}"/build
|
cd "${projectdir}"/build
|
||||||
rm -f ./shlink_${version}_dist.zip
|
rm -f ./${distId}.zip
|
||||||
zip -ry ./shlink_${version}_dist.zip ./shlink_${version}_dist
|
zip -ry ./${distId}.zip ./${distId}
|
||||||
cd "${projectdir}"
|
cd "${projectdir}"
|
||||||
rm -rf "${builtcontent}"
|
rm -rf "${builtContent}"
|
||||||
|
|
||||||
echo 'Done!'
|
echo 'Done!'
|
||||||
|
|||||||
224
composer.json
224
composer.json
@@ -12,64 +12,70 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.4",
|
"php": "^8.1",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"akrabat/ip-address-middleware": "^1.0",
|
"akrabat/ip-address-middleware": "^2.1",
|
||||||
"cakephp/chronos": "^1.2",
|
"cakephp/chronos": "^2.3",
|
||||||
"doctrine/cache": "^1.9",
|
"doctrine/migrations": "^3.5",
|
||||||
"doctrine/dbal": "^2.10",
|
"doctrine/orm": "^2.12",
|
||||||
"doctrine/migrations": "^2.2",
|
"endroid/qr-code": "^4.4",
|
||||||
"doctrine/orm": "^2.7",
|
"geoip2/geoip2": "^2.12",
|
||||||
"endroid/qr-code": "^3.6",
|
"guzzlehttp/guzzle": "^7.4",
|
||||||
"geoip2/geoip2": "^2.9",
|
"happyr/doctrine-specification": "^2.0",
|
||||||
"guzzlehttp/guzzle": "^6.5.1",
|
"jaybizzle/crawler-detect": "^1.2.110",
|
||||||
"laminas/laminas-config": "^3.3",
|
"laminas/laminas-config": "^3.7",
|
||||||
"laminas/laminas-config-aggregator": "^1.1",
|
"laminas/laminas-config-aggregator": "^1.8",
|
||||||
"laminas/laminas-dependency-plugin": "^1.0",
|
"laminas/laminas-diactoros": "^2.14",
|
||||||
"laminas/laminas-diactoros": "^2.1.3",
|
"laminas/laminas-inputfilter": "^2.19",
|
||||||
"laminas/laminas-inputfilter": "^2.10",
|
"laminas/laminas-servicemanager": "^3.16",
|
||||||
"laminas/laminas-paginator": "^2.8",
|
"laminas/laminas-stdlib": "^3.11",
|
||||||
"laminas/laminas-servicemanager": "^3.4",
|
"lcobucci/jwt": "^4.1",
|
||||||
"laminas/laminas-stdlib": "^3.2",
|
"league/uri": "^6.7",
|
||||||
"lcobucci/jwt": "^4.0@alpha",
|
"lstrojny/functional-php": "^1.17",
|
||||||
"lstrojny/functional-php": "^1.9",
|
"mezzio/mezzio": "^3.11",
|
||||||
"mezzio/mezzio": "^3.2",
|
"mezzio/mezzio-fastroute": "^3.5",
|
||||||
"mezzio/mezzio-fastroute": "^3.0",
|
"mezzio/mezzio-problem-details": "^1.6",
|
||||||
"mezzio/mezzio-helpers": "^5.3",
|
"mezzio/mezzio-swoole": "^4.3",
|
||||||
"mezzio/mezzio-platesrenderer": "^2.1",
|
"mlocati/ip-lib": "^1.18",
|
||||||
"mezzio/mezzio-problem-details": "^1.1",
|
"ocramius/proxy-manager": "^2.14",
|
||||||
"mezzio/mezzio-swoole": "^2.6.4",
|
"pagerfanta/core": "^3.6",
|
||||||
"monolog/monolog": "^2.0",
|
"php-middleware/request-id": "^4.1",
|
||||||
"nikolaposa/monolog-factory": "^3.0",
|
"pugx/shortid-php": "^1.0",
|
||||||
"ocramius/proxy-manager": "^2.7.0",
|
"ramsey/uuid": "^4.3",
|
||||||
"phly/phly-event-dispatcher": "^1.0",
|
"shlinkio/shlink-common": "^5.1",
|
||||||
"php-middleware/request-id": "^4.0",
|
"shlinkio/shlink-config": "^2.1",
|
||||||
"predis/predis": "^1.1",
|
"shlinkio/shlink-event-dispatcher": "^2.6",
|
||||||
"pugx/shortid-php": "^0.5",
|
"shlinkio/shlink-importer": "^4.0",
|
||||||
"ramsey/uuid": "^3.9",
|
"shlinkio/shlink-installer": "^8.2",
|
||||||
"shlinkio/shlink-common": "^3.1.0",
|
"shlinkio/shlink-ip-geolocation": "^3.1",
|
||||||
"shlinkio/shlink-config": "^1.0",
|
"spiral/roadrunner": "^2.11",
|
||||||
"shlinkio/shlink-event-dispatcher": "^1.4",
|
"spiral/roadrunner-jobs": "^2.3",
|
||||||
"shlinkio/shlink-installer": "^5.0.0",
|
"symfony/console": "^6.1",
|
||||||
"shlinkio/shlink-ip-geolocation": "^1.4",
|
"symfony/filesystem": "^6.1",
|
||||||
"symfony/console": "^5.0",
|
"symfony/lock": "^6.1",
|
||||||
"symfony/filesystem": "^5.0",
|
"symfony/process": "^6.1",
|
||||||
"symfony/lock": "^5.0",
|
"symfony/string": "^6.1"
|
||||||
"symfony/mercure": "^0.3.0",
|
|
||||||
"symfony/process": "^5.0"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"devster/ubench": "^2.0",
|
"cebe/php-openapi": "^1.7",
|
||||||
"dms/phpunit-arraysubset-asserts": "^0.2.0",
|
"devster/ubench": "^2.1",
|
||||||
"eaglewu/swoole-ide-helper": "dev-master",
|
"dms/phpunit-arraysubset-asserts": "^0.4.0",
|
||||||
"infection/infection": "^0.16.1",
|
"infection/infection": "^0.26.15",
|
||||||
"phpstan/phpstan": "^0.12.18",
|
"openswoole/ide-helper": "~4.11.1",
|
||||||
"phpunit/phpunit": "~9.0.1",
|
"phpspec/prophecy-phpunit": "^2.0",
|
||||||
|
"phpstan/phpstan": "^1.8",
|
||||||
|
"phpstan/phpstan-doctrine": "^1.3",
|
||||||
|
"phpstan/phpstan-symfony": "^1.2",
|
||||||
|
"phpunit/php-code-coverage": "^9.2",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"shlinkio/php-coding-standard": "~2.1.0",
|
"shlinkio/php-coding-standard": "~2.3.0",
|
||||||
"shlinkio/shlink-test-utils": "^1.4",
|
"shlinkio/shlink-test-utils": "^3.3",
|
||||||
"symfony/var-dumper": "^5.0"
|
"symfony/var-dumper": "^6.1",
|
||||||
|
"veewee/composer-run-parallel": "^1.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -78,89 +84,117 @@
|
|||||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"config/constants.php",
|
||||||
"module/Core/functions/functions.php"
|
"module/Core/functions/functions.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
||||||
|
"ShlinkioCliTest\\Shlink\\CLI\\": "module/CLI/test-cli",
|
||||||
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
||||||
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
|
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
|
||||||
"ShlinkioTest\\Shlink\\Core\\": [
|
"ShlinkioDbTest\\Shlink\\Rest\\": "module/Rest/test-db",
|
||||||
"module/Core/test",
|
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
||||||
"module/Core/test-db"
|
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db"
|
||||||
]
|
},
|
||||||
}
|
"files": [
|
||||||
|
"config/test/constants.php"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ci": [
|
"ci": [
|
||||||
"@cs",
|
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||||
"@stan",
|
"@parallel infect:test:api infect:test:cli infect:ci:unit infect:ci:db"
|
||||||
"@test:ci",
|
|
||||||
"@infect:ci"
|
|
||||||
],
|
],
|
||||||
"cs": "phpcs",
|
"cs": "phpcs",
|
||||||
"cs:fix": "phpcbf",
|
"cs:fix": "phpcbf",
|
||||||
"stan": "phpstan analyse module/*/src/ module/*/config config docker/config --level=6",
|
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/config config docker/config data/migrations --level=8",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:unit",
|
"@parallel test:unit test:db",
|
||||||
"@test:db",
|
"@parallel test:api test:cli"
|
||||||
"@test:api"
|
|
||||||
],
|
],
|
||||||
"test:ci": [
|
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --testdox",
|
||||||
"@test:unit:ci",
|
"test:unit:ci": "@test:unit --coverage-php=build/coverage-unit.cov --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
||||||
"@test:db",
|
"test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
|
||||||
"@test:api:ci"
|
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||||
],
|
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
||||||
"test:unit": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
|
"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:unit:ci": "@test:unit --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/junit.xml",
|
|
||||||
"test:db": [
|
|
||||||
"@test:db:sqlite",
|
|
||||||
"@test:db:mysql",
|
|
||||||
"@test:db:maria",
|
|
||||||
"@test:db:postgres",
|
|
||||||
"@test:db:ms"
|
|
||||||
],
|
|
||||||
"test:db:sqlite": "APP_ENV=test phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-db.cov --testdox -c phpunit-db.xml",
|
|
||||||
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
|
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
|
||||||
"test:db:maria": "DB_DRIVER=maria 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:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
||||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
||||||
"test:api": "bin/test/run-api-tests.sh",
|
"test:api": "bin/test/run-api-tests.sh",
|
||||||
"test:api:ci": "@test:api --coverage-php build/coverage-api.cov",
|
"test:api:rr": "TEST_RUNTIME=rr bin/test/run-api-tests.sh",
|
||||||
"test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage",
|
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
|
||||||
"infect": "infection --threads=4 --min-msi=80 --log-verbosity=default --only-covered",
|
"test:api:pretty": "GENERATE_COVERAGE=pretty composer test:api",
|
||||||
"infect:ci": "@infect --coverage=build --skip-initial-tests",
|
"test:cli": "APP_ENV=test DB_DRIVER=maria TEST_ENV=cli php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-cli.xml --log-junit=build/coverage-cli/junit.xml",
|
||||||
"infect:show": "@infect --show-mutations",
|
"test:cli:ci": "GENERATE_COVERAGE=yes composer test:cli",
|
||||||
|
"test:cli:pretty": "GENERATE_COVERAGE=pretty composer test:cli",
|
||||||
|
"infect:ci:base": "infection --threads=max --only-covered --only-covering-test-cases --skip-initial-tests",
|
||||||
|
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=84",
|
||||||
|
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
|
||||||
|
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json",
|
||||||
|
"infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=80 --configuration=infection-cli.json",
|
||||||
|
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli",
|
||||||
"infect:test": [
|
"infect:test": [
|
||||||
"@test:unit:ci",
|
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
||||||
"@infect:ci"
|
"@infect:ci"
|
||||||
],
|
],
|
||||||
|
"infect:test:unit": [
|
||||||
|
"@test:unit:ci",
|
||||||
|
"@infect:ci:unit"
|
||||||
|
],
|
||||||
|
"infect:test:api": [
|
||||||
|
"@test:api:ci",
|
||||||
|
"@infect:ci:api"
|
||||||
|
],
|
||||||
|
"infect:test:cli": [
|
||||||
|
"@test:cli:ci",
|
||||||
|
"@infect:ci:cli"
|
||||||
|
],
|
||||||
|
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
||||||
|
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
||||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||||
},
|
},
|
||||||
"scripts-descriptions": {
|
"scripts-descriptions": {
|
||||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"</>",
|
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
|
||||||
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
||||||
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
||||||
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
|
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
|
||||||
"test": "<fg=blue;options=bold>Runs all test suites</>",
|
"test": "<fg=blue;options=bold>Runs all test suites</>",
|
||||||
"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": "<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:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
|
||||||
|
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||||
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB, PostgreSQL and MsSQL</>",
|
"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:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
||||||
|
"test:db:sqlite:ci": "<fg=blue;options=bold>Runs database test suites on a SQLite database, generating all needed reports and logs for CI envs</>",
|
||||||
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL 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: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:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
||||||
|
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a Microsoft SQL Server database</>",
|
||||||
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
||||||
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage for CI</>",
|
||||||
"infect": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",
|
"test:api:pretty": "<fg=blue;options=bold>Runs API test suites, and generates code coverage in HTML format</>",
|
||||||
"infect:ci": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
"test:cli": "<fg=blue;options=bold>Runs CLI test suites</>",
|
||||||
"infect:show": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing and shows applied mutators</>",
|
"test:cli:ci": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage for CI</>",
|
||||||
"infect:test": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",
|
"test:cli:pretty": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage in HTML format</>",
|
||||||
|
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
||||||
|
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
||||||
|
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
||||||
|
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
||||||
|
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
||||||
|
"swagger:inline": "<fg=blue;options=bold>Inlines swagger docs in a single file</>",
|
||||||
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true
|
"sort-packages": true,
|
||||||
|
"platform-check": false,
|
||||||
|
"allow-plugins": {
|
||||||
|
"composer/package-versions-deprecated": true,
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
|
"infection/extension-installer": true,
|
||||||
|
"veewee/composer-run-parallel": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ return [
|
|||||||
'app_options' => [
|
'app_options' => [
|
||||||
'name' => 'Shlink',
|
'name' => 'Shlink',
|
||||||
'version' => '%SHLINK_VERSION%',
|
'version' => '%SHLINK_VERSION%',
|
||||||
'disable_track_param' => null,
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
11
config/autoload/app_options.local.php.dist
Normal file
11
config/autoload/app_options.local.php.dist
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'app_options' => [
|
||||||
|
'version' => 'latest',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -7,6 +7,9 @@ use Laminas\ConfigAggregator\ConfigAggregator;
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
'debug' => false,
|
'debug' => false,
|
||||||
ConfigAggregator::ENABLE_CACHE => true,
|
|
||||||
|
// Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console
|
||||||
|
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||||
|
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
11
config/autoload/cors.global.php
Normal file
11
config/autoload/cors.global.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'cors' => [
|
||||||
|
'max_age' => 3600,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -4,11 +4,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
return [
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
'delete_short_urls' => [
|
return (static function (): array {
|
||||||
'visits_threshold' => 15,
|
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD->loadFromEnv();
|
||||||
'check_visits_threshold' => true,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
return [
|
||||||
|
|
||||||
|
'delete_short_urls' => [
|
||||||
|
'check_visits_threshold' => $threshold !== null,
|
||||||
|
'visits_threshold' => (int) ($threshold ?? DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,17 +2,33 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Mezzio\Container;
|
use Mezzio\Container;
|
||||||
|
use Psr\Http\Client\ClientInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestFactoryInterface;
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use Psr\Http\Message\UploadedFileFactoryInterface;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
use Spiral\RoadRunner\WorkerInterface;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
PSR7Worker::class => ConfigAbstractFactory::class,
|
||||||
|
],
|
||||||
|
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Mezzio\Application::class => [
|
Mezzio\Application::class => [
|
||||||
Container\ApplicationConfigInjectionDelegator::class,
|
Container\ApplicationConfigInjectionDelegator::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'aliases' => [
|
||||||
|
ClientInterface::class => Client::class,
|
||||||
|
],
|
||||||
|
|
||||||
'lazy_services' => [
|
'lazy_services' => [
|
||||||
'proxies_target_dir' => 'data/proxies',
|
'proxies_target_dir' => 'data/proxies',
|
||||||
'proxies_namespace' => 'ShlinkProxy',
|
'proxies_namespace' => 'ShlinkProxy',
|
||||||
@@ -20,4 +36,13 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
PSR7Worker::class => [
|
||||||
|
WorkerInterface::class,
|
||||||
|
ServerRequestFactoryInterface::class,
|
||||||
|
StreamFactoryInterface::class,
|
||||||
|
UploadedFileFactoryInterface::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,21 +2,59 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Common;
|
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
use function Functional\contains;
|
||||||
|
|
||||||
'entity_manager' => [
|
return (static function (): array {
|
||||||
'orm' => [
|
$driver = EnvVars::DB_DRIVER->loadFromEnv();
|
||||||
'proxies_dir' => 'data/proxies',
|
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
|
||||||
'load_mappings_using_functional_style' => true,
|
|
||||||
|
$resolveDriver = static fn () => match ($driver) {
|
||||||
|
'postgres' => 'pdo_pgsql',
|
||||||
|
'mssql' => 'pdo_sqlsrv',
|
||||||
|
default => 'pdo_mysql',
|
||||||
|
};
|
||||||
|
$resolveDefaultPort = static fn () => match ($driver) {
|
||||||
|
'postgres' => '5432',
|
||||||
|
'mssql' => '1433',
|
||||||
|
default => '3306',
|
||||||
|
};
|
||||||
|
$resolveCharset = static fn () => match ($driver) {
|
||||||
|
// This does not determine charsets or collations in tables or columns, but the charset used in the data
|
||||||
|
// flowing in the connection, so it has to match what has been set in the database.
|
||||||
|
'maria', 'mysql' => 'utf8mb4',
|
||||||
|
'postgres' => 'utf8',
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
$resolveConnection = static fn () => match ($driver) {
|
||||||
|
null, 'sqlite' => [
|
||||||
|
'driver' => 'pdo_sqlite',
|
||||||
|
'path' => 'data/database.sqlite',
|
||||||
],
|
],
|
||||||
'connection' => [
|
default => [
|
||||||
'user' => '',
|
'driver' => $resolveDriver(),
|
||||||
'password' => '',
|
'dbname' => EnvVars::DB_NAME->loadFromEnv('shlink'),
|
||||||
'dbname' => 'shlink',
|
'user' => EnvVars::DB_USER->loadFromEnv(),
|
||||||
'charset' => 'utf8',
|
'password' => EnvVars::DB_PASSWORD->loadFromEnv(),
|
||||||
|
'host' => EnvVars::DB_HOST->loadFromEnv(EnvVars::DB_UNIX_SOCKET->loadFromEnv()),
|
||||||
|
'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
|
||||||
|
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
|
||||||
|
'charset' => $resolveCharset(),
|
||||||
],
|
],
|
||||||
],
|
};
|
||||||
|
|
||||||
];
|
return [
|
||||||
|
|
||||||
|
'entity_manager' => [
|
||||||
|
'orm' => [
|
||||||
|
'proxies_dir' => 'data/proxies',
|
||||||
|
'load_mappings_using_functional_style' => true,
|
||||||
|
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||||
|
],
|
||||||
|
'connection' => $resolveConnection(),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
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 [
|
return [
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
@@ -17,8 +9,9 @@ return [
|
|||||||
'user' => 'root',
|
'user' => 'root',
|
||||||
'password' => 'root',
|
'password' => 'root',
|
||||||
'driver' => 'pdo_mysql',
|
'driver' => 'pdo_mysql',
|
||||||
'host' => 'shlink_db',
|
'host' => 'shlink_db_mysql',
|
||||||
'driverOptions' => $driverOptions,
|
'dbname' => 'shlink',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ use Laminas\Stratigility\Middleware\ErrorHandler;
|
|||||||
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
|
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
|
||||||
use Shlinkio\Shlink\Common\Logger;
|
use Shlinkio\Shlink\Common\Logger;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Core\toProblemDetailsType;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'problem-details' => [
|
'problem-details' => [
|
||||||
'default_types_map' => [
|
'default_types_map' => [
|
||||||
404 => 'NOT_FOUND',
|
404 => toProblemDetailsType('not-found'),
|
||||||
500 => 'INTERNAL_SERVER_ERROR',
|
500 => toProblemDetailsType('internal-server-error'),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'geolite2' => [
|
'geolite2' => [
|
||||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||||
'temp_dir' => sys_get_temp_dir(),
|
'temp_dir' => __DIR__ . '/../../data',
|
||||||
'license_key' => 'G4Lm0C60yJsnkdPi',
|
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI;
|
||||||
|
|
||||||
use Shlinkio\Shlink\Installer\Config\Option;
|
use Shlinkio\Shlink\Installer\Config\Option;
|
||||||
|
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -14,37 +17,73 @@ return [
|
|||||||
Option\Database\DatabasePortConfigOption::class,
|
Option\Database\DatabasePortConfigOption::class,
|
||||||
Option\Database\DatabaseUserConfigOption::class,
|
Option\Database\DatabaseUserConfigOption::class,
|
||||||
Option\Database\DatabasePasswordConfigOption::class,
|
Option\Database\DatabasePasswordConfigOption::class,
|
||||||
Option\Database\DatabaseSqlitePathConfigOption::class,
|
Option\Database\DatabaseUnixSocketConfigOption::class,
|
||||||
Option\Database\DatabaseMySqlOptionsConfigOption::class,
|
|
||||||
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
||||||
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
||||||
Option\UrlShortener\ValidateUrlConfigOption::class,
|
|
||||||
Option\Visit\VisitsWebhooksConfigOption::class,
|
Option\Visit\VisitsWebhooksConfigOption::class,
|
||||||
|
Option\Visit\OrphanVisitsWebhooksConfigOption::class,
|
||||||
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
||||||
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
||||||
Option\Redirect\Regular404RedirectConfigOption::class,
|
Option\Redirect\Regular404RedirectConfigOption::class,
|
||||||
Option\DisableTrackParamConfigOption::class,
|
|
||||||
Option\Visit\CheckVisitsThresholdConfigOption::class,
|
|
||||||
Option\Visit\VisitsThresholdConfigOption::class,
|
Option\Visit\VisitsThresholdConfigOption::class,
|
||||||
Option\BasePathConfigOption::class,
|
Option\BasePathConfigOption::class,
|
||||||
|
Option\TimezoneConfigOption::class,
|
||||||
Option\Worker\TaskWorkerNumConfigOption::class,
|
Option\Worker\TaskWorkerNumConfigOption::class,
|
||||||
Option\Worker\WebWorkerNumConfigOption::class,
|
Option\Worker\WebWorkerNumConfigOption::class,
|
||||||
Option\RedisServersConfigOption::class,
|
Option\Redis\RedisServersConfigOption::class,
|
||||||
|
Option\Redis\RedisSentinelServiceConfigOption::class,
|
||||||
|
Option\Redis\RedisPubSubConfigOption::class,
|
||||||
Option\UrlShortener\ShortCodeLengthOption::class,
|
Option\UrlShortener\ShortCodeLengthOption::class,
|
||||||
Option\Mercure\EnableMercureConfigOption::class,
|
Option\Mercure\EnableMercureConfigOption::class,
|
||||||
Option\Mercure\MercurePublicUrlConfigOption::class,
|
Option\Mercure\MercurePublicUrlConfigOption::class,
|
||||||
Option\Mercure\MercureInternalUrlConfigOption::class,
|
Option\Mercure\MercureInternalUrlConfigOption::class,
|
||||||
Option\Mercure\MercureJwtSecretConfigOption::class,
|
Option\Mercure\MercureJwtSecretConfigOption::class,
|
||||||
Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
|
Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
|
||||||
Option\UrlShortener\IpAnonymizationConfigOption::class,
|
Option\UrlShortener\RedirectStatusCodeConfigOption::class,
|
||||||
|
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
||||||
|
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
|
||||||
|
Option\UrlShortener\AppendExtraPathConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableTrailingSlashConfigOption::class,
|
||||||
|
Option\Tracking\IpAnonymizationConfigOption::class,
|
||||||
|
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableTrackParamConfigOption::class,
|
||||||
|
Option\Tracking\DisableTrackingFromConfigOption::class,
|
||||||
|
Option\Tracking\DisableTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableIpTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableReferrerTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableUaTrackingConfigOption::class,
|
||||||
|
Option\QrCode\DefaultSizeConfigOption::class,
|
||||||
|
Option\QrCode\DefaultMarginConfigOption::class,
|
||||||
|
Option\QrCode\DefaultFormatConfigOption::class,
|
||||||
|
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
||||||
|
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'installation_commands' => [
|
'installation_commands' => [
|
||||||
'db_create_schema' => [
|
InstallationCommand::DB_CREATE_SCHEMA->value => [
|
||||||
'command' => 'bin/cli db:create',
|
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
|
||||||
],
|
],
|
||||||
'db_migrate' => [
|
InstallationCommand::DB_MIGRATE->value => [
|
||||||
'command' => 'bin/cli db:migrate',
|
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
|
||||||
|
],
|
||||||
|
InstallationCommand::ORM_PROXIES->value => [
|
||||||
|
'command' => 'bin/doctrine orm:generate-proxies',
|
||||||
|
],
|
||||||
|
InstallationCommand::ORM_CLEAR_CACHE->value => [
|
||||||
|
'command' => 'bin/doctrine orm:clear-cache:metadata',
|
||||||
|
],
|
||||||
|
InstallationCommand::GEOLITE_DOWNLOAD_DB->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Visit\DownloadGeoLiteDbCommand::NAME,
|
||||||
|
],
|
||||||
|
InstallationCommand::API_KEY_GENERATE->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Api\GenerateKeyCommand::NAME,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||||
use Shlinkio\Shlink\Common\Lock\RetryLockStoreDelegatorFactory;
|
|
||||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -24,16 +24,12 @@ return [
|
|||||||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
||||||
'lock_store' => 'local_lock_store',
|
|
||||||
|
|
||||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
'redis_lock_store' => Lock\Store\RedisStore::class,
|
||||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||||
],
|
],
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Lock\Store\RedisStore::class => [
|
|
||||||
RetryLockStoreDelegatorFactory::class,
|
|
||||||
],
|
|
||||||
Lock\LockFactory::class => [
|
Lock\LockFactory::class => [
|
||||||
LoggerAwareDelegatorFactory::class,
|
LoggerAwareDelegatorFactory::class,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,72 +4,36 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Formatter;
|
use Monolog\Level;
|
||||||
use Monolog\Handler;
|
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Monolog\Processor;
|
|
||||||
use MonologFactory\DiContainerLoggerFactory;
|
|
||||||
use PhpMiddleware\RequestId;
|
use PhpMiddleware\RequestId;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
|
||||||
use const PHP_EOL;
|
$common = [
|
||||||
|
'level' => Level::Info->value,
|
||||||
$processors = [
|
'processors' => [RequestId\MonologProcessor::class],
|
||||||
'exception_with_new_line' => [
|
'line_format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%',
|
||||||
'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 [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'name' => 'Shlink',
|
'type' => LoggerType::FILE->value,
|
||||||
'handlers' => [
|
...$common,
|
||||||
'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,
|
|
||||||
],
|
],
|
||||||
'Access' => [
|
'Access' => [
|
||||||
'name' => 'Access',
|
'type' => LoggerType::STREAM->value,
|
||||||
'handlers' => [
|
...$common,
|
||||||
'access_handler' => [
|
|
||||||
'name' => Handler\StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
'formatter' => $formatter,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'processors' => $processors,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'factories' => [
|
'factories' => [
|
||||||
'Logger_Shlink' => [DiContainerLoggerFactory::class, 'Shlink'],
|
'Logger_Shlink' => [LoggerFactory::class, 'Shlink'],
|
||||||
'Logger_Access' => [DiContainerLoggerFactory::class, 'Access'],
|
'Logger_Access' => [LoggerFactory::class, 'Access'],
|
||||||
],
|
],
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
'logger' => 'Logger_Shlink',
|
'logger' => 'Logger_Shlink',
|
||||||
@@ -82,7 +46,7 @@ return [
|
|||||||
'swoole-http-server' => [
|
'swoole-http-server' => [
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'logger-name' => 'Logger_Access',
|
'logger-name' => 'Logger_Access',
|
||||||
'format' => '%h %l %u "%r" %>s %b',
|
'format' => '%u "%r" %>s %B',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,33 +2,18 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
|
||||||
$isSwoole = extension_loaded('swoole');
|
$isSwoole = extension_loaded('openswoole');
|
||||||
|
|
||||||
// For swoole, send logs to standard output
|
|
||||||
$handler = $isSwoole
|
|
||||||
? [
|
|
||||||
'name' => StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'handlers' => [
|
// For swoole, send logs as stream
|
||||||
'shlink_handler' => $handler,
|
'type' => $isSwoole ? LoggerType::STREAM->value : LoggerType::FILE->value,
|
||||||
],
|
'level' => Level::Debug->value,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -4,33 +4,38 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
||||||
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
||||||
use Symfony\Component\Mercure\Publisher;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Mercure\PublisherInterface;
|
use Symfony\Component\Mercure\Hub;
|
||||||
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
|
|
||||||
return [
|
return (static function (): array {
|
||||||
|
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv();
|
||||||
|
|
||||||
'mercure' => [
|
return [
|
||||||
'public_hub_url' => null,
|
|
||||||
'internal_hub_url' => null,
|
|
||||||
'jwt_secret' => null,
|
|
||||||
'jwt_issuer' => 'Shlink',
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
'mercure' => [
|
||||||
'delegators' => [
|
'public_hub_url' => $publicUrl,
|
||||||
LcobucciJwtProvider::class => [
|
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv($publicUrl),
|
||||||
LazyServiceFactory::class,
|
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
|
||||||
|
'jwt_issuer' => 'Shlink',
|
||||||
|
],
|
||||||
|
|
||||||
|
'dependencies' => [
|
||||||
|
'delegators' => [
|
||||||
|
LcobucciJwtProvider::class => [
|
||||||
|
LazyServiceFactory::class,
|
||||||
|
],
|
||||||
|
Hub::class => [
|
||||||
|
LazyServiceFactory::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
Publisher::class => [
|
'lazy_services' => [
|
||||||
LazyServiceFactory::class,
|
'class_map' => [
|
||||||
|
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
||||||
|
Hub::class => HubInterface::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'lazy_services' => [
|
|
||||||
'class_map' => [
|
|
||||||
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
|
||||||
Publisher::class => PublisherInterface::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => 'http://localhost:8001',
|
'public_hub_url' => 'http://localhost:8001',
|
||||||
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
||||||
'jwt_secret' => 'mercure_jwt_key',
|
'jwt_secret' => 'mercure_jwt_key_long_enough_to_avoid_error',
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,25 +5,26 @@ declare(strict_types=1);
|
|||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||||
use Mezzio\Helper;
|
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
use Mezzio\Router;
|
use Mezzio\Router;
|
||||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
||||||
|
use RKA\Middleware\IpAddress;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'middleware_pipeline' => [
|
'middleware_pipeline' => [
|
||||||
'error-handler' => [
|
'error-handler' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Helper\ContentLengthMiddleware::class,
|
ContentLengthMiddleware::class,
|
||||||
|
RequestIdMiddleware::class,
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
|
Rest\Middleware\CrossDomainMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'error-handler-rest' => [
|
'error-handler-rest' => [
|
||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Rest\Middleware\CrossDomainMiddleware::class,
|
|
||||||
RequestIdMiddleware::class,
|
|
||||||
ProblemDetails\ProblemDetailsMiddleware::class,
|
ProblemDetails\ProblemDetailsMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -44,6 +45,7 @@ return [
|
|||||||
'rest' => [
|
'rest' => [
|
||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
|
||||||
Router\Middleware\ImplicitOptionsMiddleware::class,
|
Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||||
Rest\Middleware\BodyParserMiddleware::class,
|
Rest\Middleware\BodyParserMiddleware::class,
|
||||||
Rest\Middleware\AuthenticationMiddleware::class,
|
Rest\Middleware\AuthenticationMiddleware::class,
|
||||||
@@ -64,6 +66,11 @@ return [
|
|||||||
],
|
],
|
||||||
'not-found' => [
|
'not-found' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
// This middleware is in front of tracking actions explicitly. Putting here for orphan visits tracking
|
||||||
|
IpAddress::class,
|
||||||
|
Core\ErrorHandler\NotFoundTypeResolverMiddleware::class,
|
||||||
|
Core\ShortUrl\Middleware\ExtraPathRedirectMiddleware::class,
|
||||||
|
Core\ErrorHandler\NotFoundTrackerMiddleware::class,
|
||||||
Core\ErrorHandler\NotFoundRedirectHandler::class,
|
Core\ErrorHandler\NotFoundRedirectHandler::class,
|
||||||
Core\ErrorHandler\NotFoundTemplateHandler::class,
|
Core\ErrorHandler\NotFoundTemplateHandler::class,
|
||||||
],
|
],
|
||||||
|
|||||||
27
config/autoload/qr-codes.global.php
Normal file
27
config/autoload/qr-codes.global.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_MARGIN;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ROUND_BLOCK_SIZE;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'qr_codes' => [
|
||||||
|
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(DEFAULT_QR_CODE_SIZE),
|
||||||
|
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
|
||||||
|
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
|
||||||
|
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(
|
||||||
|
DEFAULT_QR_CODE_ERROR_CORRECTION,
|
||||||
|
),
|
||||||
|
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(
|
||||||
|
DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
21
config/autoload/rabbit.global.php
Normal file
21
config/autoload/rabbit.global.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'rabbitmq' => [
|
||||||
|
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(false),
|
||||||
|
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
|
||||||
|
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv('5672'),
|
||||||
|
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
|
||||||
|
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||||
|
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
'legacy_visits_publishing' => (bool) EnvVars::RABBITMQ_LEGACY_VISITS_PUBLISHING->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
14
config/autoload/rabbit.local.php.dist
Normal file
14
config/autoload/rabbit.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'rabbitmq' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'host' => 'shlink_rabbitmq',
|
||||||
|
'user' => 'rabbit',
|
||||||
|
'password' => 'rabbit',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -2,12 +2,24 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'not_found_redirects' => [
|
'not_found_redirects' => [
|
||||||
'invalid_short_url' => null, // Formerly url_shortener.not_found_short_url.redirect_to
|
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT->loadFromEnv(),
|
||||||
'regular_404' => null,
|
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT->loadFromEnv(),
|
||||||
'base_url' => null,
|
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT->loadFromEnv(),
|
||||||
|
],
|
||||||
|
|
||||||
|
'redirects' => [
|
||||||
|
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
|
||||||
|
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
|
||||||
|
DEFAULT_REDIRECT_CACHE_LIFETIME,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
27
config/autoload/redis.global.php
Normal file
27
config/autoload/redis.global.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
|
||||||
|
$pubSub = [
|
||||||
|
'redis' => [
|
||||||
|
'pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return match ($redisServers) {
|
||||||
|
null => $pubSub,
|
||||||
|
default => [
|
||||||
|
'cache' => [
|
||||||
|
'redis' => [
|
||||||
|
'servers' => $redisServers,
|
||||||
|
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
...$pubSub,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -7,12 +7,13 @@ return [
|
|||||||
'cache' => [
|
'cache' => [
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'servers' => 'tcp://shlink_redis:6379',
|
'servers' => 'tcp://shlink_redis:6379',
|
||||||
// 'servers' => [
|
|
||||||
// 'tcp://shlink_redis:6379',
|
|
||||||
// ],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'pub_sub_enabled' => true,
|
||||||
|
],
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use PhpMiddleware\RequestId;
|
use PhpMiddleware\RequestId;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\Processor\BackwardsCompatibleMonologProcessorDelegator;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ return [
|
|||||||
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
||||||
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
|
'delegators' => [
|
||||||
|
RequestId\MonologProcessor::class => [
|
||||||
|
BackwardsCompatibleMonologProcessorDelegator::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Mezzio\Router\FastRouteRouter;
|
use Mezzio\Router\FastRouteRouter;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'router' => [
|
'router' => [
|
||||||
'base_path' => '',
|
'base_path' => EnvVars::BASE_PATH->loadFromEnv(''),
|
||||||
|
|
||||||
'fastroute' => [
|
'fastroute' => [
|
||||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
||||||
|
|||||||
107
config/autoload/routes.config.php
Normal file
107
config/autoload/routes.config.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
|
use Fig\Http\Message\RequestMethodInterface;
|
||||||
|
use RKA\Middleware\IpAddress;
|
||||||
|
use Shlinkio\Shlink\Core\Action as CoreAction;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Shlinkio\Shlink\Rest\Action;
|
||||||
|
use Shlinkio\Shlink\Rest\ConfigProvider;
|
||||||
|
use Shlinkio\Shlink\Rest\Middleware;
|
||||||
|
use Shlinkio\Shlink\Rest\Middleware\Mercure\NotConfiguredMercureErrorHandler;
|
||||||
|
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
$contentNegotiationMiddleware = Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class;
|
||||||
|
$dropDomainMiddleware = Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class;
|
||||||
|
$overrideDomainMiddleware = Middleware\ShortUrl\OverrideDomainMiddleware::class;
|
||||||
|
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false) ? '[/]' : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// The order of the routes defined here matters. Changing it might cause path conflicts
|
||||||
|
'routes' => [
|
||||||
|
// Rest
|
||||||
|
...ConfigProvider::applyRoutesPrefix([
|
||||||
|
Action\HealthAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Visits
|
||||||
|
Action\Visit\ShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\Visit\TagVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\DomainVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\GlobalVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\OrphanVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\NonOrphanVisitsAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Short URLs
|
||||||
|
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
||||||
|
$contentNegotiationMiddleware,
|
||||||
|
$dropDomainMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\SingleStepCreateShortUrlAction::getRouteDef([
|
||||||
|
$contentNegotiationMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\EditShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\DeleteShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ResolveShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ListShortUrlsAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
Action\Tag\ListTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\TagsStatsAction::getRouteDef(),
|
||||||
|
Action\Tag\DeleteTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\UpdateTagAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Domains
|
||||||
|
Action\Domain\ListDomainsAction::getRouteDef(),
|
||||||
|
Action\Domain\DomainRedirectsAction::getRouteDef(),
|
||||||
|
|
||||||
|
Action\MercureInfoAction::getRouteDef([NotConfiguredMercureErrorHandler::class]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Non-rest
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RobotsAction::class,
|
||||||
|
'path' => '/robots.txt',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\RobotsAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\PixelAction::class,
|
||||||
|
'path' => '/{shortCode}/track',
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
CoreAction\PixelAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\QrCodeAction::class,
|
||||||
|
'path' => '/{shortCode}/qr-code',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\QrCodeAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RedirectAction::class,
|
||||||
|
'path' => sprintf('/{shortCode}%s', $shortUrlRouteSuffix),
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
CoreAction\RedirectAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -2,21 +2,30 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
||||||
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
|
||||||
'enable_coroutine' => false,
|
|
||||||
|
|
||||||
'swoole-http-server' => [
|
return (static function (): array {
|
||||||
'host' => '0.0.0.0',
|
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM->loadFromEnv(16);
|
||||||
'process-name' => 'shlink',
|
|
||||||
|
|
||||||
'options' => [
|
return [
|
||||||
'worker_num' => 16,
|
|
||||||
'task_worker_num' => 16,
|
'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',
|
||||||
|
'port' => (int) EnvVars::PORT->loadFromEnv(8080),
|
||||||
|
'process-name' => 'shlink',
|
||||||
|
|
||||||
|
'options' => [
|
||||||
|
'worker_num' => (int) EnvVars::WEB_WORKER_NUM->loadFromEnv(16),
|
||||||
|
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
|
||||||
use Mezzio\Swoole\HotCodeReload\FileWatcher\InotifyFileWatcher;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
'mezzio-swoole' => [
|
||||||
@@ -13,10 +10,4 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'factories' => [
|
|
||||||
InotifyFileWatcher::class => InvokableFactory::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'templates' => [
|
|
||||||
'extension' => 'phtml',
|
|
||||||
],
|
|
||||||
|
|
||||||
'plates' => [
|
|
||||||
'extensions' => [
|
|
||||||
// extension service names or instances
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
43
config/autoload/tracking.global.php
Normal file
43
config/autoload/tracking.global.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
/** @var string|null $disableTrackingFrom */
|
||||||
|
$disableTrackingFrom = EnvVars::DISABLE_TRACKING_FROM->loadFromEnv();
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'tracking' => [
|
||||||
|
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
||||||
|
// This applies only if IP address tracking is enabled
|
||||||
|
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(true),
|
||||||
|
|
||||||
|
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||||
|
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
|
||||||
|
|
||||||
|
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||||
|
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
|
||||||
|
|
||||||
|
// If true, visits will not be tracked at all
|
||||||
|
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||||
|
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// If true, the referrer will not be tracked
|
||||||
|
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// If true, the user agent will not be tracked
|
||||||
|
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
||||||
|
'disable_tracking_from' => $disableTrackingFrom === null
|
||||||
|
? []
|
||||||
|
: array_map(trim(...), explode(',', $disableTrackingFrom)),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -2,19 +2,29 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
||||||
|
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
||||||
|
|
||||||
'url_shortener' => [
|
return (static function (): array {
|
||||||
'domain' => [
|
$shortCodesLength = max(
|
||||||
'schema' => 'https',
|
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
|
||||||
'hostname' => '',
|
MIN_SHORT_CODES_LENGTH,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'url_shortener' => [
|
||||||
|
'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
|
||||||
|
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv(true)) ? 'https' : 'http',
|
||||||
|
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(''),
|
||||||
|
],
|
||||||
|
'default_short_codes_length' => $shortCodesLength,
|
||||||
|
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(false),
|
||||||
|
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(false),
|
||||||
|
'multi_segment_slugs_enabled' => (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(false),
|
||||||
],
|
],
|
||||||
'validate_url' => false,
|
|
||||||
'anonymize_remote_addr' => true,
|
|
||||||
'visits_webhooks' => [],
|
|
||||||
'default_short_codes_length' => DEFAULT_SHORT_CODES_LENGTH,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,13 +2,22 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Config\runningInOpenswoole;
|
||||||
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'domain' => [
|
'domain' => [
|
||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => 'localhost:8080',
|
'hostname' => sprintf('localhost:%s', match (true) {
|
||||||
|
runningInRoadRunner() => '8800',
|
||||||
|
runningInOpenswoole() => '8080',
|
||||||
|
default => '8000',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
|
'auto_resolve_titles' => true,
|
||||||
|
// 'multi_segment_slugs_enabled' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
20
config/autoload/webhooks.global.php
Normal file
20
config/autoload/webhooks.global.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
// Deprecated. Webhooks are no longer supported. To be removed in Shlink 4.0.0
|
||||||
|
return (static function (): array {
|
||||||
|
$webhooks = EnvVars::VISITS_WEBHOOKS->loadFromEnv();
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'visits_webhooks' => [
|
||||||
|
'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
|
||||||
|
'notify_orphan_visits_to_webhooks' =>
|
||||||
|
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
12
config/cli-app.php
Normal file
12
config/cli-app.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Symfony\Component\Console\Application as CliApp;
|
||||||
|
|
||||||
|
return (static function (): CliApp {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/container.php';
|
||||||
|
return $container->get(CliApp::class);
|
||||||
|
})();
|
||||||
@@ -4,13 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||||
use Laminas\ServiceManager\ServiceManager;
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
|
|
||||||
return (function () {
|
|
||||||
/** @var ContainerInterface|ServiceManager $container */
|
|
||||||
$container = include __DIR__ . '/container.php';
|
|
||||||
$em = $container->get(EntityManager::class);
|
|
||||||
|
|
||||||
|
return (static function () {
|
||||||
|
/** @var EntityManager $em */
|
||||||
|
$em = include __DIR__ . '/entity-manager.php';
|
||||||
return ConsoleRunner::createHelperSet($em);
|
return ConsoleRunner::createHelperSet($em);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -5,31 +5,49 @@ declare(strict_types=1);
|
|||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Laminas\ConfigAggregator;
|
use Laminas\ConfigAggregator;
|
||||||
|
use Laminas\Diactoros;
|
||||||
use Mezzio;
|
use Mezzio;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
|
use Mezzio\Swoole;
|
||||||
|
use Shlinkio\Shlink\Config\ConfigAggregator\EnvVarLoaderProvider;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function class_exists;
|
||||||
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function Shlinkio\Shlink\Config\openswooleIsInstalled;
|
||||||
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
|
||||||
|
use const PHP_SAPI;
|
||||||
|
|
||||||
|
$isTestEnv = env('APP_ENV') === 'test';
|
||||||
|
$enableSwoole = PHP_SAPI === 'cli' && openswooleIsInstalled() && ! runningInRoadRunner();
|
||||||
|
|
||||||
return (new ConfigAggregator\ConfigAggregator([
|
return (new ConfigAggregator\ConfigAggregator([
|
||||||
|
! $isTestEnv
|
||||||
|
? new EnvVarLoaderProvider('config/params/generated_config.php', Core\Config\EnvVars::values())
|
||||||
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
Mezzio\ConfigProvider::class,
|
Mezzio\ConfigProvider::class,
|
||||||
Mezzio\Router\ConfigProvider::class,
|
Mezzio\Router\ConfigProvider::class,
|
||||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||||
Mezzio\Plates\ConfigProvider::class,
|
$enableSwoole && class_exists(Swoole\ConfigProvider::class)
|
||||||
Mezzio\Swoole\ConfigProvider::class,
|
? Swoole\ConfigProvider::class
|
||||||
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
ProblemDetails\ConfigProvider::class,
|
ProblemDetails\ConfigProvider::class,
|
||||||
|
Diactoros\ConfigProvider::class,
|
||||||
Common\ConfigProvider::class,
|
Common\ConfigProvider::class,
|
||||||
Config\ConfigProvider::class,
|
Config\ConfigProvider::class,
|
||||||
|
Importer\ConfigProvider::class,
|
||||||
IpGeolocation\ConfigProvider::class,
|
IpGeolocation\ConfigProvider::class,
|
||||||
EventDispatcher\ConfigProvider::class,
|
EventDispatcher\ConfigProvider::class,
|
||||||
Core\ConfigProvider::class,
|
Core\ConfigProvider::class,
|
||||||
CLI\ConfigProvider::class,
|
CLI\ConfigProvider::class,
|
||||||
Rest\ConfigProvider::class,
|
Rest\ConfigProvider::class,
|
||||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||||
env('APP_ENV') === 'test'
|
$isTestEnv
|
||||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
||||||
: new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
|
// Routes have to be loaded last
|
||||||
|
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
|
||||||
], 'data/cache/app_config.php', [
|
], 'data/cache/app_config.php', [
|
||||||
Core\Config\SimplifiedConfigParser::class,
|
|
||||||
Core\Config\BasePathPrefixer::class,
|
Core\Config\BasePathPrefixer::class,
|
||||||
Core\Config\DeprecatedConfigParser::class,
|
Core\Config\MultiSegmentSlugProcessor::class,
|
||||||
]))->getMergedConfig();
|
]))->getMergedConfig();
|
||||||
|
|||||||
22
config/constants.php
Normal file
22
config/constants.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
|
use Fig\Http\Message\StatusCodeInterface;
|
||||||
|
|
||||||
|
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||||
|
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||||
|
const MIN_SHORT_CODES_LENGTH = 4;
|
||||||
|
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
|
||||||
|
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||||
|
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||||
|
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
|
||||||
|
const DEFAULT_QR_CODE_SIZE = 300;
|
||||||
|
const DEFAULT_QR_CODE_MARGIN = 0;
|
||||||
|
const DEFAULT_QR_CODE_FORMAT = 'png';
|
||||||
|
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
||||||
|
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
||||||
|
const MIN_TASK_WORKERS = 4;
|
||||||
|
const MIGRATIONS_TABLE = 'migrations';
|
||||||
@@ -3,14 +3,18 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\ServiceManager;
|
use Laminas\ServiceManager\ServiceManager;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
// This is one of the first files loaded. Configure the timezone here
|
||||||
|
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv(date_default_timezone_get()));
|
||||||
|
|
||||||
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
// 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
|
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||||
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
||||||
@@ -18,7 +22,7 @@ if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build container
|
// Build container
|
||||||
return (function () {
|
return (static function (): ServiceManager {
|
||||||
$config = require __DIR__ . '/config.php';
|
$config = require __DIR__ . '/config.php';
|
||||||
$container = new ServiceManager($config['dependencies']);
|
$container = new ServiceManager($config['dependencies']);
|
||||||
$container->setService('config', $config);
|
$container->setService('config', $config);
|
||||||
|
|||||||
13
config/entity-manager.php
Normal file
13
config/entity-manager.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
return (static function (): EntityManagerInterface {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/container.php';
|
||||||
|
return $container->get(EntityManager::class);
|
||||||
|
})();
|
||||||
49
config/roadrunner/.rr.dev.yml
Normal file
49
config/roadrunner/.rr.dev.yml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
version: '2.7'
|
||||||
|
|
||||||
|
rpc:
|
||||||
|
listen: tcp://127.0.0.1:6001
|
||||||
|
|
||||||
|
server:
|
||||||
|
command: 'php ../../bin/roadrunner-worker.php'
|
||||||
|
|
||||||
|
http:
|
||||||
|
address: '0.0.0.0:8080'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: '../../public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
timeout: 300
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
mode: development
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
level: debug
|
||||||
|
server:
|
||||||
|
level: debug
|
||||||
|
metrics:
|
||||||
|
level: debug
|
||||||
|
|
||||||
|
reload:
|
||||||
|
interval: 1s
|
||||||
|
patterns: ['.php']
|
||||||
|
services:
|
||||||
|
http:
|
||||||
|
dirs: ['../../bin', '../../config', '../../data/migrations', '../../module', '../../vendor']
|
||||||
|
recursive: true
|
||||||
|
jobs:
|
||||||
|
dirs: ['../../bin', '../../config', '../../data/migrations', '../../module', '../../vendor']
|
||||||
|
recursive: true
|
||||||
36
config/roadrunner/.rr.yml
Normal file
36
config/roadrunner/.rr.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
version: '2.7'
|
||||||
|
|
||||||
|
rpc:
|
||||||
|
listen: tcp://127.0.0.1:6001
|
||||||
|
|
||||||
|
server:
|
||||||
|
command: 'php -dopcache.enable_cli=1 -dopcache.validate_timestamps=0 ../../bin/roadrunner-worker.php'
|
||||||
|
|
||||||
|
http:
|
||||||
|
address: '0.0.0.0:${PORT}'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: '../../public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: ${WEB_WORKER_NUM}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
timeout: 300 # 5 minutes
|
||||||
|
pool:
|
||||||
|
num_workers: ${TASK_WORKER_NUM}
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
mode: production
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
level: info # Log all http requests, set to info to disable
|
||||||
|
server:
|
||||||
|
level: debug # Everything written to worker stderr is logged
|
||||||
@@ -4,12 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Mezzio\Application;
|
use Mezzio\Application;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Symfony\Component\Console\Application as CliApp;
|
|
||||||
|
|
||||||
return function (bool $isCli = false): void {
|
return static function (): void {
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/container.php';
|
$container = include __DIR__ . '/container.php';
|
||||||
$app = $container->get($isCli ? CliApp::class : Application::class);
|
$app = $container->get(Application::class);
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,12 +7,32 @@ namespace Shlinkio\Shlink\TestUtils;
|
|||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
use function register_shutdown_function;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
|
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||||
|
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = require __DIR__ . '/../container.php';
|
$container = require __DIR__ . '/../container.php';
|
||||||
$testHelper = $container->get(Helper\TestHelper::class);
|
$testHelper = $container->get(Helper\TestHelper::class);
|
||||||
$config = $container->get('config');
|
$config = $container->get('config');
|
||||||
$em = $container->get(EntityManager::class);
|
$em = $container->get(EntityManager::class);
|
||||||
|
$httpClient = $container->get('shlink_test_api_client');
|
||||||
|
|
||||||
$testHelper->createTestDb();
|
// Dump code coverage when process shuts down
|
||||||
ApiTest\ApiTestCase::setApiClient($container->get('shlink_test_api_client'));
|
register_shutdown_function(function () use ($httpClient): void {
|
||||||
|
$httpClient->request(
|
||||||
|
'GET',
|
||||||
|
sprintf('http://%s:%s/api-tests/stop-coverage', API_TESTS_HOST, API_TESTS_PORT),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$testHelper->createTestDb(
|
||||||
|
['bin/cli', 'db:create'],
|
||||||
|
['bin/cli', 'db:migrate'],
|
||||||
|
['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
|
ApiTest\ApiTestCase::setApiClient($httpClient);
|
||||||
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
||||||
|
|||||||
33
config/test/bootstrap_cli_tests.php
Normal file
33
config/test/bootstrap_cli_tests.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\TestUtils;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
use function file_exists;
|
||||||
|
use function unlink;
|
||||||
|
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = require __DIR__ . '/../container.php';
|
||||||
|
$testHelper = $container->get(Helper\TestHelper::class);
|
||||||
|
$config = $container->get('config');
|
||||||
|
$em = $container->get(EntityManager::class);
|
||||||
|
|
||||||
|
// Delete old coverage in PHP, to avoid merging older executions with current one
|
||||||
|
$covFile = __DIR__ . '/../../build/coverage-cli.cov';
|
||||||
|
if (file_exists($covFile)) {
|
||||||
|
unlink($covFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$testHelper->createTestDb(
|
||||||
|
['bin/cli', 'db:create'],
|
||||||
|
['bin/cli', 'db:migrate'],
|
||||||
|
['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
|
CliTest\CliTestCase::setSeedFixturesCallback(
|
||||||
|
static fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []),
|
||||||
|
);
|
||||||
@@ -8,5 +8,10 @@ use Psr\Container\ContainerInterface;
|
|||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = require __DIR__ . '/../container.php';
|
$container = require __DIR__ . '/../container.php';
|
||||||
$container->get(Helper\TestHelper::class)->createTestDb();
|
$container->get(Helper\TestHelper::class)->createTestDb(
|
||||||
|
['bin/cli', 'db:create'],
|
||||||
|
['bin/cli', 'db:migrate'],
|
||||||
|
['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));
|
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 API_TESTS_HOST = '127.0.0.1';
|
||||||
|
const API_TESTS_PORT = 9999;
|
||||||
@@ -6,40 +6,86 @@ namespace Shlinkio\Shlink;
|
|||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||||
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use PDO;
|
use League\Event\EventDispatcher;
|
||||||
|
use Monolog\Level;
|
||||||
|
use PHPUnit\Runner\Version;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||||
|
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||||
|
use SebastianBergmann\CodeCoverage\Filter;
|
||||||
|
use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html;
|
||||||
|
use SebastianBergmann\CodeCoverage\Report\PHP;
|
||||||
|
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||||
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function file_exists;
|
||||||
|
use function Functional\contains;
|
||||||
|
use function Laminas\Stratigility\middleware;
|
||||||
|
use function Shlinkio\Shlink\Config\env;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function sys_get_temp_dir;
|
use function sys_get_temp_dir;
|
||||||
|
|
||||||
$swooleTestingHost = '127.0.0.1';
|
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||||
$swooleTestingPort = 9999;
|
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||||
|
|
||||||
$buildDbConnection = function (): array {
|
$isApiTest = env('TEST_ENV') === 'api';
|
||||||
|
$isCliTest = env('TEST_ENV') === 'cli';
|
||||||
|
$isE2eTest = $isApiTest || $isCliTest;
|
||||||
|
$coverageType = env('GENERATE_COVERAGE');
|
||||||
|
$generateCoverage = contains(['yes', 'pretty'], $coverageType);
|
||||||
|
|
||||||
|
$coverage = null;
|
||||||
|
if ($isE2eTest && $generateCoverage) {
|
||||||
|
$filter = new Filter();
|
||||||
|
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
|
||||||
|
$filter->includeDirectory(__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src');
|
||||||
|
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param 'api'|'cli' $type
|
||||||
|
*/
|
||||||
|
$exportCoverage = static function (string $type = 'api') use (&$coverage, $coverageType): void {
|
||||||
|
if ($coverage === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$basePath = __DIR__ . '/../../build/coverage-' . $type;
|
||||||
|
$covPath = $basePath . '.cov';
|
||||||
|
|
||||||
|
// Every CLI test runs on its own process and dumps the coverage afterwards.
|
||||||
|
// Try to load it and merge it, so that we end up with the whole coverage at the end.
|
||||||
|
if ($type === 'cli' && file_exists($covPath)) {
|
||||||
|
$coverage->merge(require $covPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($coverageType === 'pretty') {
|
||||||
|
(new Html())->process($coverage, $basePath . '/coverage-html');
|
||||||
|
} else {
|
||||||
|
(new PHP())->process($coverage, $covPath);
|
||||||
|
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$buildDbConnection = static function (): array {
|
||||||
$driver = env('DB_DRIVER', 'sqlite');
|
$driver = env('DB_DRIVER', 'sqlite');
|
||||||
$isCi = env('TRAVIS', false);
|
$isCi = env('CI', false);
|
||||||
$getMysqlHost = fn (string $driver) => sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria');
|
$getCiMysqlPort = static fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
||||||
$getCiMysqlPort = fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
|
||||||
|
|
||||||
$driverConfigMap = [
|
return match ($driver) {
|
||||||
'sqlite' => [
|
'sqlite' => [
|
||||||
'driver' => 'pdo_sqlite',
|
'driver' => 'pdo_sqlite',
|
||||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
'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' => [
|
'postgres' => [
|
||||||
'driver' => 'pdo_pgsql',
|
'driver' => 'pdo_pgsql',
|
||||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
||||||
@@ -56,12 +102,24 @@ $buildDbConnection = function (): array {
|
|||||||
'password' => 'Passw0rd!',
|
'password' => 'Passw0rd!',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
],
|
],
|
||||||
];
|
default => [ // mysql and maria
|
||||||
$driverConfigMap['maria'] = $driverConfigMap['mysql'];
|
'driver' => 'pdo_mysql',
|
||||||
|
'host' => $isCi ? '127.0.0.1' : sprintf('shlink_db_%s', $driver),
|
||||||
return $driverConfigMap[$driver] ?? [];
|
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
||||||
|
'user' => 'root',
|
||||||
|
'password' => 'root',
|
||||||
|
'dbname' => 'shlink_test',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$buildTestLoggerConfig = static fn (string $filename) => [
|
||||||
|
'level' => Level::Debug->value,
|
||||||
|
'type' => LoggerType::STREAM->value,
|
||||||
|
'destination' => sprintf('data/log/api-tests/%s', $filename),
|
||||||
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'debug' => true,
|
'debug' => true,
|
||||||
@@ -72,22 +130,54 @@ return [
|
|||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => 'doma.in',
|
'hostname' => 'doma.in',
|
||||||
],
|
],
|
||||||
'validate_url' => true,
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
'mezzio-swoole' => [
|
||||||
'enable_coroutine' => false,
|
'enable_coroutine' => false,
|
||||||
'swoole-http-server' => [
|
'swoole-http-server' => [
|
||||||
'host' => $swooleTestingHost,
|
'host' => API_TESTS_HOST,
|
||||||
'port' => $swooleTestingPort,
|
'port' => API_TESTS_PORT,
|
||||||
'process-name' => 'shlink_test',
|
'process-name' => 'shlink_test',
|
||||||
'options' => [
|
'options' => [
|
||||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
||||||
|
'log_file' => __DIR__ . '/../../data/log/api-tests/output.log',
|
||||||
'enable_coroutine' => false,
|
'enable_coroutine' => false,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'routes' => !$isApiTest ? [] : [
|
||||||
|
[
|
||||||
|
'name' => 'dump_coverage',
|
||||||
|
'path' => '/api-tests/stop-coverage',
|
||||||
|
'middleware' => middleware(static function () use ($exportCoverage) {
|
||||||
|
// TODO I have tried moving this block to a listener so that it's invoked automatically,
|
||||||
|
// but then the coverage is generated empty ¯\_(ツ)_/¯
|
||||||
|
$exportCoverage();
|
||||||
|
return new EmptyResponse();
|
||||||
|
}),
|
||||||
|
'allowed_methods' => ['GET'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'middleware_pipeline' => !$isApiTest ? [] : [
|
||||||
|
'capture_code_coverage' => [
|
||||||
|
'middleware' => middleware(static function (
|
||||||
|
ServerRequestInterface $req,
|
||||||
|
RequestHandlerInterface $handler,
|
||||||
|
) use (&$coverage): ResponseInterface {
|
||||||
|
$coverage?->start($req->getHeaderLine('x-coverage-id'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $handler->handle($req);
|
||||||
|
} finally {
|
||||||
|
$coverage?->stop();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'priority' => 9999,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => null,
|
'public_hub_url' => null,
|
||||||
'internal_hub_url' => null,
|
'internal_hub_url' => null,
|
||||||
@@ -97,13 +187,69 @@ return [
|
|||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'services' => [
|
'services' => [
|
||||||
'shlink_test_api_client' => new Client([
|
'shlink_test_api_client' => new Client([
|
||||||
'base_uri' => sprintf('http://%s:%s/', $swooleTestingHost, $swooleTestingPort),
|
'base_uri' => sprintf('http://%s:%s/', API_TESTS_HOST, API_TESTS_PORT),
|
||||||
'http_errors' => false,
|
'http_errors' => false,
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
'factories' => [
|
'factories' => [
|
||||||
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
|
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
|
||||||
],
|
],
|
||||||
|
'delegators' => $isCliTest ? [
|
||||||
|
Application::class => [
|
||||||
|
static function (
|
||||||
|
ContainerInterface $c,
|
||||||
|
string $serviceName,
|
||||||
|
callable $callback,
|
||||||
|
) use (
|
||||||
|
&$coverage,
|
||||||
|
$exportCoverage,
|
||||||
|
) {
|
||||||
|
/** @var Application $app */
|
||||||
|
$app = $callback();
|
||||||
|
$wrappedEventDispatcher = new EventDispatcher();
|
||||||
|
|
||||||
|
// When the command starts, start collecting coverage
|
||||||
|
$wrappedEventDispatcher->subscribeTo(
|
||||||
|
ConsoleCommandEvent::class,
|
||||||
|
static function () use (&$coverage): void {
|
||||||
|
$id = env('COVERAGE_ID');
|
||||||
|
if ($id === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage?->start($id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// When the command ends, stop collecting coverage
|
||||||
|
$wrappedEventDispatcher->subscribeTo(
|
||||||
|
ConsoleTerminateEvent::class,
|
||||||
|
static function () use (&$coverage, $exportCoverage): void {
|
||||||
|
$id = env('COVERAGE_ID');
|
||||||
|
if ($id === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage?->stop();
|
||||||
|
$exportCoverage('cli');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->setDispatcher(new class ($wrappedEventDispatcher) implements EventDispatcherInterface {
|
||||||
|
public function __construct(private EventDispatcher $wrappedDispatcher)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(object $event, ?string $eventName = null): object
|
||||||
|
{
|
||||||
|
$this->wrappedDispatcher->dispatch($event);
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
] : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
@@ -112,8 +258,14 @@ return [
|
|||||||
|
|
||||||
'data_fixtures' => [
|
'data_fixtures' => [
|
||||||
'paths' => [
|
'paths' => [
|
||||||
|
// TODO These are used for CLI tests too, so maybe should be somewhere else
|
||||||
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'logger' => [
|
||||||
|
'Shlink' => $buildTestLoggerConfig('shlink.log'),
|
||||||
|
'Access' => $buildTestLoggerConfig('access.log'),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
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
|
curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
|
||||||
apt-get update
|
apt-get update
|
||||||
ACCEPT_EULA=Y apt-get install msodbcsql17
|
ACCEPT_EULA=Y apt-get install msodbcsql17
|
||||||
apt-get install unixodbc-dev
|
apt-get install unixodbc-dev
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ server {
|
|||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi.conf;
|
include fastcgi.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/var/log/shlink/shlink_swoole.log {
|
/var/log/shlink/shlink_openswoole.log {
|
||||||
su root root
|
su root root
|
||||||
daily
|
daily
|
||||||
missingok
|
missingok
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
notifempty
|
notifempty
|
||||||
create 0640 root root
|
create 0640 root root
|
||||||
postrotate
|
postrotate
|
||||||
/etc/init.d/shlink_swoole restart
|
/etc/init.d/shlink_openswoole restart
|
||||||
endscript
|
endscript
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
### BEGIN INIT INFO
|
### BEGIN INIT INFO
|
||||||
# Provides: shlink_swoole
|
# Provides: shlink_openswoole
|
||||||
# Required-Start: $local_fs $network $named $time $syslog
|
# Required-Start: $local_fs $network $named $time $syslog
|
||||||
# Required-Stop: $local_fs $network $named $time $syslog
|
# Required-Stop: $local_fs $network $named $time $syslog
|
||||||
# Default-Start: 2 3 4 5
|
# Default-Start: 2 3 4 5
|
||||||
# Default-Stop: 0 1 6
|
# Default-Stop: 0 1 6
|
||||||
# Description: Shlink non-blocking server with swoole
|
# Description: Shlink non-blocking server with openswoole
|
||||||
### END INIT INFO
|
### END INIT INFO
|
||||||
|
|
||||||
SCRIPT=/path/to/shlink/vendor/bin/mezzio-swoole\ start
|
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
|
||||||
RUNAS=root
|
RUNAS=root
|
||||||
|
|
||||||
PIDFILE=/var/run/shlink_swoole.pid
|
PIDFILE=/var/run/shlink_openswoole.pid
|
||||||
LOGDIR=/var/log/shlink
|
LOGDIR=/var/log/shlink
|
||||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
LOGFILE=${LOGDIR}/shlink_openswoole.log
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||||
echo 'Shlink with swoole already running' >&2
|
echo 'Shlink with openswoole already running' >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo 'Starting shlink with swoole' >&2
|
echo 'Starting shlink with openswoole' >&2
|
||||||
mkdir -p "$LOGDIR"
|
mkdir -p "$LOGDIR"
|
||||||
touch "$LOGFILE"
|
touch "$LOGFILE"
|
||||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||||
@@ -30,10 +30,10 @@ start() {
|
|||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||||
echo 'Shlink with swoole not running' >&2
|
echo 'Shlink with openswoole not running' >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo 'Stopping shlink with swoole' >&2
|
echo 'Stopping shlink with openswoole' >&2
|
||||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||||
echo 'Shlink stopped' >&2
|
echo 'Shlink stopped' >&2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
FROM php:7.4.5-fpm-alpine3.11
|
FROM php:8.1.9-fpm-alpine3.16
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.18
|
ENV APCU_VERSION 5.1.21
|
||||||
ENV APCU_BC_VERSION 1.0.5
|
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||||
ENV XDEBUG_VERSION 2.9.0
|
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
# Install common php extensions
|
# Install common php extensions
|
||||||
RUN docker-php-ext-install pdo_mysql
|
RUN docker-php-ext-install pdo_mysql
|
||||||
RUN docker-php-ext-install iconv
|
|
||||||
RUN docker-php-ext-install calendar
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
RUN apk add --no-cache oniguruma-dev
|
RUN apk add --no-cache oniguruma-dev
|
||||||
@@ -31,53 +30,30 @@ RUN docker-php-ext-install gd
|
|||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
|
RUN docker-php-ext-install sockets
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||||
# configure and install
|
&& docker-php-ext-configure apcu \
|
||||||
RUN docker-php-ext-configure apcu\
|
&& docker-php-ext-install apcu \
|
||||||
&& docker-php-ext-install apcu
|
&& rm /tmp/apcu.tar.gz \
|
||||||
# cleanup
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
RUN rm /tmp/apcu.tar.gz
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
# Install APCu-BC extension
|
# Install pcov and sqlsrv driver
|
||||||
ADD https://pecl.php.net/get/apcu_bc-$APCU_BC_VERSION.tgz /tmp/apcu_bc.tar.gz
|
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
|
||||||
# configure and install
|
|
||||||
RUN docker-php-ext-configure apcu-bc\
|
|
||||||
&& docker-php-ext-install apcu-bc
|
|
||||||
# cleanup
|
|
||||||
RUN rm /tmp/apcu_bc.tar.gz
|
|
||||||
|
|
||||||
# Load APCU.ini before APC.ini
|
|
||||||
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 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 && \
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
pecl install pdo_sqlsrv && \
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
docker-php-ext-enable pdo_sqlsrv && \
|
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||||
apk del .phpize-deps && \
|
apk del .phpize-deps && \
|
||||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
# Install composer
|
# Install composer
|
||||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
RUN chmod +x composer.phar
|
|
||||||
RUN mv composer.phar /usr/local/bin/composer
|
|
||||||
|
|
||||||
# Make home directory writable by anyone
|
# Make home directory writable by anyone
|
||||||
RUN chmod 777 /home
|
RUN chmod 777 /home
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ memory_limit=-1
|
|||||||
log_errors_max_len=0
|
log_errors_max_len=0
|
||||||
zend.assertions=1
|
zend.assertions=1
|
||||||
assert.exception=1
|
assert.exception=1
|
||||||
|
pcov.enabled=1
|
||||||
|
pcov.directory=module
|
||||||
|
|||||||
73
data/infra/roadrunner.Dockerfile
Normal file
73
data/infra/roadrunner.Dockerfile
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
FROM php:8.1.9-alpine3.16
|
||||||
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
|
ENV APCU_VERSION 5.1.21
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||||
|
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
||||||
|
|
||||||
|
RUN apk update
|
||||||
|
|
||||||
|
# Install common php extensions
|
||||||
|
RUN docker-php-ext-install pdo_mysql
|
||||||
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
|
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 icu-dev
|
||||||
|
RUN docker-php-ext-install intl
|
||||||
|
|
||||||
|
RUN apk add --no-cache libzip-dev zlib-dev
|
||||||
|
RUN docker-php-ext-install zip
|
||||||
|
|
||||||
|
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 docker-php-ext-install sockets
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
|
# 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 \
|
||||||
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||||
|
&& docker-php-ext-configure apcu \
|
||||||
|
&& docker-php-ext-install apcu \
|
||||||
|
&& rm /tmp/apcu.tar.gz \
|
||||||
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
|
# Install pcov and sqlsrv driver
|
||||||
|
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
|
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||||
|
apk del .phpize-deps && \
|
||||||
|
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
|
# Install composer
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Make home directory writable by anyone
|
||||||
|
RUN chmod 777 /home
|
||||||
|
|
||||||
|
VOLUME /home/shlink
|
||||||
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
|
# Expose roadrunner port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD \
|
||||||
|
# Install dependencies if the vendor dir does not exist
|
||||||
|
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||||
|
# Download roadrunner binary
|
||||||
|
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr ; fi && \
|
||||||
|
# This forces the app to be started every second until the exit code is 0
|
||||||
|
until ./bin/rr serve -c config/roadrunner/.rr.dev.yml; do sleep 1 ; done
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
FROM php:7.4.5-alpine3.11
|
FROM php:8.1.9-alpine3.16
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.18
|
ENV APCU_VERSION 5.1.21
|
||||||
ENV APCU_BC_VERSION 1.0.5
|
ENV INOTIFY_VERSION 3.0.0
|
||||||
ENV INOTIFY_VERSION 2.0.0
|
ENV OPENSWOOLE_VERSION 4.11.1
|
||||||
ENV SWOOLE_VERSION 4.4.18
|
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||||
|
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
# Install common php extensions
|
# Install common php extensions
|
||||||
RUN docker-php-ext-install pdo_mysql
|
RUN docker-php-ext-install pdo_mysql
|
||||||
RUN docker-php-ext-install iconv
|
|
||||||
RUN docker-php-ext-install calendar
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
RUN apk add --no-cache oniguruma-dev
|
RUN apk add --no-cache oniguruma-dev
|
||||||
@@ -32,53 +32,38 @@ RUN docker-php-ext-install gd
|
|||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
|
RUN docker-php-ext-install sockets
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||||
# configure and install
|
&& docker-php-ext-configure apcu \
|
||||||
RUN docker-php-ext-configure apcu\
|
&& docker-php-ext-install apcu \
|
||||||
&& docker-php-ext-install apcu
|
&& rm /tmp/apcu.tar.gz \
|
||||||
# cleanup
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
RUN rm /tmp/apcu.tar.gz
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
# Install APCu-BC extension
|
|
||||||
ADD https://pecl.php.net/get/apcu_bc-$APCU_BC_VERSION.tgz /tmp/apcu_bc.tar.gz
|
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
|
||||||
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
|
||||||
# configure and install
|
|
||||||
RUN docker-php-ext-configure apcu-bc\
|
|
||||||
&& docker-php-ext-install apcu-bc
|
|
||||||
# cleanup
|
|
||||||
RUN rm /tmp/apcu_bc.tar.gz
|
|
||||||
|
|
||||||
# Load APCU.ini before APC.ini
|
|
||||||
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 inotify extension
|
# Install inotify extension
|
||||||
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
||||||
RUN mkdir -p /usr/src/php/ext/inotify\
|
RUN mkdir -p /usr/src/php/ext/inotify \
|
||||||
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1
|
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1 \
|
||||||
# configure and install
|
&& docker-php-ext-configure inotify \
|
||||||
RUN docker-php-ext-configure inotify\
|
&& docker-php-ext-install inotify \
|
||||||
&& docker-php-ext-install inotify
|
&& rm /tmp/inotify.tar.gz
|
||||||
# cleanup
|
|
||||||
RUN rm /tmp/inotify.tar.gz
|
|
||||||
|
|
||||||
# Install swoole and mssql driver
|
# Install openswoole, 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 && \
|
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
pecl install swoole-${SWOOLE_VERSION} pdo_sqlsrv && \
|
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
docker-php-ext-enable swoole pdo_sqlsrv && \
|
docker-php-ext-enable openswoole pdo_sqlsrv pcov && \
|
||||||
apk del .phpize-deps && \
|
apk del .phpize-deps && \
|
||||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
# Install composer
|
# Install composer
|
||||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
RUN chmod +x composer.phar
|
|
||||||
RUN mv composer.phar /usr/local/bin/composer
|
|
||||||
|
|
||||||
# Make home directory writable by anyone
|
# Make home directory writable by anyone
|
||||||
RUN chmod 777 /home
|
RUN chmod 777 /home
|
||||||
@@ -86,12 +71,12 @@ RUN chmod 777 /home
|
|||||||
VOLUME /home/shlink
|
VOLUME /home/shlink
|
||||||
WORKDIR /home/shlink
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
# Expose swoole port
|
# Expose openswoole port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD \
|
CMD \
|
||||||
# Install dependencies if the vendor dir does not exist
|
# Install dependencies if the vendor dir does not exist
|
||||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||||
# When restarting the container, swoole might think it is already in execution
|
# When restarting the container, openswoole might think it is already in execution
|
||||||
# This forces the app to be started every second until the exit code is 0
|
# This forces the app to be started every second until the exit code is 0
|
||||||
until php ./vendor/bin/mezzio-swoole start; do sleep 1 ; done
|
until php ./vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||||
|
|||||||
@@ -4,41 +4,46 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
use Doctrine\DBAL\DBALException;
|
use Doctrine\DBAL\Exception;
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
|
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
use function is_subclass_of;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
*/
|
*/
|
||||||
class Version20160819142757 extends AbstractMigration
|
class Version20160819142757 extends AbstractMigration
|
||||||
{
|
{
|
||||||
private const MYSQL = 'mysql';
|
|
||||||
private const SQLITE = 'sqlite';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws DBALException
|
* @throws Exception
|
||||||
* @throws SchemaException
|
* @throws SchemaException
|
||||||
*/
|
*/
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
$db = $this->connection->getDatabasePlatform()->getName();
|
$platformClass = $this->connection->getDatabasePlatform();
|
||||||
$table = $schema->getTable('short_urls');
|
$table = $schema->getTable('short_urls');
|
||||||
$column = $table->getColumn('short_code');
|
$column = $table->getColumn('short_code');
|
||||||
|
|
||||||
if ($db === self::MYSQL) {
|
match (true) {
|
||||||
$column->setPlatformOption('collation', 'utf8_bin');
|
is_subclass_of($platformClass, MySQLPlatform::class) => $column
|
||||||
} elseif ($db === self::SQLITE) {
|
->setPlatformOption('charset', 'utf8mb4')
|
||||||
$column->setPlatformOption('collate', 'BINARY');
|
->setPlatformOption('collation', 'utf8mb4_bin'),
|
||||||
}
|
is_subclass_of($platformClass, SqlitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'),
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws DBALException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
$db = $this->connection->getDatabasePlatform()->getName();
|
// Nothing to roll back
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
@@ -73,4 +74,9 @@ class Version20160820191203 extends AbstractMigration
|
|||||||
$schema->dropTable('short_urls_in_tags');
|
$schema->dropTable('short_urls_in_tags');
|
||||||
$schema->dropTable('tags');
|
$schema->dropTable('tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -24,10 +25,10 @@ class Version20171021093246 extends AbstractMigration
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$shortUrls->addColumn('valid_since', Types::DATETIME, [
|
$shortUrls->addColumn('valid_since', Types::DATETIME_MUTABLE, [
|
||||||
'notnull' => false,
|
'notnull' => false,
|
||||||
]);
|
]);
|
||||||
$shortUrls->addColumn('valid_until', Types::DATETIME, [
|
$shortUrls->addColumn('valid_until', Types::DATETIME_MUTABLE, [
|
||||||
'notnull' => false,
|
'notnull' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -45,4 +46,9 @@ class Version20171021093246 extends AbstractMigration
|
|||||||
$shortUrls->dropColumn('valid_since');
|
$shortUrls->dropColumn('valid_since');
|
||||||
$shortUrls->dropColumn('valid_until');
|
$shortUrls->dropColumn('valid_until');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -42,4 +43,9 @@ class Version20171022064541 extends AbstractMigration
|
|||||||
|
|
||||||
$shortUrls->dropColumn('max_visits');
|
$shortUrls->dropColumn('max_visits');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
@@ -39,4 +40,9 @@ final class Version20180801183328 extends AbstractMigration
|
|||||||
{
|
{
|
||||||
$schema->getTable('short_urls')->getColumn('short_code')->setLength($size);
|
$schema->getTable('short_urls')->getColumn('short_code')->setLength($size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
use Doctrine\DBAL\DBALException;
|
use Doctrine\DBAL\Exception;
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
use PDO;
|
use PDO;
|
||||||
@@ -16,15 +17,13 @@ use Shlinkio\Shlink\Common\Util\IpAddress;
|
|||||||
*/
|
*/
|
||||||
final class Version20180913205455 extends AbstractMigration
|
final class Version20180913205455 extends AbstractMigration
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// Nothing to create
|
// Nothing to create
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws DBALException
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function postUp(Schema $schema): void
|
public function postUp(Schema $schema): void
|
||||||
{
|
{
|
||||||
@@ -58,16 +57,19 @@ final class Version20180913205455 extends AbstractMigration
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return (string) IpAddress::fromString($addr)->getObfuscatedCopy();
|
return (string) IpAddress::fromString($addr)->getAnonymizedCopy();
|
||||||
} catch (InvalidArgumentException $e) {
|
} catch (InvalidArgumentException) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// Nothing to rollback
|
// Nothing to rollback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
@@ -47,4 +48,9 @@ final class Version20180915110857 extends AbstractMigration
|
|||||||
{
|
{
|
||||||
// Nothing to run
|
// Nothing to run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
use Doctrine\DBAL\DBALException;
|
use Doctrine\DBAL\Exception;
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\DBAL\Schema\Table;
|
use Doctrine\DBAL\Schema\Table;
|
||||||
@@ -42,7 +43,7 @@ final class Version20181020060559 extends AbstractMigration
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws SchemaException
|
* @throws SchemaException
|
||||||
* @throws DBALException
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function postUp(Schema $schema): void
|
public function postUp(Schema $schema): void
|
||||||
{
|
{
|
||||||
@@ -58,11 +59,16 @@ final class Version20181020060559 extends AbstractMigration
|
|||||||
foreach (self::COLUMNS as $camelCaseName => $snakeCaseName) {
|
foreach (self::COLUMNS as $camelCaseName => $snakeCaseName) {
|
||||||
$qb->set($snakeCaseName, $camelCaseName);
|
$qb->set($snakeCaseName, $camelCaseName);
|
||||||
}
|
}
|
||||||
$qb->execute();
|
$qb->executeStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// No down
|
// No down
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
@@ -38,4 +39,9 @@ final class Version20181020065148 extends AbstractMigration
|
|||||||
{
|
{
|
||||||
// No down
|
// No down
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Column;
|
use Doctrine\DBAL\Schema\Column;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
@@ -34,4 +35,9 @@ final class Version20181110175521 extends AbstractMigration
|
|||||||
{
|
{
|
||||||
return $schema->getTable('visits')->getColumn('user_agent');
|
return $schema->getTable('visits')->getColumn('user_agent');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Column;
|
use Doctrine\DBAL\Schema\Column;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
@@ -34,4 +35,9 @@ final class Version20190824075137 extends AbstractMigration
|
|||||||
{
|
{
|
||||||
return $schema->getTable('visits')->getColumn('referer');
|
return $schema->getTable('visits')->getColumn('referer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -52,4 +53,9 @@ final class Version20190930165521 extends AbstractMigration
|
|||||||
$schema->getTable('short_urls')->dropColumn('domain_id');
|
$schema->getTable('short_urls')->dropColumn('domain_id');
|
||||||
$schema->dropTable('domains');
|
$schema->dropTable('domains');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Index;
|
use Doctrine\DBAL\Schema\Index;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
@@ -46,4 +47,9 @@ final class Version20191001201532 extends AbstractMigration
|
|||||||
$shortUrls->dropIndex('unique_short_code_plus_domain');
|
$shortUrls->dropIndex('unique_short_code_plus_domain');
|
||||||
$shortUrls->addUniqueIndex(['short_code']);
|
$shortUrls->addUniqueIndex(['short_code']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkMigrations;
|
namespace ShlinkMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Column;
|
use Doctrine\DBAL\Schema\Column;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
use Doctrine\DBAL\Schema\SchemaException;
|
||||||
@@ -34,4 +35,9 @@ final class Version20191020074522 extends AbstractMigration
|
|||||||
{
|
{
|
||||||
return $schema->getTable('short_urls')->getColumn('original_url');
|
return $schema->getTable('short_urls')->getColumn('original_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user