mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
1224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02e48ae665 | ||
|
|
0d627ce808 | ||
|
|
99639b9844 | ||
|
|
0c3c7ff3b2 | ||
|
|
d7423585ff | ||
|
|
7de07a9cd4 | ||
|
|
2a734b5d89 | ||
|
|
4520afb271 | ||
|
|
e7a9ad1db0 | ||
|
|
84860539ce | ||
|
|
2901fe8b7b | ||
|
|
f9694333c5 | ||
|
|
fc1f35ad59 | ||
|
|
9a58748581 | ||
|
|
45e108d21e | ||
|
|
f4da9c1fcc | ||
|
|
a3ea8f56dd | ||
|
|
f3244b35e3 | ||
|
|
442eea0ea7 | ||
|
|
46601443f5 | ||
|
|
c0200317dd | ||
|
|
c8e5196aab | ||
|
|
b991b1699e | ||
|
|
582033ceb3 | ||
|
|
549a8d8837 | ||
|
|
5fb6c8708c | ||
|
|
7ee757243a | ||
|
|
044efe6ee4 | ||
|
|
9b16749737 | ||
|
|
6d51ff831f | ||
|
|
0635615149 | ||
|
|
51de4b17c0 | ||
|
|
615b443652 | ||
|
|
4b7b530f49 | ||
|
|
fa7969c746 | ||
|
|
aef04af4f0 | ||
|
|
f118ea252c | ||
|
|
d514f39a82 | ||
|
|
e17556a7ae | ||
|
|
d79f11eeb8 | ||
|
|
1ec950ee1e | ||
|
|
14ba9fd6a4 | ||
|
|
83e8801827 | ||
|
|
be822646e4 | ||
|
|
3a4a27a60c | ||
|
|
1773e6ecae | ||
|
|
a8e4b2fceb | ||
|
|
15b53ef43c | ||
|
|
11a4702b10 | ||
|
|
6b15cd6d51 | ||
|
|
00169a5729 | ||
|
|
94702791d9 | ||
|
|
447cccacdf | ||
|
|
0413399102 | ||
|
|
9afc7876c4 | ||
|
|
187c17319a | ||
|
|
7310ecd886 | ||
|
|
620cd92d11 | ||
|
|
f9658c8da1 | ||
|
|
613b1d3045 | ||
|
|
d39711ec51 | ||
|
|
69dcab96f8 | ||
|
|
d76c96ad41 | ||
|
|
133efff2cd | ||
|
|
c10f0db170 | ||
|
|
037cd8a389 | ||
|
|
1d24750f43 | ||
|
|
b52ceaff9a | ||
|
|
6b0b52853c | ||
|
|
64d7ac7093 | ||
|
|
b9ba1246d4 | ||
|
|
7f9dc10f6a | ||
|
|
a1afc90150 | ||
|
|
df94c68e2e | ||
|
|
65ea1e00a6 | ||
|
|
5bccdded8a | ||
|
|
8917ed5c2e | ||
|
|
fabc752398 | ||
|
|
38d8086516 | ||
|
|
ae0ff5f23c | ||
|
|
7c659699f3 | ||
|
|
9e6cdcb838 | ||
|
|
7e2f755dfd | ||
|
|
ce2ed237c7 | ||
|
|
626caa4afa | ||
|
|
f4a7712ded | ||
|
|
bab6a3951e | ||
|
|
f49d98f2ea | ||
|
|
1312ea61f4 | ||
|
|
8d90661d0a | ||
|
|
b6b2530cb6 | ||
|
|
e4f66b7ce6 | ||
|
|
4b52c92e97 | ||
|
|
76c42bc17c | ||
|
|
c4f8da5f02 | ||
|
|
80bdeb280a | ||
|
|
99010b6eae | ||
|
|
b2dabf06bf | ||
|
|
67ae05f473 | ||
|
|
fb4fecf411 | ||
|
|
c855f011d1 | ||
|
|
02717eb2fb | ||
|
|
de70ebe769 | ||
|
|
c6109fd396 | ||
|
|
83584a3175 | ||
|
|
f5dcc52b3b | ||
|
|
1901964de1 | ||
|
|
80e9c2452b | ||
|
|
5ad4b39160 | ||
|
|
89b73a9cfa | ||
|
|
e2d8334d69 | ||
|
|
9b16d7acc0 | ||
|
|
6836840746 | ||
|
|
4084d301ca | ||
|
|
added21b18 | ||
|
|
8cd77391cc | ||
|
|
05ebfccc63 | ||
|
|
cb3a690294 | ||
|
|
194a7b0e57 | ||
|
|
98e4d01feb | ||
|
|
c22e3895b5 | ||
|
|
9a76c19615 | ||
|
|
59fa088975 | ||
|
|
163244f40f | ||
|
|
a89b53af4f | ||
|
|
35508e253d | ||
|
|
e586fec338 | ||
|
|
93fa27bdba | ||
|
|
048856c333 | ||
|
|
986f1162dd | ||
|
|
dc8dfa9f0c | ||
|
|
82e7094f3a | ||
|
|
f0e62004d5 | ||
|
|
bbdbafd8db | ||
|
|
6121efec59 | ||
|
|
4fdbcc25a0 | ||
|
|
ca42425b33 | ||
|
|
ce0f61b66d | ||
|
|
13ee71f351 | ||
|
|
c57494d7cd | ||
|
|
d2e74ab330 | ||
|
|
850dde1a06 | ||
|
|
5e83f301ff | ||
|
|
5e74dd7a6d | ||
|
|
8a273e01e9 | ||
|
|
75f6f8dd18 | ||
|
|
e1cf0c4ea7 | ||
|
|
cc134abd12 | ||
|
|
b7db676cba | ||
|
|
3881996560 | ||
|
|
527d28ad81 | ||
|
|
f2371e8a80 | ||
|
|
fd882834d3 | ||
|
|
f92a720d63 | ||
|
|
d6f58698b7 | ||
|
|
d090260b17 | ||
|
|
cd43c1c01f | ||
|
|
284b28e8d9 | ||
|
|
b50547d868 | ||
|
|
401046fbe5 | ||
|
|
6e82509964 | ||
|
|
ab6fa490e5 | ||
|
|
55e2780f50 | ||
|
|
f4803c675c | ||
|
|
90514c603f | ||
|
|
7f4137e7cc | ||
|
|
071cb9af2b | ||
|
|
6ce1550457 | ||
|
|
8cb5d44dc9 | ||
|
|
1331b3f87c | ||
|
|
ab96297e58 | ||
|
|
c4fd3a74c5 | ||
|
|
da922fb2a7 | ||
|
|
4a05c4be40 | ||
|
|
cef30c8e2d | ||
|
|
8417498f08 | ||
|
|
10e941cea6 | ||
|
|
3d7b1ca799 | ||
|
|
b236354fc7 | ||
|
|
6fbb5a380d | ||
|
|
054eb42613 | ||
|
|
6074f4475d | ||
|
|
7afd3fd6a2 | ||
|
|
7d415e40b2 | ||
|
|
3c89d252d2 | ||
|
|
f678873e9f | ||
|
|
17d37a062a | ||
|
|
14702063f2 | ||
|
|
c599d8a0ed | ||
|
|
207d5adceb | ||
|
|
b4c46ce222 | ||
|
|
6fe269193a | ||
|
|
d948543d5c | ||
|
|
a327e6c0a7 | ||
|
|
fbd35b7974 | ||
|
|
b94a22e6a7 | ||
|
|
63ea9e4a21 | ||
|
|
e028d8ea31 | ||
|
|
457a7a14e5 | ||
|
|
cd387328be | ||
|
|
5524476787 | ||
|
|
78526fb405 | ||
|
|
b2dee43bb0 | ||
|
|
60e9443b12 | ||
|
|
ab8fa52ca4 | ||
|
|
16f64f6247 | ||
|
|
98992c656f | ||
|
|
053e026982 | ||
|
|
74180a4381 | ||
|
|
293725f933 | ||
|
|
c33f8d0ea2 | ||
|
|
0f2cd3cb7f | ||
|
|
2441ac5e77 | ||
|
|
f248001460 | ||
|
|
1fe2c93946 | ||
|
|
a3d50605c1 | ||
|
|
5427152f15 | ||
|
|
a4e9c2fdde | ||
|
|
e244b2dc51 | ||
|
|
31dea8fa99 | ||
|
|
be8cf56240 | ||
|
|
0bc7412430 | ||
|
|
6d56e92306 | ||
|
|
97c94f8fcc | ||
|
|
92b5a5296d | ||
|
|
febca6d441 | ||
|
|
bf29abc468 | ||
|
|
97cb30565c | ||
|
|
9809f050ef | ||
|
|
7ecfb24584 | ||
|
|
4aa65f750e | ||
|
|
63c533fa62 | ||
|
|
8751d6c315 | ||
|
|
eb40dc2d5d | ||
|
|
c9d1a955b9 | ||
|
|
c346fd0602 | ||
|
|
a45550b0c6 | ||
|
|
a843c59d77 | ||
|
|
3bfb29a51c | ||
|
|
d8ede3263f | ||
|
|
c36e43e249 | ||
|
|
52150b3067 | ||
|
|
e7796cc917 | ||
|
|
7f560e6a65 | ||
|
|
8f233221e5 | ||
|
|
f700abd65d | ||
|
|
f9e4d6d617 | ||
|
|
d9286765e1 | ||
|
|
a7cde9364a | ||
|
|
070d74830b | ||
|
|
23c07c4e82 | ||
|
|
ab7824aa85 | ||
|
|
67bafbe44e | ||
|
|
c4805b8152 | ||
|
|
33729289c7 | ||
|
|
721e3d9ef9 | ||
|
|
a72e22e046 | ||
|
|
36749658da | ||
|
|
4ad3dc0bc7 | ||
|
|
73864b923d | ||
|
|
71277e979a | ||
|
|
60fef3de74 | ||
|
|
0fe503fa0e | ||
|
|
db02d9f1ba | ||
|
|
89a987d03a | ||
|
|
3284cea6f2 | ||
|
|
df5ad554c1 | ||
|
|
07ae92943d | ||
|
|
175712d4a9 | ||
|
|
3f1b253c31 | ||
|
|
202d0b86b3 | ||
|
|
4e87affb0b | ||
|
|
7f83d37b3c | ||
|
|
09e81b00c5 | ||
|
|
68b77e22c5 | ||
|
|
c5ddd8302a | ||
|
|
1a0fe0429a | ||
|
|
6646232311 | ||
|
|
c1e88c3e83 | ||
|
|
c91a534d1a | ||
|
|
752100f1ce | ||
|
|
dae083c540 | ||
|
|
857c3a4f8d | ||
|
|
acc4c4756e | ||
|
|
0bacb215c5 | ||
|
|
d1a6e60b01 | ||
|
|
8f954151ca | ||
|
|
145d4eaaed | ||
|
|
7673232793 | ||
|
|
f08951a9b9 | ||
|
|
ff963a9df4 | ||
|
|
f30c74b987 | ||
|
|
467dbdd183 | ||
|
|
0e78deb8f2 | ||
|
|
50cc7ae632 | ||
|
|
512d765d60 | ||
|
|
7b9331bd14 | ||
|
|
4f5ce9fb43 | ||
|
|
83f73eb631 | ||
|
|
3f1b89d665 | ||
|
|
8f6fc97fc8 | ||
|
|
a463e6f9d7 | ||
|
|
2a0364ca8f | ||
|
|
23e9ed93bb | ||
|
|
689343d1c9 | ||
|
|
d01dc334d7 | ||
|
|
58a3791a5c | ||
|
|
1a133af141 | ||
|
|
938fb6509e | ||
|
|
d3bfd99210 | ||
|
|
3a1740fdca | ||
|
|
e3de403c6c | ||
|
|
5c1ab02753 | ||
|
|
e5713df008 | ||
|
|
95ea64980b | ||
|
|
c0a77b790d | ||
|
|
e073b4331a | ||
|
|
e919901487 | ||
|
|
13f9f106be | ||
|
|
e9c7053ef5 | ||
|
|
62051c8809 | ||
|
|
0a6a794e23 | ||
|
|
01846657d1 | ||
|
|
dd7545afdf | ||
|
|
9296013596 | ||
|
|
8015c6cc88 | ||
|
|
8c93444286 | ||
|
|
96ed7cae0d | ||
|
|
72c4628b79 | ||
|
|
1117631717 | ||
|
|
60176060cb | ||
|
|
d949b54ef4 | ||
|
|
720db64a03 | ||
|
|
37e0978bfc | ||
|
|
cf355b0b69 | ||
|
|
f2edb54b8b | ||
|
|
13ec27039d | ||
|
|
ad3805a560 | ||
|
|
cc4afa7b62 | ||
|
|
7a6bfed445 | ||
|
|
f2a7b687a9 | ||
|
|
522d021264 | ||
|
|
14a0db1f34 | ||
|
|
430883987a | ||
|
|
f17b641d46 | ||
|
|
48a8290e92 | ||
|
|
46acf4de1c | ||
|
|
17792a1603 | ||
|
|
a8611f5d80 | ||
|
|
deef938e97 | ||
|
|
e014cfa72a | ||
|
|
aa242eba25 | ||
|
|
0ac5569d60 | ||
|
|
7c3e3442c2 | ||
|
|
0f894dcdfe | ||
|
|
361d987f47 | ||
|
|
6017db260a | ||
|
|
f9c9b3d981 | ||
|
|
e7b876f4e6 | ||
|
|
554b948775 | ||
|
|
9bdbb59401 | ||
|
|
377861c5f1 | ||
|
|
26c2aaf567 | ||
|
|
62b54ceaaf | ||
|
|
625eba76c7 | ||
|
|
e12bda3f42 | ||
|
|
0f0301ae5c | ||
|
|
8d1776af98 | ||
|
|
c597738915 | ||
|
|
639329dbe4 | ||
|
|
92b0525b6e | ||
|
|
06306aabd5 | ||
|
|
225905fcdb | ||
|
|
8ca2b3c641 | ||
|
|
ac1737492b | ||
|
|
a63075eb4c | ||
|
|
97e9dfad67 | ||
|
|
17c4f13568 | ||
|
|
3b5243689b | ||
|
|
4d28adf4a7 | ||
|
|
1b14bb07b1 | ||
|
|
3a43aa4d41 | ||
|
|
2340b4f601 | ||
|
|
664886eddf | ||
|
|
d3570dac0b | ||
|
|
1854cc2f19 | ||
|
|
bff4bd12ae | ||
|
|
549c6605f0 | ||
|
|
f50263d2d9 | ||
|
|
c80ec54508 | ||
|
|
a91a560651 | ||
|
|
a931c60230 | ||
|
|
479a331008 | ||
|
|
5d99b1aef0 | ||
|
|
17e0c9176e | ||
|
|
48d7388bdc | ||
|
|
aa01c034db | ||
|
|
9035161b65 | ||
|
|
df57ca5edb | ||
|
|
0511c73cc8 | ||
|
|
a3554eaf74 | ||
|
|
cb0bac55d2 | ||
|
|
bd5d3f6897 | ||
|
|
5e6e386c5a | ||
|
|
e783bdc456 | ||
|
|
316b88cea6 | ||
|
|
c03eea789c | ||
|
|
bd5d3cb6fa | ||
|
|
e1f2dcc136 | ||
|
|
5e6ebfa5a9 | ||
|
|
a7ed14a1c9 | ||
|
|
f88d57b2b6 | ||
|
|
9dbd15bc0c | ||
|
|
0edb3e5c2c | ||
|
|
7501eca71e | ||
|
|
b145d106b0 | ||
|
|
b4386a3508 | ||
|
|
36e2a9387d | ||
|
|
14c68b4bbe | ||
|
|
d6fedaf926 | ||
|
|
8d35c1dde2 | ||
|
|
85b5f760e5 | ||
|
|
1a4a107952 | ||
|
|
e431395a12 | ||
|
|
cfc3d54122 | ||
|
|
d9d6d5bd9c | ||
|
|
32f465f7a6 | ||
|
|
4cddb573a0 | ||
|
|
2cb8486bb3 | ||
|
|
2a782ab60b | ||
|
|
5bde273d59 | ||
|
|
41e322fd47 | ||
|
|
55885b0f25 | ||
|
|
d419b9d62d | ||
|
|
3bdc05fbc4 | ||
|
|
57053d66a4 | ||
|
|
9d8ea0a4f6 | ||
|
|
46354baae9 | ||
|
|
27c48414da | ||
|
|
25b1138000 | ||
|
|
4cf3bc08f9 | ||
|
|
7e093a3fd8 | ||
|
|
abecf3be02 | ||
|
|
3d9b48c5fd | ||
|
|
ba4a66f772 | ||
|
|
ec839183e8 | ||
|
|
b0ec0601c1 | ||
|
|
637d8334f4 | ||
|
|
6db46b50e9 | ||
|
|
f6b1cc7556 | ||
|
|
65a0a90a51 | ||
|
|
38a7872fbf | ||
|
|
5839cc5926 | ||
|
|
49bd230474 | ||
|
|
074f2135f6 | ||
|
|
ef073d59ca | ||
|
|
a3b2f94339 | ||
|
|
b17c576a30 | ||
|
|
bc4156ca3c | ||
|
|
b747b8448e | ||
|
|
aa4b9fc27e | ||
|
|
3f3c2c3d1e | ||
|
|
4b49f8fb7f | ||
|
|
550f3b28ea | ||
|
|
6d4c232345 | ||
|
|
2d085ad6f4 | ||
|
|
3ea83f5cc3 | ||
|
|
b47bd0fc7a | ||
|
|
27e90c4c26 | ||
|
|
ad1a846d8e | ||
|
|
78f75a06df | ||
|
|
262d714751 | ||
|
|
f71c3bba5c | ||
|
|
8b495064b2 | ||
|
|
57a36204db | ||
|
|
7cc1722858 | ||
|
|
af50887361 | ||
|
|
99c8c6c8d4 | ||
|
|
1d7c9fd553 | ||
|
|
274c454fa4 | ||
|
|
453fcc4675 | ||
|
|
42427bfd74 | ||
|
|
33eedd2270 | ||
|
|
edaf9e34f4 | ||
|
|
965325aa7c | ||
|
|
bdf2bbd0f1 | ||
|
|
dc4aab2cab | ||
|
|
3b1f6c69de | ||
|
|
cdf5082cff | ||
|
|
61686ed6ea | ||
|
|
f63b96fd05 | ||
|
|
228bd83b75 | ||
|
|
a21dcb852a | ||
|
|
6558c37b9a | ||
|
|
e6720cce12 | ||
|
|
22d039c550 | ||
|
|
a21fcd72ce | ||
|
|
058391cf06 | ||
|
|
24e6acc6e8 | ||
|
|
8e3508f28d | ||
|
|
e72b424968 | ||
|
|
56d299a7dc | ||
|
|
575e6bf707 | ||
|
|
e50c21440f | ||
|
|
7cff11080d | ||
|
|
72381f9844 | ||
|
|
7c649e7497 | ||
|
|
eff308cd43 | ||
|
|
bd3745118e | ||
|
|
602ebef02a | ||
|
|
9040937376 | ||
|
|
a11be5b2ff | ||
|
|
6351d0b87d | ||
|
|
fae3434393 | ||
|
|
4013ae87dd | ||
|
|
cb4ba58b08 | ||
|
|
8c94452348 | ||
|
|
ea96a00b12 | ||
|
|
be26dd58c3 | ||
|
|
eaba5edf7f | ||
|
|
12da04ef37 | ||
|
|
8b03532ddb | ||
|
|
112b54ec7d | ||
|
|
ee6a8ede0a | ||
|
|
07ce5f05a2 | ||
|
|
7b04016ca2 | ||
|
|
b6792d3fb8 | ||
|
|
2f0d658432 | ||
|
|
8c1865c3ec | ||
|
|
096d2098d6 | ||
|
|
882d64ae11 | ||
|
|
3352bcd186 | ||
|
|
9743c1624d | ||
|
|
e85d59c5a4 | ||
|
|
ac0ff8fb94 | ||
|
|
90f93ee4ec | ||
|
|
794d926e3a | ||
|
|
bd41ebef9f | ||
|
|
725370704f | ||
|
|
f03b7689ce | ||
|
|
fb31e2a5e4 | ||
|
|
d688c6da7e | ||
|
|
618784dc3b | ||
|
|
9d64d4ed1d | ||
|
|
7f02243c6c | ||
|
|
3916c68126 | ||
|
|
a6f0c66331 | ||
|
|
bdfb220126 | ||
|
|
abcf2f86be | ||
|
|
a4d8ebdfc9 | ||
|
|
b51c149c30 | ||
|
|
39095a3098 | ||
|
|
765199727e | ||
|
|
c7043af853 | ||
|
|
02a8ef7dd9 | ||
|
|
6bb8c1b2f5 | ||
|
|
3cf253fd0f | ||
|
|
0365728337 | ||
|
|
b8143a5bb4 | ||
|
|
531a19dde9 | ||
|
|
69ff7de481 | ||
|
|
ffc0555c7c | ||
|
|
84a7981dfa | ||
|
|
2573c2bf98 | ||
|
|
3b4c1501f3 | ||
|
|
e836bedecc | ||
|
|
a797b74a70 | ||
|
|
ab497403ca | ||
|
|
d4dea9a1d2 | ||
|
|
28d93ea5e0 | ||
|
|
e6a31b16ed | ||
|
|
9553192281 | ||
|
|
74069f2d24 | ||
|
|
b4b00a57c1 | ||
|
|
a516ef691d | ||
|
|
e80b7448f5 | ||
|
|
f129544f83 | ||
|
|
9fa291a32f | ||
|
|
d06e92ffc2 | ||
|
|
1b83344995 | ||
|
|
cf49393ef2 | ||
|
|
f2ecbceae9 | ||
|
|
c582eba753 | ||
|
|
de86b62cdd | ||
|
|
73150471e9 | ||
|
|
ec751f4ac2 | ||
|
|
e652166289 | ||
|
|
a671d555cb | ||
|
|
6240554f4c | ||
|
|
4ee9c9bbe3 | ||
|
|
c830439085 | ||
|
|
f2196583c8 | ||
|
|
3dbca2115c | ||
|
|
b45d8de27d | ||
|
|
3ba46bbbfa | ||
|
|
06f3f0c86c | ||
|
|
06f07e3e40 | ||
|
|
740740b8c6 | ||
|
|
b6ed39b18b | ||
|
|
958c4704f8 | ||
|
|
ef075fb0ce | ||
|
|
556520583a | ||
|
|
399c56a097 | ||
|
|
f078d95588 | ||
|
|
33911afcd6 | ||
|
|
ae8d31e83f | ||
|
|
72c4052012 | ||
|
|
f713a1fa7e | ||
|
|
62488ac4e5 | ||
|
|
ab4c6e5fca | ||
|
|
26f4a969c9 | ||
|
|
703965915d | ||
|
|
24e38a3cf9 | ||
|
|
b12cfaedf3 | ||
|
|
71807e698c | ||
|
|
1d155298c1 | ||
|
|
4dfc5ae681 | ||
|
|
26f237069c | ||
|
|
b6e1c65c4c | ||
|
|
11f94b8306 | ||
|
|
01bcedef7a | ||
|
|
e51384fcc0 | ||
|
|
83c53c8b2e | ||
|
|
1afe08caed | ||
|
|
7289833928 | ||
|
|
f4d10df0f3 | ||
|
|
652b0df054 | ||
|
|
0e9ea5027c | ||
|
|
658303d375 | ||
|
|
ccc3a4b584 | ||
|
|
ef5ac86e0a | ||
|
|
91b90b276a | ||
|
|
85c32c3c9a | ||
|
|
40838255a7 | ||
|
|
a67ccb384f | ||
|
|
cb31e5a581 | ||
|
|
3c12a55872 | ||
|
|
6da8b11674 | ||
|
|
552489611f | ||
|
|
e48d0f4f0c | ||
|
|
49b6063501 | ||
|
|
dd049feb40 | ||
|
|
76a86c452e | ||
|
|
41aec15fab | ||
|
|
245cb0e35d | ||
|
|
7a0b1e8494 | ||
|
|
70c1c9f018 | ||
|
|
97e965157b | ||
|
|
04bbd471ff | ||
|
|
650a286982 | ||
|
|
ad44a8441a | ||
|
|
b339cf2429 | ||
|
|
9cd97c2f1e | ||
|
|
a7f6b60cba | ||
|
|
0d7dc50670 | ||
|
|
4bc5b9261f | ||
|
|
fb572d5abb | ||
|
|
8fa4219b30 | ||
|
|
a52d0cd419 | ||
|
|
0080ab5132 | ||
|
|
8afa582aa5 | ||
|
|
d847c7648e | ||
|
|
c140db16d1 | ||
|
|
adbf7c6f5e | ||
|
|
5cec697be3 | ||
|
|
587bbfdd73 | ||
|
|
b3a2ceedea | ||
|
|
621f18bf40 | ||
|
|
99c1a59dd4 | ||
|
|
3a149c9edc | ||
|
|
fdaf5fb2f3 | ||
|
|
2f83e90c8b | ||
|
|
05acd4ae88 | ||
|
|
87007677ed | ||
|
|
4ee0032c2a | ||
|
|
06583a0bc1 | ||
|
|
024c9c1a7a | ||
|
|
f3855dbc6f | ||
|
|
758dac47c3 | ||
|
|
81393a76b4 | ||
|
|
9949bb654d | ||
|
|
b0b9902f40 | ||
|
|
5aa8de11f4 | ||
|
|
b18c9e495f | ||
|
|
d3590234a3 | ||
|
|
39adef8ab8 | ||
|
|
13e443880a | ||
|
|
45961144b9 | ||
|
|
34129b8d24 | ||
|
|
48bd97fe41 | ||
|
|
b1b67c497e | ||
|
|
237fb95b4b | ||
|
|
c1b7c6ba6c | ||
|
|
d8add9291f | ||
|
|
a93edf158e | ||
|
|
fdadf3ba07 | ||
|
|
3e26f1113d | ||
|
|
822652cac3 | ||
|
|
1447687ebe | ||
|
|
12150f775d | ||
|
|
5f2f179581 | ||
|
|
407134bab1 | ||
|
|
de5b895fad | ||
|
|
80e3f01562 | ||
|
|
6904dcfed0 | ||
|
|
21863e8de6 | ||
|
|
d75be372cb | ||
|
|
edaf999bf5 | ||
|
|
3e98485c8b | ||
|
|
cc292886a6 | ||
|
|
0c1b36d0d4 | ||
|
|
a06957e9fa | ||
|
|
390bc59d99 | ||
|
|
85464f0fbb | ||
|
|
42f7a68ba5 | ||
|
|
e3397a7c90 | ||
|
|
46b4a21617 | ||
|
|
fc0aba6311 | ||
|
|
0b96a79c41 | ||
|
|
a5929ebb29 | ||
|
|
ce9ec0d738 | ||
|
|
961178fd82 | ||
|
|
49c73a9590 | ||
|
|
92c80e7833 | ||
|
|
6d5bce0078 | ||
|
|
112cbb9039 | ||
|
|
812c5f4993 | ||
|
|
921f303404 | ||
|
|
e0a9f8120c | ||
|
|
8ecc241a4b | ||
|
|
30e34151ed | ||
|
|
d734578f74 | ||
|
|
37c8328eed | ||
|
|
e71f6bb528 | ||
|
|
f7ae52f86e | ||
|
|
067d1cc41c | ||
|
|
b97af7efb9 | ||
|
|
fd0ecc05b2 | ||
|
|
5b934c3f9a | ||
|
|
c7a2f499e0 | ||
|
|
713f7e7bc9 | ||
|
|
09078e4c6a | ||
|
|
1f66ec2af5 | ||
|
|
936e5b3b86 | ||
|
|
99f28b569b | ||
|
|
0c83dea8b7 | ||
|
|
30edfdbdc5 | ||
|
|
60ef98b836 | ||
|
|
73c8b53882 | ||
|
|
425d8f0a3f | ||
|
|
92a83b82a0 | ||
|
|
d1ec15febf | ||
|
|
dd345c82ea | ||
|
|
2bf3e6a13b | ||
|
|
0b04476c99 | ||
|
|
229dc93132 | ||
|
|
0952c488be | ||
|
|
c4f28b3a32 | ||
|
|
201f25e0ad | ||
|
|
0c3523c34a | ||
|
|
0d7a0ee9ea | ||
|
|
931bdb0cd7 | ||
|
|
8807a78463 | ||
|
|
d832133410 | ||
|
|
cdde59b543 | ||
|
|
463dfe9729 | ||
|
|
805c8c87ba | ||
|
|
7ba2cfc010 | ||
|
|
40794c476f | ||
|
|
c3ab871366 | ||
|
|
42a5296f93 | ||
|
|
183db4ff80 | ||
|
|
0bc9bd9281 | ||
|
|
9bed7ef156 | ||
|
|
8f68e4b9f5 | ||
|
|
6589c8fce6 | ||
|
|
38b313a25d | ||
|
|
dab0ebeb99 | ||
|
|
27bf7220b9 | ||
|
|
e68ef87c66 | ||
|
|
29b747c192 | ||
|
|
2047d6b772 | ||
|
|
71e7938b7a | ||
|
|
6bce219eb3 | ||
|
|
dfcac525bc | ||
|
|
da307aee0a | ||
|
|
edf2b5b4c2 | ||
|
|
f41d947cf7 | ||
|
|
54bc169525 | ||
|
|
05d55c4000 | ||
|
|
739f5eb421 | ||
|
|
0aab1bdc4e | ||
|
|
47f99cf6cc | ||
|
|
55c9773a02 | ||
|
|
4b66aaba5c | ||
|
|
4223408090 | ||
|
|
58e6b0b683 | ||
|
|
891438c672 | ||
|
|
910864eaaf | ||
|
|
598c0757be | ||
|
|
01e0a95e14 | ||
|
|
f459a99e7e | ||
|
|
85e18a4754 | ||
|
|
1650499a38 | ||
|
|
51f243995a | ||
|
|
aeafb244d9 | ||
|
|
142417dda1 | ||
|
|
da658185c3 | ||
|
|
ef82158368 | ||
|
|
083ccd36b7 | ||
|
|
d61c79da84 | ||
|
|
8f76c3e202 | ||
|
|
23aa7a015c | ||
|
|
674a4416cf | ||
|
|
db85915c2f | ||
|
|
dfc8e8d74e | ||
|
|
b2b424a4ed | ||
|
|
3433899577 | ||
|
|
b1f814e118 | ||
|
|
7aa6afeb30 | ||
|
|
d414496a3c | ||
|
|
d4684fd01f | ||
|
|
bb444a02fe | ||
|
|
e980a8d121 | ||
|
|
f493baaf2b | ||
|
|
28f26920dd | ||
|
|
69e994c067 | ||
|
|
656083cb6f | ||
|
|
ab9ea887d2 | ||
|
|
9ac6a50e66 | ||
|
|
acc9cb94b5 | ||
|
|
01829c82ee | ||
|
|
9c02ea8799 | ||
|
|
d202538581 | ||
|
|
a84b642ba5 | ||
|
|
74176c298f | ||
|
|
91e21441f7 | ||
|
|
896b7f2d73 | ||
|
|
66ed152358 | ||
|
|
257134cd80 | ||
|
|
a4373aee91 | ||
|
|
7442905873 | ||
|
|
d3af51f684 | ||
|
|
04419a7242 | ||
|
|
a45d6e6b44 | ||
|
|
37b1306eb3 | ||
|
|
cff6573767 | ||
|
|
a2f34e02ad | ||
|
|
796543d194 | ||
|
|
3b25fb27fe | ||
|
|
3b20f955ff | ||
|
|
c81ae9c40d | ||
|
|
7ceae7af87 | ||
|
|
5e02cfe375 | ||
|
|
6e836b5fd9 | ||
|
|
8753e3a77f | ||
|
|
6a2227efc5 | ||
|
|
1fbcea7a06 | ||
|
|
168c839cf1 | ||
|
|
162e913cc4 | ||
|
|
5aaf50d68e | ||
|
|
d2f5be1d18 | ||
|
|
36ab455a49 | ||
|
|
ee8cab8455 | ||
|
|
bd884e85d4 | ||
|
|
5ceb6fb740 | ||
|
|
0d6155e8bc | ||
|
|
a78c59c11a | ||
|
|
173420c608 | ||
|
|
10b0ec301b | ||
|
|
1706a869d9 | ||
|
|
d0393799d2 | ||
|
|
739433ba8b | ||
|
|
a15e9c29c8 | ||
|
|
d58f89aa26 | ||
|
|
b7671f70da | ||
|
|
52366b9dd4 | ||
|
|
32417e40cb | ||
|
|
4cb44be9a0 | ||
|
|
a484455b0b | ||
|
|
4b3ed2b7ba | ||
|
|
e2986a7b4c | ||
|
|
82e04800aa | ||
|
|
5d367da626 | ||
|
|
59de5a5f55 | ||
|
|
0855104068 | ||
|
|
8c6f97c4e2 | ||
|
|
2d16856582 | ||
|
|
41e903cf26 | ||
|
|
4872bd3a92 | ||
|
|
8b675f55cc | ||
|
|
acda7f02c6 | ||
|
|
184ff90b9f | ||
|
|
d8be3c28cb | ||
|
|
3d358ab046 | ||
|
|
960bdfc232 | ||
|
|
101b4daff4 | ||
|
|
13431ff8cf | ||
|
|
4cdcad29df | ||
|
|
a4c34ff7be | ||
|
|
2b7b5e9a8f | ||
|
|
58db902084 | ||
|
|
983e3c9eaa | ||
|
|
dbe35cf567 | ||
|
|
8298f9d491 | ||
|
|
16a951b938 | ||
|
|
51fcbfb3c2 | ||
|
|
e01e370d16 | ||
|
|
736ac8ba90 | ||
|
|
d07104b8d9 | ||
|
|
cad53e397a | ||
|
|
3608a6d068 | ||
|
|
92ddd2eebe | ||
|
|
bf0b58b344 | ||
|
|
ff543b151c | ||
|
|
d842025835 | ||
|
|
230e56370a | ||
|
|
a8514a9ae4 | ||
|
|
148f7a9cfe | ||
|
|
29d50cabc2 | ||
|
|
a8f8297131 | ||
|
|
cd4b632d75 | ||
|
|
843754b7e7 | ||
|
|
847cc2bc50 | ||
|
|
751bd15785 | ||
|
|
c12db7567e | ||
|
|
e8069a10ba | ||
|
|
9742bf13e4 | ||
|
|
6441707c76 | ||
|
|
23bcba4fd9 | ||
|
|
9049a205b7 | ||
|
|
8cfa0b595c | ||
|
|
4b958e8b87 | ||
|
|
bcd5d2848d | ||
|
|
b59cbeceac | ||
|
|
46f948a584 | ||
|
|
14bf3a134b | ||
|
|
1557438fdf | ||
|
|
27b680e0cd | ||
|
|
14314ef939 | ||
|
|
bf5c168d7d | ||
|
|
1e0791416d | ||
|
|
ab8d42b609 | ||
|
|
96dbdbe7c9 | ||
|
|
6f135ad6ab | ||
|
|
5b9a1e1978 | ||
|
|
4ba3522e79 | ||
|
|
d3faa22b78 | ||
|
|
1daad334a5 | ||
|
|
3dda49dab4 | ||
|
|
c6c4e5580b | ||
|
|
3f808e3813 | ||
|
|
e5107c40f9 | ||
|
|
0871ca884e | ||
|
|
62ce9311bf | ||
|
|
70b15a7ab0 | ||
|
|
708bff20f0 | ||
|
|
369628ee95 | ||
|
|
0c6f8f1136 | ||
|
|
9f9d011d46 | ||
|
|
e28b73c130 | ||
|
|
56f953ab2f | ||
|
|
3ad8be175c | ||
|
|
f5f990511c | ||
|
|
1e3ccba503 | ||
|
|
a842b5b7cd | ||
|
|
909e42b0be | ||
|
|
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 | ||
|
|
4d082a87a1 | ||
|
|
1b6512fc8d | ||
|
|
9e32886f60 |
@@ -1,4 +1,6 @@
|
|||||||
|
bin/rr
|
||||||
config/autoload/*local*
|
config/autoload/*local*
|
||||||
|
config/params/shlink_dev_env.*
|
||||||
data/infra
|
data/infra
|
||||||
data/cache/*
|
data/cache/*
|
||||||
data/log/*
|
data/log/*
|
||||||
@@ -18,8 +20,7 @@ indocker
|
|||||||
docker-*
|
docker-*
|
||||||
phpstan.neon
|
phpstan.neon
|
||||||
php*xml*
|
php*xml*
|
||||||
infection*
|
|
||||||
**/test*
|
**/test*
|
||||||
build*
|
build*
|
||||||
**/.*
|
**/.*
|
||||||
bin/helper
|
!config/roadrunner/.rr.yml
|
||||||
|
|||||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -13,7 +13,6 @@
|
|||||||
.travis.yml export-ignore
|
.travis.yml export-ignore
|
||||||
build.sh export-ignore
|
build.sh export-ignore
|
||||||
CHANGELOG.md export-ignore
|
CHANGELOG.md export-ignore
|
||||||
docker-compose.override.yml.dist export-ignore
|
|
||||||
docker-compose.yml export-ignore
|
docker-compose.yml export-ignore
|
||||||
indocker export-ignore
|
indocker export-ignore
|
||||||
phpcs.xml export-ignore
|
phpcs.xml export-ignore
|
||||||
|
|||||||
49
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
49
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
title: 'Help wanted'
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Shlink version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: PHP version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: How do you serve Shlink
|
||||||
|
options:
|
||||||
|
- Self-hosted Apache
|
||||||
|
- Self-hosted nginx
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Docker image
|
||||||
|
- Other (explain in summary)
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database engine
|
||||||
|
options:
|
||||||
|
- MySQL
|
||||||
|
- MariaDB
|
||||||
|
- PostgreSQL
|
||||||
|
- MicrosoftSQL
|
||||||
|
- SQLite
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
value: '<!-- Describe your issue, question or request here. -->'
|
||||||
|
|
||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
github: ['acelaya']
|
github: ['acelaya']
|
||||||
custom: ['https://acel.me/donate']
|
custom: ['https://slnk.to/donate']
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and 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 expected.
|
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 personally if an issue gets eventually closed.
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
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.
|
||||||
-->
|
-->
|
||||||
|
|||||||
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Something on shlink is broken or not working as documented?
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### How Shlink is set-up
|
|
||||||
|
|
||||||
* Shlink Version: x.y.z
|
|
||||||
* PHP Version: x.y.z
|
|
||||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
|
|
||||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Provide a summary describing the problem you are experiencing. -->
|
|
||||||
|
|
||||||
#### Current behavior
|
|
||||||
|
|
||||||
<!-- How is it actually behaving (and it shouldn't)? -->
|
|
||||||
|
|
||||||
#### Expected behavior
|
|
||||||
|
|
||||||
<!-- How did you expected to behave? -->
|
|
||||||
|
|
||||||
#### How to reproduce
|
|
||||||
|
|
||||||
<!-- Provide steps to reproduce the bug. -->
|
|
||||||
71
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
71
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Something on Shlink is broken or not working as documented?
|
||||||
|
labels: ['bug']
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Shlink version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: PHP version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: How do you serve Shlink
|
||||||
|
options:
|
||||||
|
- Self-hosted Apache
|
||||||
|
- Self-hosted nginx
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Docker image
|
||||||
|
- Other (explain in summary)
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database engine
|
||||||
|
options:
|
||||||
|
- MySQL
|
||||||
|
- MariaDB
|
||||||
|
- PostgreSQL
|
||||||
|
- MicrosoftSQL
|
||||||
|
- SQLite
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Current behavior
|
||||||
|
value: '<!-- How is it actually behaving (and it should not)? -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
value: '<!-- How did you expect it to behave? -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Minimum steps to reproduce
|
||||||
|
value: |
|
||||||
|
<!--
|
||||||
|
Simple but detailed way to reproduce the bug:
|
||||||
|
|
||||||
|
* Avoid things like "create a kubernetes cluster", or anything related with cloud providers, as that is rarely the root cause.
|
||||||
|
* Avoid too vague steps or one-liners like "Update from v1 to v2".
|
||||||
|
* Providing the reproduction in the form of a self-contained docker-composer is desirable.
|
||||||
|
|
||||||
|
Failing in any of these will cause the issue to be closed as "not reproducible".
|
||||||
|
-->
|
||||||
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Do you find shlink is missing some important feature that would make it more useful?
|
|
||||||
labels: feature
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Describe the new feature you would like to request. -->
|
|
||||||
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Do you find Shlink is missing some important feature that would make it more useful?
|
||||||
|
labels: ['feature']
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
value: '<!-- Describe the new feature you would like to request. -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Use case
|
||||||
|
value: '<!-- Explain why do you think this feature would be useful, and what problems would it help to solve. -->'
|
||||||
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: Question - Support
|
|
||||||
about: Do you have a problem setting up or using shlink?
|
|
||||||
labels: question
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### How Shlink is set-up
|
|
||||||
|
|
||||||
* Shlink Version: x.y.z
|
|
||||||
* PHP Version: x.y.z
|
|
||||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
|
|
||||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Describe the issue you are facing here. -->
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Question - Support
|
||||||
|
about: Do you need help setting up or using Shlink?
|
||||||
|
url: https://github.com/shlinkio/shlink/discussions/new?category=help-wanted
|
||||||
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
|
||||||
|
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
|
||||||
|
if: ${{ inputs.php-extensions }}
|
||||||
|
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
|
||||||
|
if: ${{ inputs.php-extensions }}
|
||||||
|
uses: actions/cache@v4
|
||||||
|
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: xdebug
|
||||||
|
- name: Install dependencies
|
||||||
|
if: ${{ inputs.install-deps == 'yes' }}
|
||||||
|
run: composer install --no-interaction --prefer-dist ${{ inputs.php-version == '8.4' && '--ignore-platform-req=php' || '' }}
|
||||||
|
shell: bash
|
||||||
45
.github/workflows/ci-db-tests.yml
vendored
Normal file
45
.github/workflows/ci-db-tests.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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-24.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3', '8.4']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.4' }}
|
||||||
|
env:
|
||||||
|
LC_ALL: C
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- 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: pdo_sqlsrv-5.12.0
|
||||||
|
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-tools18/bin/sqlcmd -C -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@v4
|
||||||
|
if: ${{ matrix.php-version == '8.3' && inputs.platform == 'sqlite:ci' }}
|
||||||
|
with:
|
||||||
|
name: coverage-db
|
||||||
|
path: |
|
||||||
|
build/coverage-db
|
||||||
|
build/coverage-db.cov
|
||||||
10
.github/workflows/ci-docker-image-build.yml
vendored
Normal file
10
.github/workflows/ci-docker-image-build.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name: Test docker image build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'Dockerfile'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-docker-image:
|
||||||
|
uses: shlinkio/github-actions/.github/workflows/docker-image-build-ci.yml@main
|
||||||
42
.github/workflows/ci-tests.yml
vendored
Normal file
42
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3', '8.4']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.4' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # rr get-binary picks this env automatically
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- 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 }}
|
||||||
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- name: Download RoadRunner binary
|
||||||
|
if: ${{ inputs.test-group == 'api' }}
|
||||||
|
run: ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr
|
||||||
|
- run: composer test:${{ inputs.test-group }}:ci
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ matrix.php-version == '8.3' }}
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: |
|
||||||
|
build/coverage-${{ inputs.test-group }}
|
||||||
|
build/coverage-${{ inputs.test-group }}.cov
|
||||||
191
.github/workflows/ci.yml
vendored
191
.github/workflows/ci.yml
vendored
@@ -1,189 +1,102 @@
|
|||||||
name: Continuous integration
|
name: Continuous integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request: null
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.neon'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- develop
|
- develop
|
||||||
- 2.x
|
- 2.x
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.neon'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
static-analysis:
|
static-analysis:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.3']
|
||||||
command: ['cs', 'stan', 'swagger:validate']
|
command: ['cs', 'stan', 'swagger:validate']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: './.github/actions/ci-setup'
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
|
||||||
extensions: openswoole-4.11.0
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer ${{ matrix.command }}
|
- run: composer ${{ matrix.command }}
|
||||||
|
|
||||||
tests:
|
unit-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
test-group: unit
|
||||||
php-version: ['8.0', '8.1']
|
|
||||||
test-group: ['unit', 'api']
|
cli-tests:
|
||||||
steps:
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
- name: Checkout code
|
with:
|
||||||
uses: actions/checkout@v2
|
test-group: cli
|
||||||
- name: Start database server
|
|
||||||
if: ${{ matrix.test-group == 'api' }}
|
api-tests:
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
- name: Use PHP
|
with:
|
||||||
uses: shivammathur/setup-php@v2
|
test-group: api
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: openswoole-4.11.0
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:${{ matrix.test-group }}:ci
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
with:
|
|
||||||
name: coverage-${{ matrix.test-group }}
|
|
||||||
path: |
|
|
||||||
build/coverage-${{ matrix.test-group }}
|
|
||||||
build/coverage-${{ matrix.test-group }}.cov
|
|
||||||
|
|
||||||
db-tests:
|
db-tests:
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0', '8.1']
|
|
||||||
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
|
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
|
||||||
env:
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
LC_ALL: C
|
with:
|
||||||
steps:
|
platform: ${{ matrix.platform }}
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Install MSSQL ODBC
|
|
||||||
if: ${{ matrix.platform == 'ms' }}
|
|
||||||
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
|
||||||
- name: Start database server
|
|
||||||
if: ${{ matrix.platform != 'sqlite:ci' }}
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ matrix.platform }}
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: openswoole-4.11.0, pdo_sqlsrv-5.10.0
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- name: Create test database
|
|
||||||
if: ${{ matrix.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:${{ matrix.platform }}
|
|
||||||
- name: Upload code coverage
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '8.0' && matrix.platform == 'sqlite:ci' }}
|
|
||||||
with:
|
|
||||||
name: coverage-db
|
|
||||||
path: |
|
|
||||||
build/coverage-db
|
|
||||||
build/coverage-db.cov
|
|
||||||
|
|
||||||
mutation-tests:
|
|
||||||
needs:
|
|
||||||
- tests
|
|
||||||
- db-tests
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['8.0', '8.1']
|
|
||||||
test-group: ['unit', 'db', 'api']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: openswoole-4.11.0
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
path: build
|
|
||||||
- if: ${{ matrix.test-group == 'unit' }}
|
|
||||||
run: composer infect:ci:unit
|
|
||||||
env:
|
|
||||||
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
|
|
||||||
- if: ${{ matrix.test-group != 'unit' }}
|
|
||||||
run: composer infect:ci:${{ matrix.test-group }}
|
|
||||||
|
|
||||||
upload-coverage:
|
upload-coverage:
|
||||||
needs:
|
needs:
|
||||||
- tests
|
- unit-tests
|
||||||
|
- api-tests
|
||||||
|
- cli-tests
|
||||||
- db-tests
|
- db-tests
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.3']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Use PHP
|
- name: Use PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: './.github/actions/ci-setup'
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
coverage: pcov
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}
|
||||||
ini-values: pcov.directory=module
|
- uses: actions/download-artifact@v4
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
path: build
|
path: build
|
||||||
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
- 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-db/coverage-db.cov build/coverage-db.cov
|
||||||
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
||||||
- run: wget https://phar.phpunit.de/phpcov-8.2.0.phar
|
- run: mv build/coverage-cli/coverage-cli.cov build/coverage-cli.cov
|
||||||
- run: php phpcov-8.2.0.phar merge build --clover build/clover.xml
|
- run: vendor/bin/phpcov merge build --clover build/clover.xml
|
||||||
- name: Publish coverage
|
- name: Publish coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
file: ./build/clover.xml
|
file: ./build/clover.xml
|
||||||
|
|
||||||
delete-artifacts:
|
delete-artifacts:
|
||||||
needs:
|
needs:
|
||||||
- mutation-tests
|
|
||||||
- upload-coverage
|
- upload-coverage
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: geekyeggo/delete-artifact@v1
|
- uses: geekyeggo/delete-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: |
|
name: |
|
||||||
coverage-unit
|
coverage-*
|
||||||
coverage-db
|
|
||||||
coverage-api
|
|
||||||
|
|
||||||
build-docker-image:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
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"
|
|
||||||
|
|||||||
28
.github/workflows/docker-image-build.yml
vendored
28
.github/workflows/docker-image-build.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: Build docker image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
- name: Login to docker hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
- name: Build the image
|
|
||||||
run: bash ./docker/build
|
|
||||||
26
.github/workflows/publish-docker-image.yml
vendored
Normal file
26
.github/workflows/publish-docker-image.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Build and publish docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-image:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runtime: 'rr'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
- runtime: 'rr'
|
||||||
|
tag-suffix: 'roadrunner'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
uses: shlinkio/github-actions/.github/workflows/docker-publish-image.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
image-name: shlinkio/shlink
|
||||||
|
version-arg-name: SHLINK_VERSION
|
||||||
|
platforms: ${{ matrix.platforms }}
|
||||||
|
tags-suffix: ${{ matrix.tag-suffix }}
|
||||||
|
extra-build-args: |
|
||||||
|
SHLINK_RUNTIME=${{ matrix.runtime }}
|
||||||
41
.github/workflows/publish-release.yml
vendored
41
.github/workflows/publish-release.yml
vendored
@@ -7,36 +7,29 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0', '8.1']
|
php-version: ['8.2', '8.3'] # TODO 8.4
|
||||||
swoole: ['yes', 'no']
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: './.github/actions/ci-setup'
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
extensions: openswoole-4.11.0
|
install-deps: 'no'
|
||||||
- if: ${{ matrix.swoole == 'yes' }}
|
- run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||||
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
- uses: actions/upload-artifact@v4
|
||||||
- if: ${{ matrix.swoole == 'no' }}
|
|
||||||
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
name: dist-files-${{ matrix.php-version }}
|
||||||
path: build
|
path: build
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: ['build']
|
needs: ['build']
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: actions/download-artifact@v4
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
path: build
|
path: build
|
||||||
- name: Publish release with assets
|
- name: Publish release with assets
|
||||||
@@ -50,12 +43,8 @@ jobs:
|
|||||||
|
|
||||||
delete-artifacts:
|
delete-artifacts:
|
||||||
needs: ['publish']
|
needs: ['publish']
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: [ '8.0', '8.1' ]
|
|
||||||
swoole: [ 'yes', 'no' ]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: geekyeggo/delete-artifact@v1
|
- uses: geekyeggo/delete-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
name: dist-files-*
|
||||||
|
|||||||
19
.github/workflows/publish-swagger-spec.yml
vendored
19
.github/workflows/publish-swagger-spec.yml
vendored
@@ -7,30 +7,25 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.2']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Determine version
|
- name: Determine version
|
||||||
id: determine_version
|
id: determine_version
|
||||||
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
|
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Use PHP
|
- uses: './.github/actions/ci-setup'
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
extensions: openswoole-4.11.0
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer swagger:inline
|
- run: composer swagger:inline
|
||||||
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
||||||
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
|
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
|
||||||
- name: Publish spec
|
- name: Publish spec
|
||||||
uses: JamesIves/github-pages-deploy-action@4.1.7
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
||||||
repository-name: 'shlinkio/shlink-open-api-specs'
|
repository-name: 'shlinkio/shlink-open-api-specs'
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
.idea
|
.idea
|
||||||
|
bin/rr
|
||||||
|
.pid
|
||||||
build
|
build
|
||||||
!docker/build
|
!docker/build
|
||||||
composer.lock
|
composer.lock
|
||||||
@@ -7,8 +9,8 @@ vendor/
|
|||||||
data/database.sqlite
|
data/database.sqlite
|
||||||
data/shlink-tests.db
|
data/shlink-tests.db
|
||||||
data/GeoLite2-City.*
|
data/GeoLite2-City.*
|
||||||
|
data/infra/matomo
|
||||||
docs/swagger-ui*
|
docs/swagger-ui*
|
||||||
docs/mercure.html
|
docs/mercure.html
|
||||||
docker-compose.override.yml
|
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
docs/swagger/swagger-inlined.json
|
docs/swagger/swagger-inlined.json
|
||||||
|
|||||||
2878
CHANGELOG.md
2878
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@ You will also see how to ensure the code fulfills the expected code checks, and
|
|||||||
|
|
||||||
## System dependencies
|
## System dependencies
|
||||||
|
|
||||||
The project provides all its dependencies as docker containers through a docker-compose configuration.
|
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/).
|
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
|
## Setting up the project
|
||||||
|
|
||||||
@@ -16,12 +16,15 @@ The first thing you need to do is fork the repository, and clone it in your loca
|
|||||||
|
|
||||||
Then you will have to follow these steps:
|
Then you will have to follow these steps:
|
||||||
|
|
||||||
* Copy all files with `.local.php.dist` extension from `config/autoload` by removing the dist extension.
|
* Copy the `config/params/shlink_dev_env.php.dist` in the same directory, but removing the `.dist` extension:
|
||||||
|
|
||||||
For example the `common.local.php.dist` file should be copied as `common.local.php`.
|
```
|
||||||
|
cp config/params/shlink_dev_env.php.dist config/params/shlink_dev_env.php
|
||||||
|
```
|
||||||
|
|
||||||
* Copy the file `docker-compose.override.yml.dist` by also removing the `dist` extension.
|
The `shlink_dev_env.php` file is gitignored, so you can customize it as you want. For example, by adding your own GeoLite license key.
|
||||||
* Start-up the project by running `docker-compose up`.
|
|
||||||
|
* 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.
|
The first time this command is run, it will create several containers that are used during development, so it may take some time.
|
||||||
|
|
||||||
@@ -31,7 +34,7 @@ Then you will have to follow these steps:
|
|||||||
* Run `./indocker bin/cli db:migrate` to get database migrations up to date.
|
* 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.
|
* 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.
|
Once you finish this, you will have the project exposed in ports `8800` through RoadRunner and `8000` through nginx+php-fpm.
|
||||||
|
|
||||||
> Note: The `indocker` shell script is a helper tool used to run commands inside the main docker container.
|
> Note: The `indocker` shell script is a helper tool used to run commands inside the main docker container.
|
||||||
|
|
||||||
@@ -46,17 +49,18 @@ This is a simplified version of the project structure:
|
|||||||
```
|
```
|
||||||
shlink
|
shlink
|
||||||
├── bin
|
├── bin
|
||||||
│ └── cli
|
│ ├── cli
|
||||||
|
│ └── [...]
|
||||||
├── config
|
├── config
|
||||||
│ ├── autoload
|
│ ├── autoload
|
||||||
│ ├── params
|
│ ├── params
|
||||||
│ ├── config.php
|
│ ├── config.php
|
||||||
│ └── container.php
|
│ ├── container.php
|
||||||
|
│ └── [...]
|
||||||
├── data
|
├── data
|
||||||
│ ├── cache
|
│ ├── cache
|
||||||
│ ├── locks
|
│ ├── locks
|
||||||
│ ├── log
|
│ ├── log
|
||||||
│ ├── migrations
|
|
||||||
│ └── proxies
|
│ └── proxies
|
||||||
├── docs
|
├── docs
|
||||||
│ ├── adr
|
│ ├── adr
|
||||||
@@ -67,18 +71,19 @@ shlink
|
|||||||
│ ├── Core
|
│ ├── Core
|
||||||
│ └── Rest
|
│ └── Rest
|
||||||
├── public
|
├── public
|
||||||
|
│ └── [...]
|
||||||
├── composer.json
|
├── composer.json
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
The purposes of every folder are:
|
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.
|
* `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.
|
* `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.
|
* `data`: Common git-ignored assets, like logs, caches, lock files, GeoLite DB files, etc. It's the only location where Shlink may need to write at runtime.
|
||||||
* `docs`: Any project documentation is stored here, like API spec definitions or architectural decision records.
|
* `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.
|
* `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.
|
* `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 RoadRunner.
|
||||||
|
|
||||||
## Project tests
|
## Project tests
|
||||||
|
|
||||||
@@ -94,7 +99,7 @@ In order to ensure stability and no regressions are introduced while developing
|
|||||||
|
|
||||||
The project provides some tooling to run them against any of the supported database engines.
|
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.
|
* **API tests**: These are E2E tests that spin up an instance of the app with RoadRunner, 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.
|
These are the best tests to catch regressions, and to verify everything behaves as expected.
|
||||||
|
|
||||||
@@ -102,7 +107,9 @@ In order to ensure stability and no regressions are introduced while developing
|
|||||||
|
|
||||||
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.
|
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**: *TBD. Once included, its purpose will be the same as API tests, but running through the command line*
|
* **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.
|
Depending on the kind of contribution, maybe not all kinds of tests are needed, but the more you provide, the better.
|
||||||
|
|
||||||
@@ -119,9 +126,14 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
|
|||||||
For example, `test:db:postgres`.
|
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:api` to run API E2E tests. For these, the Postgres 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 test:cli` to run CLI E2E tests. For these, the Maria DB database engine is used.
|
||||||
* Run `./indocker composer ci` to run all previous commands together. This command is run during the project's continuous integration.
|
* Run `./indocker composer ci` to run all previous commands together, parallelizing non-conflicting tasks as much as possible.
|
||||||
* Run `./indocker composer ci:parallel` to do the same as in previous case, but parallelizing non-conflicting tasks as much as possible.
|
|
||||||
|
## Testing endpoints
|
||||||
|
|
||||||
|
The project provides a Swagger UI container for dev envs, which can be accessed in http://localhost:8005.
|
||||||
|
|
||||||
|
It will automatically load the contents of `docs/swagger`, so you can make any updates and they will get reflected.
|
||||||
|
|
||||||
## Pull request process
|
## Pull request process
|
||||||
|
|
||||||
@@ -133,7 +145,7 @@ Once everything is clear, to provide a pull request to this project, you should
|
|||||||
|
|
||||||
The base branch should always be `develop`, and the target branch for the pull request should also be `develop`.
|
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:parallel`, or wait for the build to be run automatically after the pull request is created.
|
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
|
## Architectural Decision Records
|
||||||
|
|
||||||
|
|||||||
50
Dockerfile
50
Dockerfile
@@ -1,18 +1,22 @@
|
|||||||
FROM php:8.1.5-alpine3.15 as base
|
FROM php:8.3-alpine3.19 as base
|
||||||
|
|
||||||
ARG SHLINK_VERSION=latest
|
ARG SHLINK_VERSION=latest
|
||||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||||
ENV OPENSWOOLE_VERSION 4.11.0
|
ARG SHLINK_RUNTIME=rr
|
||||||
ENV PDO_SQLSRV_VERSION 5.10.0
|
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
|
||||||
ENV LC_ALL "C"
|
ENV USER_ID '1001'
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.12.0
|
||||||
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
ENV LC_ALL 'C'
|
||||||
|
|
||||||
WORKDIR /etc/shlink
|
WORKDIR /etc/shlink
|
||||||
|
|
||||||
# Install required PHP extensions
|
# Install required PHP extensions
|
||||||
RUN \
|
RUN \
|
||||||
# Temp install dev dependencies needed to compile the extensions
|
# Temp install dev dependencies needed to compile the extensions
|
||||||
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev && \
|
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev linux-headers && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
||||||
apk add --no-cache sqlite-libs && \
|
apk add --no-cache sqlite-libs && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||||
@@ -20,16 +24,14 @@ RUN \
|
|||||||
apk del .dev-deps && \
|
apk del .dev-deps && \
|
||||||
apk add --no-cache postgresql icu libzip libpng
|
apk add --no-cache postgresql icu libzip libpng
|
||||||
|
|
||||||
# Install openswoole and sqlsrv driver for x86_64 builds
|
# Install sqlsrv driver for x86_64 builds
|
||||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
||||||
pecl install openswoole-${OPENSWOOLE_VERSION} && \
|
|
||||||
docker-php-ext-enable openswoole && \
|
|
||||||
if [ $(uname -m) == "x86_64" ]; then \
|
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 && \
|
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --no-cache --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \
|
||||||
docker-php-ext-enable pdo_sqlsrv && \
|
docker-php-ext-enable pdo_sqlsrv && \
|
||||||
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
|
||||||
fi; \
|
fi; \
|
||||||
apk del .phpize-deps
|
apk del .phpize-deps
|
||||||
|
|
||||||
@@ -38,35 +40,29 @@ FROM base as builder
|
|||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=composer:2 /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 && \
|
||||||
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" module/Core/src/Config/Options/AppOptions.php
|
||||||
|
|
||||||
|
|
||||||
# Prepare final image
|
# Prepare final image
|
||||||
FROM base
|
FROM base
|
||||||
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
||||||
|
|
||||||
COPY --from=builder /etc/shlink .
|
COPY --from=builder --chown=${USER_ID} /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 --no-config --location bin/ && chmod +x bin/rr ; \
|
||||||
|
fi;
|
||||||
|
|
||||||
# Expose default openswoole port
|
# Expose default port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# 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/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
|
USER ${USER_ID}
|
||||||
# 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-2021 Alejandro Celaya
|
Copyright (c) 2016-2024 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
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -1,12 +1,13 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Continuous+integration%22)
|
[](https://github.com/shlinkio/shlink/actions/workflows/ci.yml?query=workflow%3A%22Continuous+integration%22)
|
||||||
[](https://app.codecov.io/gh/shlinkio/shlink)
|
[](https://app.codecov.io/gh/shlinkio/shlink)
|
||||||
[](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/main/LICENSE)
|
[](https://github.com/shlinkio/shlink/blob/main/LICENSE)
|
||||||
[](https://twitter.com/shlinkio)
|
|
||||||
|
[](https://fosstodon.org/@shlinkio)
|
||||||
|
[](https://bsky.app/profile/shlinkio.bsky.social)
|
||||||
[](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 domain.
|
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
|
||||||
@@ -15,7 +16,7 @@ A PHP-based self-hosted URL shortener that can be used to serve shortened URLs u
|
|||||||
|
|
||||||
- [Full documentation](#full-documentation)
|
- [Full documentation](#full-documentation)
|
||||||
- [Docker image](#docker-image)
|
- [Docker image](#docker-image)
|
||||||
- [Self hosted](#self-hosted)
|
- [Self-hosted](#self-hosted)
|
||||||
- [Download](#download)
|
- [Download](#download)
|
||||||
- [Configure](#configure)
|
- [Configure](#configure)
|
||||||
- [Using shlink](#using-shlink)
|
- [Using shlink](#using-shlink)
|
||||||
@@ -31,18 +32,17 @@ You can learn how to use the official docker image by reading [the docs](https:/
|
|||||||
|
|
||||||
The idea is that you can just generate a container using the image and provide the custom config via env vars.
|
The idea is that you can just generate a container using the image and provide the custom config via env vars.
|
||||||
|
|
||||||
## Self hosted
|
## 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 8.0 or 8.1
|
* PHP 8.2 or 8.3
|
||||||
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
||||||
* apcu extension is recommended if you don't plan to use openswoole.
|
* apcu extension is recommended if you don't plan to use RoadRunner.
|
||||||
* xml extension is required if you want to generate QR codes in svg format.
|
* 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.
|
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||||
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
|
* 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`.
|
* 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
|
||||||
|
|
||||||
@@ -52,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*_dist.zip` file that suits your needs. You will find one for every supported PHP version and with/without openswoole integration.
|
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.
|
||||||
|
|
||||||
Finally, decompress the file in the location of your choice.
|
Finally, decompress the file in the location of your choice.
|
||||||
|
|
||||||
@@ -66,7 +66,9 @@ In order to run Shlink, you will need a built version of the project. There are
|
|||||||
|
|
||||||
After that, you will have a dist file inside the `build` directory, that you need to decompress in the location of 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 a [GitHub workflow](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Publish+release%22), 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
|
||||||
|
|
||||||
|
|||||||
52
UPGRADE.md
52
UPGRADE.md
@@ -1,5 +1,55 @@
|
|||||||
# Upgrading
|
# Upgrading
|
||||||
|
|
||||||
|
## From v3.x to v4.x
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
* Swoole and Openswoole are no longer officially supported runtimes. The recommended alternative is RoadRunner.
|
||||||
|
* Dist files for swoole/openswoole are no longer published.
|
||||||
|
* Webhooks are no longer supported. Migrate to one of the other [real-time updates](https://shlink.io/documentation/advanced/real-time-updates/) mechanisms.
|
||||||
|
* When using RoadRunner, the amount of web workers, task workers and the port number can no longer be provided via config options. Use `WEB_WORKER_NUM`, `TASK_WORKER_NUM` and `PORT` env vars instead.
|
||||||
|
|
||||||
|
### Changes in URL shortener
|
||||||
|
|
||||||
|
* The short URLs `loosely` mode is no longer supported, as it was a typo. Use `loose` mode instead.
|
||||||
|
* QR codes URLs now work by default, even for short URLs that cannot be visited due to max visits or date range limitations.
|
||||||
|
If you want to keep previous behavior, pass `QR_CODE_FOR_DISABLED_SHORT_URLS=false` or the equivalent configuration option.
|
||||||
|
* Long URL title resolution is now enabled by default. You can still disable it by passing `AUTO_RESOLVE_TITLES=false` or the equivalent configuration option.
|
||||||
|
* Shlink no longer allows to opt-in for long URL verification. Long URLs are unconditionally considered correct during short URL creation/edition.
|
||||||
|
* Device long URLs have been migrated to the new Dynamic rule-based redirects system and will continue to work as expected, but the API surface has changed.
|
||||||
|
If you use shlink-web-client and rely on this feature when creating/updating short URLs, **DO NOT UPDATE YET**. Support for dynamic rule-based redirects will be added to shlink-web-client soon, in v4.1.0
|
||||||
|
|
||||||
|
### Changes in REST API
|
||||||
|
|
||||||
|
* REST API v1/v2 now behave like v3. This only affects error codes, which are now proper URIs.
|
||||||
|
* `INVALID_ARGUMENT` -> `https://shlink.io/api/error/invalid-data`
|
||||||
|
* `INVALID_SHORT_URL_DELETION` -> `https://shlink.io/api/error/invalid-short-url-deletion`
|
||||||
|
* `DOMAIN_NOT_FOUND` -> `https://shlink.io/api/error/domain-not-found`
|
||||||
|
* `FORBIDDEN_OPERATION` -> `https://shlink.io/api/error/forbidden-tag-operation`
|
||||||
|
* `INVALID_SLUG` -> `https://shlink.io/api/error/non-unique-slug`
|
||||||
|
* `INVALID_SHORTCODE` -> `https://shlink.io/api/error/short-url-not-found`
|
||||||
|
* `TAG_CONFLICT` -> `https://shlink.io/api/error/tag-conflict`
|
||||||
|
* `TAG_NOT_FOUND` -> `https://shlink.io/api/error/tag-not-found`
|
||||||
|
* `MERCURE_NOT_CONFIGURED` -> `https://shlink.io/api/error/mercure-not-configured`
|
||||||
|
* `INVALID_AUTHORIZATION` -> `https://shlink.io/api/error/missing-authentication`
|
||||||
|
* `INVALID_API_KEY` -> `https://shlink.io/api/error/invalid-api-key`
|
||||||
|
* Endpoints previously returning props like `"visitsCount": {number}` no longer do it. There should be an alternative `"visitsSummary": {}` object with the amount nested on it.
|
||||||
|
* It is no longer possible to order the short URLs list with `orderBy=visitsCount-ASC`/`orderBy=visitsCount-DESC`. Use `orderBy=visits-ASC`/`orderBy=visits-DESC` instead.
|
||||||
|
* It is no longer possible to get tags with stats using `GET /tags?withStats=true`. Use `GET /tags/stats` endpoint instead.
|
||||||
|
* The `deviceLongUrls` are ignored when calling `POST /short-urls` or `PATCH /short-urls/{shortCode}`. These should now be configured as dynamic rule-based redirects via `POST /short-urls/{shortCode}/redirect-rules`.
|
||||||
|
|
||||||
|
### Changes in Docker image
|
||||||
|
|
||||||
|
* Since openswoole is no longer supported, there are no longer image tags suffixed with `openswoole`. You should migrate to the default or `roadrunner` ones.
|
||||||
|
* The `non-root` docker tag is no longer published, as all docker images are now running without super-user permissions.
|
||||||
|
* Due to previous point, it is no longer possible to pass `ENABLE_PERIODIC_VISIT_LOCATE=true` in order to configure a cron job that locates visits periodically.
|
||||||
|
This was not really needed in the docker image, as visits are located on the fly.
|
||||||
|
|
||||||
|
### Changes in integrations
|
||||||
|
|
||||||
|
* Credentials in redis URLs should now be URL-encoded, as they are unconditionally url-decoded before being used. Previously, it was possible to customize this behavior via `REDIS_DECODE_CREDENTIALS=true|false`.
|
||||||
|
* Providing redis URIs in the form of `tcp://password@6.6.6.6:6379` is no longer supported. If you want to provide password with no username, do `tcp://:password@6.6.6.6:6379` instead.
|
||||||
|
|
||||||
## From v2.x to v3.x
|
## From v2.x to v3.x
|
||||||
|
|
||||||
### Changes in REST API
|
### Changes in REST API
|
||||||
@@ -76,7 +126,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
|
||||||
|
|
||||||
|
|||||||
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));
|
||||||
36
bin/roadrunner-worker.php
Normal file
36
bin/roadrunner-worker.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Mezzio\Application;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||||
|
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 {
|
||||||
|
$requestIdMiddleware = $container->get(RequestIdMiddleware::class);
|
||||||
|
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks(
|
||||||
|
fn (string $requestId) => $requestIdMiddleware->setCurrentRequestId($requestId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,25 +1,33 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
export APP_ENV=test
|
export APP_ENV=test
|
||||||
export DB_DRIVER=postgres
|
|
||||||
export TEST_ENV=api
|
export TEST_ENV=api
|
||||||
export GENERATE_COVERAGE=${GENERATE_COVERAGE:-"no"}
|
export TEST_RUNTIME="${TEST_RUNTIME:-"rr"}" # rr is the only runtime currently supported
|
||||||
|
export DB_DRIVER="${DB_DRIVER:-"postgres"}"
|
||||||
|
export GENERATE_COVERAGE="${GENERATE_COVERAGE:-"no"}"
|
||||||
|
|
||||||
|
[ "$GENERATE_COVERAGE" != 'no' ] && export XDEBUG_MODE=coverage
|
||||||
|
|
||||||
# Reset logs
|
# Reset logs
|
||||||
|
OUTPUT_LOGS=data/log/api-tests/output.log
|
||||||
rm -rf data/log/api-tests
|
rm -rf data/log/api-tests
|
||||||
mkdir data/log/api-tests
|
mkdir data/log/api-tests
|
||||||
touch data/log/api-tests/output.log
|
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/laminas mezzio:swoole:stop
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f -w .
|
||||||
|
|
||||||
echo 'Starting server...'
|
echo 'Starting server...'
|
||||||
vendor/bin/laminas mezzio:swoole:start -d
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -w . -c=config/roadrunner/.rr.test.yml \
|
||||||
sleep 2
|
-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
|
||||||
|
|
||||||
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $*
|
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --testdox-summary $*
|
||||||
testsExitCode=$?
|
TESTS_EXIT_CODE=$?
|
||||||
|
|
||||||
vendor/bin/laminas mezzio:swoole:stop
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -w .
|
||||||
|
|
||||||
# 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 $TESTS_EXIT_CODE
|
||||||
|
|||||||
14
bin/test/run-cli-tests.sh
Executable file
14
bin/test/run-cli-tests.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
export APP_ENV=test
|
||||||
|
export TEST_ENV=cli
|
||||||
|
export DB_DRIVER="${DB_DRIVER:-"maria"}"
|
||||||
|
export GENERATE_COVERAGE="${GENERATE_COVERAGE:-"no"}"
|
||||||
|
|
||||||
|
[ "$GENERATE_COVERAGE" != 'no' ] && export XDEBUG_MODE=coverage
|
||||||
|
|
||||||
|
vendor/bin/phpunit --order-by=random --testdox --testdox-summary -c phpunit-cli.xml $*
|
||||||
|
TESTS_EXIT_CODE=$?
|
||||||
|
|
||||||
|
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
||||||
|
exit $TESTS_EXIT_CODE
|
||||||
21
build.sh
21
build.sh
@@ -1,17 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || ([ "$#" == 2 ] && [ "$2" != "--no-swoole" ]); then
|
if [ "$#" -lt 1 ]; then
|
||||||
echo "Usage:" >&2
|
echo "Usage:" >&2
|
||||||
echo " $0 {version} [--no-swoole]" >&2
|
echo " $0 {version}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
version=$1
|
version=$1
|
||||||
noSwoole=$2
|
|
||||||
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
||||||
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_openswoole"
|
distId="shlink${version}_php${phpVersion}_dist"
|
||||||
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
|
||||||
builtContent="./build/${distId}"
|
builtContent="./build/${distId}"
|
||||||
projectdir=$(pwd)
|
projectdir=$(pwd)
|
||||||
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
||||||
@@ -24,26 +22,21 @@ 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 --prefer-dist $composerFlags
|
${composerBin} install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction
|
||||||
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Delete development files
|
# Delete development files
|
||||||
echo 'Deleting dev files...'
|
echo 'Deleting dev files...'
|
||||||
rm composer.*
|
rm composer.*
|
||||||
|
|
||||||
# Update shlink version in config
|
# Update Shlink version
|
||||||
sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
sed -i "s/%SHLINK_VERSION%/${version}/g" module/Core/src/Config/Options/AppOptions.php
|
||||||
|
|
||||||
# Compressing file
|
# Compressing file
|
||||||
echo 'Compressing files...'
|
echo 'Compressing files...'
|
||||||
|
|||||||
223
composer.json
223
composer.json
@@ -12,71 +12,73 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0",
|
"php": "^8.2",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"akrabat/ip-address-middleware": "^2.1",
|
"akrabat/ip-address-middleware": "^2.3",
|
||||||
"cakephp/chronos": "^2.3",
|
"cakephp/chronos": "^3.1",
|
||||||
"doctrine/migrations": "^3.3",
|
"doctrine/dbal": "^4.2",
|
||||||
"doctrine/orm": "^2.11",
|
"doctrine/migrations": "^3.8",
|
||||||
"endroid/qr-code": "^4.4",
|
"doctrine/orm": "^3.3",
|
||||||
"geoip2/geoip2": "^2.12",
|
"endroid/qr-code": "^6.0",
|
||||||
"guzzlehttp/guzzle": "^7.4",
|
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||||
"happyr/doctrine-specification": "^2.0",
|
"geoip2/geoip2": "^3.0",
|
||||||
"jaybizzle/crawler-detect": "^1.2.110",
|
"guzzlehttp/guzzle": "^7.9",
|
||||||
"laminas/laminas-config": "^3.7",
|
"hidehalo/nanoid-php": "^1.1",
|
||||||
"laminas/laminas-config-aggregator": "^1.7",
|
"jaybizzle/crawler-detect": "^1.2.116",
|
||||||
"laminas/laminas-diactoros": "^2.8",
|
"laminas/laminas-config": "^3.9",
|
||||||
"laminas/laminas-inputfilter": "^2.13",
|
"laminas/laminas-config-aggregator": "^1.15",
|
||||||
"laminas/laminas-servicemanager": "^3.11.2",
|
"laminas/laminas-diactoros": "^3.5",
|
||||||
"laminas/laminas-stdlib": "^3.6",
|
"laminas/laminas-inputfilter": "^2.30",
|
||||||
"lcobucci/jwt": "^4.1",
|
"laminas/laminas-servicemanager": "^3.22",
|
||||||
"league/uri": "^6.4",
|
"laminas/laminas-stdlib": "^3.19",
|
||||||
"lstrojny/functional-php": "^1.17",
|
"matomo/matomo-php-tracker": "^3.3",
|
||||||
"mezzio/mezzio": "^3.7",
|
"mezzio/mezzio": "^3.20",
|
||||||
"mezzio/mezzio-fastroute": "^3.3",
|
"mezzio/mezzio-fastroute": "^3.12",
|
||||||
"mezzio/mezzio-problem-details": "^1.5",
|
"mezzio/mezzio-problem-details": "^1.15",
|
||||||
"mezzio/mezzio-swoole": "^4.0",
|
"mlocati/ip-lib": "^1.18",
|
||||||
"mlocati/ip-lib": "^1.17",
|
"mobiledetect/mobiledetectlib": "^4.8",
|
||||||
"monolog/monolog": "^2.3",
|
"pagerfanta/core": "^3.8",
|
||||||
"nikolaposa/monolog-factory": "^3.1",
|
"ramsey/uuid": "^4.7",
|
||||||
"ocramius/proxy-manager": "^2.11",
|
"shlinkio/doctrine-specification": "^2.1.1",
|
||||||
"pagerfanta/core": "^3.5",
|
"shlinkio/shlink-common": "^6.4",
|
||||||
"php-amqplib/php-amqplib": "^3.1",
|
"shlinkio/shlink-config": "^3.3",
|
||||||
"php-middleware/request-id": "^4.1",
|
"shlinkio/shlink-event-dispatcher": "^4.1",
|
||||||
"predis/predis": "^1.1",
|
"shlinkio/shlink-importer": "^5.3.2",
|
||||||
"pugx/shortid-php": "^1.0",
|
"shlinkio/shlink-installer": "^9.2",
|
||||||
"ramsey/uuid": "^4.2",
|
"shlinkio/shlink-ip-geolocation": "^4.1",
|
||||||
"shlinkio/shlink-common": "^4.4",
|
"shlinkio/shlink-json": "^1.1",
|
||||||
"shlinkio/shlink-config": "^1.6",
|
"spiral/roadrunner": "^2024.1",
|
||||||
"shlinkio/shlink-event-dispatcher": "^2.3",
|
"spiral/roadrunner-cli": "^2.6",
|
||||||
"shlinkio/shlink-importer": "dev-main#af0e05e as 3.0",
|
"spiral/roadrunner-http": "^3.5",
|
||||||
"shlinkio/shlink-installer": "dev-develop#fbbc8f5 as 7.1",
|
"spiral/roadrunner-jobs": "^4.5",
|
||||||
"shlinkio/shlink-ip-geolocation": "^2.2",
|
"symfony/console": "^7.1",
|
||||||
"symfony/console": "^6.0",
|
"symfony/filesystem": "^7.1",
|
||||||
"symfony/filesystem": "^6.0",
|
"symfony/lock": "^7.1",
|
||||||
"symfony/lock": "^6.0",
|
"symfony/process": "^7.1",
|
||||||
"symfony/mercure": "^0.6",
|
"symfony/string": "^7.1"
|
||||||
"symfony/process": "^6.0",
|
|
||||||
"symfony/string": "^6.0"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"cebe/php-openapi": "^1.7",
|
"devizzent/cebe-php-openapi": "^1.0.1",
|
||||||
"devster/ubench": "^2.1",
|
"devster/ubench": "^2.1",
|
||||||
"dms/phpunit-arraysubset-asserts": "^0.3.0",
|
"phpstan/phpstan": "^1.12",
|
||||||
"infection/infection": "^0.26.5",
|
"phpstan/phpstan-doctrine": "^1.5",
|
||||||
"openswoole/ide-helper": "~4.11.0",
|
"phpstan/phpstan-phpunit": "^1.4",
|
||||||
"phpspec/prophecy-phpunit": "^2.0",
|
"phpstan/phpstan-symfony": "^1.4",
|
||||||
"phpstan/phpstan": "^1.2",
|
"phpunit/php-code-coverage": "^11.0",
|
||||||
"phpstan/phpstan-doctrine": "^1.0",
|
"phpunit/phpcov": "^10.0",
|
||||||
"phpstan/phpstan-symfony": "^1.0",
|
"phpunit/phpunit": "^11.4",
|
||||||
"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.2.0",
|
"shlinkio/php-coding-standard": "~2.3.0",
|
||||||
"shlinkio/shlink-test-utils": "^3.0.1",
|
"shlinkio/shlink-test-utils": "^4.1.1",
|
||||||
"symfony/var-dumper": "^6.0",
|
"symfony/var-dumper": "^7.1",
|
||||||
"veewee/composer-run-parallel": "^1.1"
|
"veewee/composer-run-parallel": "^1.4"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/var-exporter": ">=6.3.9,<=6.4.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -86,16 +88,20 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/constants.php",
|
"config/constants.php",
|
||||||
|
"module/Core/functions/array-utils.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",
|
||||||
|
"ShlinkioDbTest\\Shlink\\Rest\\": "module/Rest/test-db",
|
||||||
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
||||||
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db"
|
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db",
|
||||||
|
"ShlinkioApiTest\\Shlink\\Core\\": "module/Core/test-api"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/test/constants.php"
|
"config/test/constants.php"
|
||||||
@@ -103,70 +109,62 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ci": [
|
"ci": [
|
||||||
"@cs",
|
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:postgres test:db:mysql test:db:maria test:db:ms",
|
||||||
"@stan",
|
"@parallel test:api:ci test:cli:ci"
|
||||||
"@swagger:validate",
|
|
||||||
"@test:ci",
|
|
||||||
"@infect:ci"
|
|
||||||
],
|
],
|
||||||
"ci:parallel": [
|
"cs": "phpcs -s",
|
||||||
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
|
||||||
"@parallel infect:test:api infect:ci:unit infect:ci:db"
|
|
||||||
],
|
|
||||||
"cs": "phpcs",
|
|
||||||
"cs:fix": "phpcbf",
|
"cs:fix": "phpcbf",
|
||||||
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/config config docker/config data/migrations --level=8",
|
"stan": ["@putenv APP_ENV=test", "phpstan analyse"],
|
||||||
"test": [
|
"test": [
|
||||||
"@test:unit",
|
"@parallel test:unit test:db",
|
||||||
"@test:db",
|
"@parallel test:api test:cli"
|
||||||
"@test:api"
|
|
||||||
],
|
],
|
||||||
"test:ci": [
|
"test:unit": ["@putenv COLUMNS=120", "phpunit --order-by=random --testdox --testdox-summary"],
|
||||||
"@test:unit:ci",
|
"test:unit:ci": ["@putenv XDEBUG_MODE=coverage", "@test:unit --coverage-php=build/coverage-unit.cov"],
|
||||||
"@test:db",
|
"test:unit:pretty": ["@putenv XDEBUG_MODE=coverage", "@test:unit --coverage-html build/coverage-unit/coverage-html"],
|
||||||
"@test:api:ci"
|
|
||||||
],
|
|
||||||
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
|
|
||||||
"test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
|
||||||
"test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
|
|
||||||
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
"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:db:sqlite": ["@putenv APP_ENV=test", "phpunit --order-by=random --testdox --testdox-summary -c phpunit-db.xml"],
|
||||||
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
"test:db:sqlite:ci": ["@putenv XDEBUG_MODE=coverage", "@test:db:sqlite --coverage-php build/coverage-db.cov"],
|
||||||
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
|
"test:db:mysql": ["@putenv DB_DRIVER=mysql", "@test:db:sqlite"],
|
||||||
"test:db:maria": "DB_DRIVER=maria composer test:db:sqlite",
|
"test:db:maria": ["@putenv DB_DRIVER=maria", "@test:db:sqlite"],
|
||||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
"test:db:postgres": ["@putenv DB_DRIVER=postgres", "@test:db:sqlite"],
|
||||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
"test:db:ms": ["@putenv DB_DRIVER=mssql", "@test:db:sqlite"],
|
||||||
"test:api": "bin/test/run-api-tests.sh",
|
"test:api": "bin/test/run-api-tests.sh",
|
||||||
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
|
"test:api:sqlite": ["@putenv DB_DRIVER=sqlite", "@test:api"],
|
||||||
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --only-covering-test-cases --skip-initial-tests",
|
"test:api:mysql": ["@putenv DB_DRIVER=mysql", "@test:api"],
|
||||||
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=85",
|
"test:api:maria": ["@putenv DB_DRIVER=maria", "@test:api"],
|
||||||
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
|
"test:api:mssql": ["@putenv DB_DRIVER=mssql", "@test:api"],
|
||||||
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json",
|
"test:api:ci": [
|
||||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api",
|
"@putenv GENERATE_COVERAGE=yes",
|
||||||
"infect:test": [
|
"@test:api",
|
||||||
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
"phpcov merge build/coverage-api --php build/coverage-api.cov && rm build/coverage-api/*.cov"
|
||||||
"@infect:ci"
|
|
||||||
],
|
],
|
||||||
"infect:test:unit": [
|
"test:api:pretty": [
|
||||||
"@test:unit:ci",
|
"@putenv GENERATE_COVERAGE=yes",
|
||||||
"@infect:ci:unit"
|
"@test:api",
|
||||||
|
"phpcov merge build/coverage-api --html build/coverage-api/coverage-html && rm build/coverage-api/*.cov"
|
||||||
],
|
],
|
||||||
"infect:test:api": [
|
"test:cli": "bin/test/run-cli-tests.sh",
|
||||||
"@test:api:ci",
|
"test:cli:ci": [
|
||||||
"@infect:ci:api"
|
"@putenv GENERATE_COVERAGE=yes",
|
||||||
|
"@test:cli",
|
||||||
|
"vendor/bin/phpcov merge build/coverage-cli --php build/coverage-cli.cov && rm build/coverage-cli/*.cov"
|
||||||
|
],
|
||||||
|
"test:cli:pretty": [
|
||||||
|
"@putenv GENERATE_COVERAGE=yes",
|
||||||
|
"@test:cli",
|
||||||
|
"phpcov merge build/coverage-cli --html build/coverage-cli/coverage-html && rm build/coverage-cli/*.cov"
|
||||||
],
|
],
|
||||||
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
||||||
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.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\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
|
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\" and \"test:ci\"</>",
|
||||||
"ci:parallel": "<fg=blue;options=bold>Same as \"ci\", but parallelizing tasks as much as possible</>",
|
|
||||||
"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:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||||
@@ -176,13 +174,13 @@
|
|||||||
"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 Miscrosoft SQL Server 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:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage reports</>",
|
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage for CI</>",
|
||||||
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
"test:api:pretty": "<fg=blue;options=bold>Runs API test suites, and generates code coverage in HTML format</>",
|
||||||
"infect:ci:unit": "<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:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
"test:cli:ci": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage for CI</>",
|
||||||
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
"test:cli:pretty": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage in HTML format</>",
|
||||||
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
"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</>",
|
"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</>"
|
||||||
@@ -193,7 +191,6 @@
|
|||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"composer/package-versions-deprecated": true,
|
"composer/package-versions-deprecated": true,
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
"infection/extension-installer": true,
|
|
||||||
"veewee/composer-run-parallel": true
|
"veewee/composer-run-parallel": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
config/autoload/.gitignore
vendored
2
config/autoload/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
local.php
|
|
||||||
*.local.php
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'app_options' => [
|
|
||||||
'name' => 'Shlink',
|
|
||||||
'version' => '%SHLINK_VERSION%',
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'app_options' => [
|
|
||||||
'version' => 'latest',
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
24
config/autoload/cache.global.php
Normal file
24
config/autoload/cache.global.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
|
||||||
|
$redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv()];
|
||||||
|
$cacheRedisBlock = $redisServers === null ? [] : [
|
||||||
|
'redis' => [
|
||||||
|
'servers' => $redisServers,
|
||||||
|
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'cache' => [
|
||||||
|
'namespace' => EnvVars::CACHE_NAMESPACE->loadFromEnv(),
|
||||||
|
...$cacheRedisBlock,
|
||||||
|
],
|
||||||
|
'redis' => $redis,
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -3,13 +3,18 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return (function () {
|
||||||
|
$isDev = EnvVars::isDevEnv();
|
||||||
|
|
||||||
'debug' => false,
|
return [
|
||||||
|
|
||||||
// Disabling config cache for cli, ensures it's never used for openswoole and also that console commands don't
|
'debug' => $isDev,
|
||||||
// generate a cache file that's then used by non-openswoole web executions
|
|
||||||
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
|
||||||
|
|
||||||
];
|
// Disabling config cache for cli, ensures it's never used for RoadRunner, and also that console
|
||||||
|
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||||
|
ConfigAggregator::ENABLE_CACHE => ! $isDev && PHP_SAPI !== 'cli',
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'debug' => true,
|
|
||||||
ConfigAggregator::ENABLE_CACHE => false,
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
|
||||||
|
|
||||||
return (static function (): array {
|
|
||||||
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD()->loadFromEnv();
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'delete_short_urls' => [
|
|
||||||
'check_visits_threshold' => $threshold !== null,
|
|
||||||
'visits_threshold' => (int) ($threshold ?? DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
})();
|
|
||||||
@@ -3,14 +3,29 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
|
use Mezzio\Application;
|
||||||
use Mezzio\Container;
|
use Mezzio\Container;
|
||||||
use Psr\Http\Client\ClientInterface;
|
use Psr\Http\Client\ClientInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestFactoryInterface;
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use Psr\Http\Message\UploadedFileFactoryInterface;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
use Spiral\RoadRunner\WorkerInterface;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
PSR7Worker::class => ConfigAbstractFactory::class,
|
||||||
|
Filesystem::class => InvokableFactory::class,
|
||||||
|
],
|
||||||
|
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Mezzio\Application::class => [
|
Application::class => [
|
||||||
Container\ApplicationConfigInjectionDelegator::class,
|
Container\ApplicationConfigInjectionDelegator::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -22,7 +37,16 @@ return [
|
|||||||
'lazy_services' => [
|
'lazy_services' => [
|
||||||
'proxies_target_dir' => 'data/proxies',
|
'proxies_target_dir' => 'data/proxies',
|
||||||
'proxies_namespace' => 'ShlinkProxy',
|
'proxies_namespace' => 'ShlinkProxy',
|
||||||
'write_proxy_files' => true,
|
'write_proxy_files' => EnvVars::isProdEnv(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
PSR7Worker::class => [
|
||||||
|
WorkerInterface::class,
|
||||||
|
ServerRequestFactoryInterface::class,
|
||||||
|
StreamFactoryInterface::class,
|
||||||
|
UploadedFileFactoryInterface::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
use Psr\Log;
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'lazy_services' => [
|
|
||||||
'write_proxy_files' => false,
|
|
||||||
],
|
|
||||||
|
|
||||||
'initializers' => [
|
|
||||||
function (ContainerInterface $container, $instance): void {
|
|
||||||
if ($instance instanceof Log\LoggerAwareInterface) {
|
|
||||||
$instance->setLogger($container->get(Log\LoggerInterface::class));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -2,24 +2,26 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Listener\OrphanVisitsCountTracker;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Listener\ShortUrlVisitsCountTracker;
|
||||||
|
|
||||||
use function Functional\contains;
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||||
|
|
||||||
return (static function (): array {
|
return (static function (): array {
|
||||||
$driver = EnvVars::DB_DRIVER()->loadFromEnv();
|
$driver = EnvVars::DB_DRIVER->loadFromEnv();
|
||||||
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
|
$isMysqlCompatible = contains($driver, ['maria', 'mysql']);
|
||||||
|
|
||||||
$resolveDriver = static fn () => match ($driver) {
|
$resolveDriver = static fn () => match ($driver) {
|
||||||
'postgres' => 'pdo_pgsql',
|
'postgres' => 'pdo_pgsql',
|
||||||
'mssql' => 'pdo_sqlsrv',
|
'mssql' => 'pdo_sqlsrv',
|
||||||
default => 'pdo_mysql',
|
default => 'pdo_mysql',
|
||||||
};
|
};
|
||||||
$resolveDefaultPort = static fn () => match ($driver) {
|
$readCredentialAsString = static function (EnvVars $envVar): string|null {
|
||||||
'postgres' => '5432',
|
$value = $envVar->loadFromEnv();
|
||||||
'mssql' => '1433',
|
return $value === null ? null : (string) $value;
|
||||||
default => '3306',
|
|
||||||
};
|
};
|
||||||
$resolveCharset = static fn () => match ($driver) {
|
$resolveCharset = static fn () => match ($driver) {
|
||||||
// This does not determine charsets or collations in tables or columns, but the charset used in the data
|
// This does not determine charsets or collations in tables or columns, but the charset used in the data
|
||||||
@@ -28,6 +30,7 @@ return (static function (): array {
|
|||||||
'postgres' => 'utf8',
|
'postgres' => 'utf8',
|
||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
$resolveConnection = static fn () => match ($driver) {
|
$resolveConnection = static fn () => match ($driver) {
|
||||||
null, 'sqlite' => [
|
null, 'sqlite' => [
|
||||||
'driver' => 'pdo_sqlite',
|
'driver' => 'pdo_sqlite',
|
||||||
@@ -35,13 +38,16 @@ return (static function (): array {
|
|||||||
],
|
],
|
||||||
default => [
|
default => [
|
||||||
'driver' => $resolveDriver(),
|
'driver' => $resolveDriver(),
|
||||||
'dbname' => EnvVars::DB_NAME()->loadFromEnv('shlink'),
|
'dbname' => EnvVars::DB_NAME->loadFromEnv(),
|
||||||
'user' => EnvVars::DB_USER()->loadFromEnv(),
|
'user' => $readCredentialAsString(EnvVars::DB_USER),
|
||||||
'password' => EnvVars::DB_PASSWORD()->loadFromEnv(),
|
'password' => $readCredentialAsString(EnvVars::DB_PASSWORD),
|
||||||
'host' => EnvVars::DB_HOST()->loadFromEnv(EnvVars::DB_UNIX_SOCKET()->loadFromEnv()),
|
'host' => EnvVars::DB_HOST->loadFromEnv(),
|
||||||
'port' => EnvVars::DB_PORT()->loadFromEnv($resolveDefaultPort()),
|
'port' => EnvVars::DB_PORT->loadFromEnv(),
|
||||||
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET()->loadFromEnv() : null,
|
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
|
||||||
'charset' => $resolveCharset(),
|
'charset' => $resolveCharset(),
|
||||||
|
'driverOptions' => $driver !== 'mssql' ? [] : [
|
||||||
|
'TrustServerCertificate' => 'true',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,6 +58,10 @@ return (static function (): array {
|
|||||||
'proxies_dir' => 'data/proxies',
|
'proxies_dir' => 'data/proxies',
|
||||||
'load_mappings_using_functional_style' => true,
|
'load_mappings_using_functional_style' => true,
|
||||||
'default_repository_classname' => EntitySpecificationRepository::class,
|
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||||
|
'listeners' => [
|
||||||
|
Events::onFlush => [ShortUrlVisitsCountTracker::class, OrphanVisitsCountTracker::class],
|
||||||
|
Events::postFlush => [ShortUrlVisitsCountTracker::class, OrphanVisitsCountTracker::class],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'connection' => $resolveConnection(),
|
'connection' => $resolveConnection(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'entity_manager' => [
|
|
||||||
'connection' => [
|
|
||||||
'user' => 'root',
|
|
||||||
'password' => 'root',
|
|
||||||
'driver' => 'pdo_mysql',
|
|
||||||
'host' => 'shlink_db_mysql',
|
|
||||||
'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'),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ return [
|
|||||||
'geolite2' => [
|
'geolite2' => [
|
||||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||||
'temp_dir' => __DIR__ . '/../../data',
|
'temp_dir' => __DIR__ . '/../../data',
|
||||||
'license_key' => EnvVars::GEOLITE_LICENSE_KEY()->loadFromEnv(),
|
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ return [
|
|||||||
|
|
||||||
'installer' => [
|
'installer' => [
|
||||||
'enabled_options' => [
|
'enabled_options' => [
|
||||||
|
Option\Server\RuntimeConfigOption::class,
|
||||||
|
Option\Server\MemoryLimitConfigOption::class,
|
||||||
Option\Database\DatabaseDriverConfigOption::class,
|
Option\Database\DatabaseDriverConfigOption::class,
|
||||||
Option\Database\DatabaseNameConfigOption::class,
|
Option\Database\DatabaseNameConfigOption::class,
|
||||||
Option\Database\DatabaseHostConfigOption::class,
|
Option\Database\DatabaseHostConfigOption::class,
|
||||||
@@ -20,18 +22,16 @@ return [
|
|||||||
Option\Database\DatabaseUnixSocketConfigOption::class,
|
Option\Database\DatabaseUnixSocketConfigOption::class,
|
||||||
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
||||||
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
Option\UrlShortener\ShortDomainSchemaConfigOption::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\Visit\VisitsThresholdConfigOption::class,
|
Option\Visit\VisitsThresholdConfigOption::class,
|
||||||
Option\BasePathConfigOption::class,
|
Option\BasePathConfigOption::class,
|
||||||
Option\TimezoneConfigOption::class,
|
Option\TimezoneConfigOption::class,
|
||||||
Option\Worker\TaskWorkerNumConfigOption::class,
|
Option\Cache\CacheNamespaceConfigOption::class,
|
||||||
Option\Worker\WebWorkerNumConfigOption::class,
|
|
||||||
Option\Redis\RedisServersConfigOption::class,
|
Option\Redis\RedisServersConfigOption::class,
|
||||||
Option\Redis\RedisSentinelServiceConfigOption::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,
|
||||||
@@ -42,6 +42,11 @@ return [
|
|||||||
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
||||||
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
|
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
|
||||||
Option\UrlShortener\AppendExtraPathConfigOption::class,
|
Option\UrlShortener\AppendExtraPathConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableTrailingSlashConfigOption::class,
|
||||||
|
Option\UrlShortener\ShortUrlModeConfigOption::class,
|
||||||
|
Option\Robots\RobotsAllowAllShortUrlsConfigOption::class,
|
||||||
|
Option\Robots\RobotsUserAgentsConfigOption::class,
|
||||||
Option\Tracking\IpAnonymizationConfigOption::class,
|
Option\Tracking\IpAnonymizationConfigOption::class,
|
||||||
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
||||||
Option\Tracking\DisableTrackParamConfigOption::class,
|
Option\Tracking\DisableTrackParamConfigOption::class,
|
||||||
@@ -55,24 +60,45 @@ return [
|
|||||||
Option\QrCode\DefaultFormatConfigOption::class,
|
Option\QrCode\DefaultFormatConfigOption::class,
|
||||||
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
||||||
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
||||||
|
Option\QrCode\DefaultColorConfigOption::class,
|
||||||
|
Option\QrCode\DefaultBgColorConfigOption::class,
|
||||||
|
Option\QrCode\DefaultLogoUrlConfigOption::class,
|
||||||
|
Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqUseSslConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
||||||
|
Option\Matomo\MatomoEnabledConfigOption::class,
|
||||||
|
Option\Matomo\MatomoBaseUrlConfigOption::class,
|
||||||
|
Option\Matomo\MatomoSiteIdConfigOption::class,
|
||||||
|
Option\Matomo\MatomoApiTokenConfigOption::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'installation_commands' => [
|
'installation_commands' => [
|
||||||
InstallationCommand::DB_CREATE_SCHEMA => [
|
InstallationCommand::DB_CREATE_SCHEMA->value => [
|
||||||
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
|
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
|
||||||
],
|
],
|
||||||
InstallationCommand::DB_MIGRATE => [
|
InstallationCommand::DB_MIGRATE->value => [
|
||||||
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
|
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
|
||||||
],
|
],
|
||||||
InstallationCommand::GEOLITE_DOWNLOAD_DB => [
|
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,
|
'command' => 'bin/cli ' . Command\Visit\DownloadGeoLiteDbCommand::NAME,
|
||||||
],
|
],
|
||||||
|
InstallationCommand::API_KEY_GENERATE->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Api\GenerateKeyCommand::NAME,
|
||||||
|
],
|
||||||
|
InstallationCommand::API_KEY_CREATE->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Api\InitialApiKeyCommand::NAME,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Predis\ClientInterface as PredisClient;
|
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Lock\NamespacedStore;
|
||||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
@@ -22,11 +23,12 @@ return [
|
|||||||
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
||||||
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
||||||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||||
|
NamespacedStore::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
'lock_store' => EnvVars::REDIS_SERVERS()->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
||||||
|
|
||||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
'redis_lock_store' => NamespacedStore::class,
|
||||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||||
],
|
],
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
@@ -38,7 +40,9 @@ return [
|
|||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
||||||
Lock\Store\RedisStore::class => [PredisClient::class],
|
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
||||||
|
NamespacedStore::class => [Lock\Store\RedisStore::class, 'config.cache.namespace'],
|
||||||
|
|
||||||
Lock\LockFactory::class => ['lock_store'],
|
Lock\LockFactory::class => ['lock_store'],
|
||||||
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,87 +4,73 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Formatter;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Monolog\Handler;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Monolog\Processor;
|
|
||||||
use MonologFactory\DiContainerLoggerFactory;
|
|
||||||
use PhpMiddleware\RequestId;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Shlinkio\Shlink\Core\EventDispatcher\Helper\RequestIdProvider;
|
||||||
|
use Shlinkio\Shlink\EventDispatcher\Util\RequestIdProviderInterface;
|
||||||
|
|
||||||
use const PHP_EOL;
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
|
||||||
$processors = [
|
return (static function (): array {
|
||||||
'exception_with_new_line' => [
|
$isDev = EnvVars::isDevEnv();
|
||||||
'name' => Common\Logger\Processor\ExceptionWithNewLineProcessor::class,
|
$common = [
|
||||||
],
|
'level' => $isDev ? Level::Debug->value : Level::Info->value,
|
||||||
'psr3' => [
|
'processors' => [RequestIdMiddleware::class],
|
||||||
'name' => Processor\PsrLogMessageProcessor::class,
|
'line_format' =>
|
||||||
],
|
'[%datetime%] [%extra.' . RequestIdMiddleware::ATTRIBUTE . '%] %channel%.%level_name% - %message%',
|
||||||
'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 [
|
// In dev env or the docker container, stream Shlink logs to stderr, otherwise send them to a file
|
||||||
|
$useStreamForShlinkLogger = $isDev || env('SHLINK_RUNTIME') !== null;
|
||||||
|
|
||||||
'logger' => [
|
return [
|
||||||
'Shlink' => [
|
|
||||||
'name' => 'Shlink',
|
'logger' => [
|
||||||
'handlers' => [
|
'Shlink' => $useStreamForShlinkLogger ? [
|
||||||
'shlink_handler' => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'name' => Handler\RotatingFileHandler::class,
|
'destination' => 'php://stderr',
|
||||||
'params' => [
|
...$common,
|
||||||
'level' => Logger::INFO,
|
] : [
|
||||||
'filename' => 'data/log/shlink_log.log',
|
'type' => LoggerType::FILE->value,
|
||||||
'max_files' => 30,
|
...$common,
|
||||||
'file_permission' => 0666,
|
|
||||||
],
|
|
||||||
'formatter' => $formatter,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
'processors' => $processors,
|
'Access' => [
|
||||||
],
|
'type' => LoggerType::STREAM->value,
|
||||||
'Access' => [
|
'destination' => 'php://stderr',
|
||||||
'name' => 'Access',
|
'add_new_line' => ! runningInRoadRunner(),
|
||||||
'handlers' => [
|
...$common,
|
||||||
'access_handler' => [
|
|
||||||
'name' => Handler\StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
'formatter' => $formatter,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'processors' => $processors,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'factories' => [
|
|
||||||
'Logger_Shlink' => [DiContainerLoggerFactory::class, 'Shlink'],
|
|
||||||
'Logger_Access' => [DiContainerLoggerFactory::class, 'Access'],
|
|
||||||
],
|
|
||||||
'aliases' => [
|
|
||||||
'logger' => 'Logger_Shlink',
|
|
||||||
Logger::class => 'Logger_Shlink',
|
|
||||||
LoggerInterface::class => 'Logger_Shlink',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'swoole-http-server' => [
|
|
||||||
'logger' => [
|
|
||||||
'logger-name' => 'Logger_Access',
|
|
||||||
'format' => '%u "%r" %>s %B',
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
|
||||||
|
|
||||||
];
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
'Logger_Shlink' => [LoggerFactory::class, 'Shlink'],
|
||||||
|
'Logger_Access' => [LoggerFactory::class, 'Access'],
|
||||||
|
NullLogger::class => InvokableFactory::class,
|
||||||
|
RequestIdProvider::class => ConfigAbstractFactory::class,
|
||||||
|
],
|
||||||
|
'aliases' => [
|
||||||
|
'logger' => 'Logger_Shlink',
|
||||||
|
Logger::class => 'Logger_Shlink',
|
||||||
|
LoggerInterface::class => 'Logger_Shlink',
|
||||||
|
AccessLogMiddleware::LOGGER_SERVICE_NAME => 'Logger_Access',
|
||||||
|
RequestIdProviderInterface::class => RequestIdProvider::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
RequestIdProvider::class => [RequestIdMiddleware::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Monolog\Handler\StreamHandler;
|
|
||||||
use Monolog\Logger;
|
|
||||||
|
|
||||||
$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 [
|
|
||||||
|
|
||||||
'logger' => [
|
|
||||||
'Shlink' => [
|
|
||||||
'handlers' => [
|
|
||||||
'shlink_handler' => $handler,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -8,34 +8,31 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
|||||||
use Symfony\Component\Mercure\Hub;
|
use Symfony\Component\Mercure\Hub;
|
||||||
use Symfony\Component\Mercure\HubInterface;
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
|
|
||||||
return (static function (): array {
|
return [
|
||||||
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL()->loadFromEnv();
|
|
||||||
|
|
||||||
return [
|
// This config is used by shlink-common. Do not delete
|
||||||
|
'mercure' => [
|
||||||
|
'public_hub_url' => EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv(),
|
||||||
|
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv(),
|
||||||
|
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
|
||||||
|
'jwt_issuer' => 'Shlink',
|
||||||
|
],
|
||||||
|
|
||||||
'mercure' => [
|
'dependencies' => [
|
||||||
'public_hub_url' => $publicUrl,
|
'delegators' => [
|
||||||
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL()->loadFromEnv($publicUrl),
|
LcobucciJwtProvider::class => [
|
||||||
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET()->loadFromEnv(),
|
LazyServiceFactory::class,
|
||||||
'jwt_issuer' => 'Shlink',
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'delegators' => [
|
|
||||||
LcobucciJwtProvider::class => [
|
|
||||||
LazyServiceFactory::class,
|
|
||||||
],
|
|
||||||
Hub::class => [
|
|
||||||
LazyServiceFactory::class,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
'lazy_services' => [
|
Hub::class => [
|
||||||
'class_map' => [
|
LazyServiceFactory::class,
|
||||||
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
|
||||||
Hub::class => HubInterface::class,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'lazy_services' => [
|
||||||
|
'class_map' => [
|
||||||
|
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
||||||
|
Hub::class => HubInterface::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
})();
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'mercure' => [
|
|
||||||
'public_hub_url' => 'http://localhost:8001',
|
|
||||||
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
|
||||||
'jwt_secret' => 'mercure_jwt_key',
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -7,15 +7,17 @@ namespace Shlinkio\Shlink;
|
|||||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
use Mezzio\Router;
|
use Mezzio\Router;
|
||||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
|
||||||
use RKA\Middleware\IpAddress;
|
use RKA\Middleware\IpAddress;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||||
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'middleware_pipeline' => [
|
'middleware_pipeline' => [
|
||||||
'error-handler' => [
|
'error-handler' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
AccessLogMiddleware::class,
|
||||||
ContentLengthMiddleware::class,
|
ContentLengthMiddleware::class,
|
||||||
RequestIdMiddleware::class,
|
RequestIdMiddleware::class,
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -2,46 +2,19 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
|
||||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
|
||||||
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
// This config is used by shlink-common. Do not delete
|
||||||
'rabbitmq' => [
|
'rabbitmq' => [
|
||||||
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED()->loadFromEnv(false),
|
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(),
|
||||||
'host' => EnvVars::RABBITMQ_HOST()->loadFromEnv(),
|
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
|
||||||
'port' => (int) EnvVars::RABBITMQ_PORT()->loadFromEnv('5672'),
|
'use_ssl' => (bool) EnvVars::RABBITMQ_USE_SSL->loadFromEnv(),
|
||||||
'user' => EnvVars::RABBITMQ_USER()->loadFromEnv(),
|
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv(),
|
||||||
'password' => EnvVars::RABBITMQ_PASSWORD()->loadFromEnv(),
|
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
|
||||||
'vhost' => EnvVars::RABBITMQ_VHOST()->loadFromEnv('/'),
|
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||||
],
|
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv(),
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'factories' => [
|
|
||||||
AMQPStreamConnection::class => ConfigAbstractFactory::class,
|
|
||||||
],
|
|
||||||
'delegators' => [
|
|
||||||
AMQPStreamConnection::class => [
|
|
||||||
LazyServiceFactory::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'lazy_services' => [
|
|
||||||
'class_map' => [
|
|
||||||
AMQPStreamConnection::class => AMQPStreamConnection::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
|
||||||
AMQPStreamConnection::class => [
|
|
||||||
'config.rabbitmq.host',
|
|
||||||
'config.rabbitmq.port',
|
|
||||||
'config.rabbitmq.user',
|
|
||||||
'config.rabbitmq.password',
|
|
||||||
'config.rabbitmq.vhost',
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'rabbitmq' => [
|
|
||||||
'enabled' => true,
|
|
||||||
'host' => 'shlink_rabbitmq',
|
|
||||||
'user' => 'rabbit',
|
|
||||||
'password' => 'rabbit',
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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 [
|
|
||||||
|
|
||||||
'not_found_redirects' => [
|
|
||||||
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT()->loadFromEnv(),
|
|
||||||
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT()->loadFromEnv(),
|
|
||||||
'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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
|
||||||
|
|
||||||
return (static function (): array {
|
|
||||||
$redisServers = EnvVars::REDIS_SERVERS()->loadFromEnv();
|
|
||||||
|
|
||||||
return match ($redisServers) {
|
|
||||||
null => [],
|
|
||||||
default => [
|
|
||||||
'cache' => [
|
|
||||||
'redis' => [
|
|
||||||
'servers' => $redisServers,
|
|
||||||
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE()->loadFromEnv(),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'cache' => [
|
|
||||||
'redis' => [
|
|
||||||
'servers' => 'tcp://shlink_redis:6379',
|
|
||||||
// 'servers' => [
|
|
||||||
// 'tcp://shlink_redis:6379',
|
|
||||||
// ],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'aliases' => [
|
|
||||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
|
||||||
// 'lock_store' => 'redis_lock_store',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
|
||||||
use PhpMiddleware\RequestId;
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'request_id' => [
|
|
||||||
'allow_override' => true,
|
|
||||||
'header_name' => 'X-Request-Id',
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'factories' => [
|
|
||||||
RequestId\Generator\RamseyUuid4StaticGenerator::class => InvokableFactory::class,
|
|
||||||
RequestId\RequestIdProviderFactory::class => ConfigAbstractFactory::class,
|
|
||||||
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
|
||||||
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
|
||||||
RequestId\RequestIdProviderFactory::class => [
|
|
||||||
RequestId\Generator\RamseyUuid4StaticGenerator::class,
|
|
||||||
'config.request_id.allow_override',
|
|
||||||
'config.request_id.header_name',
|
|
||||||
],
|
|
||||||
RequestId\RequestIdMiddleware::class => [
|
|
||||||
RequestId\RequestIdProviderFactory::class,
|
|
||||||
'config.request_id.header_name',
|
|
||||||
],
|
|
||||||
RequestId\MonologProcessor::class => [RequestId\RequestIdMiddleware::class],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -8,10 +8,12 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
'router' => [
|
'router' => [
|
||||||
'base_path' => EnvVars::BASE_PATH()->loadFromEnv(''),
|
'base_path' => EnvVars::BASE_PATH->loadFromEnv(),
|
||||||
|
|
||||||
'fastroute' => [
|
'fastroute' => [
|
||||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
// Disabling config cache for cli, ensures it's never used for RoadRunner, and also that console
|
||||||
|
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||||
|
FastRouteRouter::CONFIG_CACHE_ENABLED => EnvVars::isProdEnv() && PHP_SAPI !== 'cli',
|
||||||
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Mezzio\Router\FastRouteRouter;
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'router' => [
|
|
||||||
// 'base_path' => '',
|
|
||||||
'fastroute' => [
|
|
||||||
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
116
config/autoload/routes.config.php
Normal file
116
config/autoload/routes.config.php
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?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\Core\ShortUrl\Middleware\TrimTrailingSlashMiddleware;
|
||||||
|
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 {
|
||||||
|
$dropDomainMiddleware = Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class;
|
||||||
|
$overrideDomainMiddleware = Middleware\ShortUrl\OverrideDomainMiddleware::class;
|
||||||
|
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv() ? '[/]' : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// The order of the routes defined here matters. Changing it might cause path conflicts
|
||||||
|
'routes' => [
|
||||||
|
// Rest
|
||||||
|
...ConfigProvider::applyRoutesPrefix([
|
||||||
|
Action\HealthAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Visits and rules routes must go first, as they have a more specific path, otherwise, when
|
||||||
|
// multi-segment slugs are enabled, routes with a less-specific path might match first
|
||||||
|
|
||||||
|
// Visits.
|
||||||
|
Action\Visit\ShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\DeleteShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\Visit\TagVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\DomainVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\GlobalVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\OrphanVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\DeleteOrphanVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\NonOrphanVisitsAction::getRouteDef(),
|
||||||
|
|
||||||
|
//Redirect rules
|
||||||
|
Action\RedirectRule\ListRedirectRulesAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\RedirectRule\SetRedirectRulesAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
|
||||||
|
// Short URLs
|
||||||
|
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
||||||
|
$dropDomainMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\SingleStepCreateShortUrlAction::getRouteDef([
|
||||||
|
Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class,
|
||||||
|
$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,
|
||||||
|
TrimTrailingSlashMiddleware::class,
|
||||||
|
CoreAction\RedirectAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
|
||||||
|
|
||||||
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
|
||||||
|
|
||||||
return (static function (): array {
|
|
||||||
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM()->loadFromEnv(16);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'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),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
})();
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'hot-code-reload' => [
|
|
||||||
'enable' => true,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
|
||||||
|
|
||||||
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' => EnvVars::DISABLE_TRACKING_FROM()->loadFromEnv(),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
|
||||||
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
|
||||||
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
|
||||||
|
|
||||||
return (static function (): array {
|
|
||||||
$shortCodesLength = max(
|
|
||||||
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH()->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
|
|
||||||
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),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
})();
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
$isSwoole = extension_loaded('openswoole');
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'url_shortener' => [
|
|
||||||
'domain' => [
|
|
||||||
'schema' => 'http',
|
|
||||||
'hostname' => sprintf('localhost:%s', $isSwoole ? '8080' : '8000'),
|
|
||||||
],
|
|
||||||
'auto_resolve_titles' => true,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?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),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
})();
|
|
||||||
@@ -2,11 +2,26 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager;
|
||||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
use Doctrine\Migrations\Configuration\Migration\ConfigurationArray;
|
||||||
|
use Doctrine\Migrations\DependencyFactory;
|
||||||
|
|
||||||
|
// This file is currently used by doctrine migrations only
|
||||||
|
|
||||||
return (static function () {
|
return (static function () {
|
||||||
/** @var EntityManager $em */
|
$migrationsConfig = [
|
||||||
|
'migrations_paths' => [
|
||||||
|
'ShlinkMigrations' => 'module/Core/migrations',
|
||||||
|
],
|
||||||
|
'table_storage' => [
|
||||||
|
'table_name' => 'migrations',
|
||||||
|
],
|
||||||
|
'custom_template' => 'data/migrations_template.txt',
|
||||||
|
];
|
||||||
$em = include __DIR__ . '/entity-manager.php';
|
$em = include __DIR__ . '/entity-manager.php';
|
||||||
return ConsoleRunner::createHelperSet($em);
|
|
||||||
|
return DependencyFactory::fromEntityManager(
|
||||||
|
new ConfigurationArray($migrationsConfig),
|
||||||
|
new ExistingEntityManager($em),
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -8,41 +8,35 @@ use Laminas\ConfigAggregator;
|
|||||||
use Laminas\Diactoros;
|
use Laminas\Diactoros;
|
||||||
use Mezzio;
|
use Mezzio;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
use Mezzio\Swoole;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Shlinkio\Shlink\Config\ConfigAggregator\EnvVarLoaderProvider;
|
|
||||||
|
|
||||||
use function class_exists;
|
return (new ConfigAggregator\ConfigAggregator(
|
||||||
use function Shlinkio\Shlink\Config\env;
|
providers: [
|
||||||
|
Mezzio\ConfigProvider::class,
|
||||||
use const PHP_SAPI;
|
Mezzio\Router\ConfigProvider::class,
|
||||||
|
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||||
$isCli = PHP_SAPI === 'cli';
|
ProblemDetails\ConfigProvider::class,
|
||||||
$isTestEnv = env('APP_ENV') === 'test';
|
Diactoros\ConfigProvider::class,
|
||||||
|
Common\ConfigProvider::class,
|
||||||
return (new ConfigAggregator\ConfigAggregator([
|
Config\ConfigProvider::class,
|
||||||
! $isTestEnv
|
Importer\ConfigProvider::class,
|
||||||
? new EnvVarLoaderProvider('config/params/generated_config.php', Core\Config\EnvVars::cases())
|
IpGeolocation\ConfigProvider::class,
|
||||||
: new ConfigAggregator\ArrayProvider([]),
|
EventDispatcher\ConfigProvider::class,
|
||||||
Mezzio\ConfigProvider::class,
|
Core\ConfigProvider::class,
|
||||||
Mezzio\Router\ConfigProvider::class,
|
CLI\ConfigProvider::class,
|
||||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
Rest\ConfigProvider::class,
|
||||||
$isCli && class_exists(Swoole\ConfigProvider::class)
|
new ConfigAggregator\PhpFileProvider('config/autoload/{,*.}global.php'),
|
||||||
? Swoole\ConfigProvider::class
|
// Test config should be loaded ONLY during tests
|
||||||
: new ConfigAggregator\ArrayProvider([]),
|
EnvVars::isTestEnv()
|
||||||
ProblemDetails\ConfigProvider::class,
|
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
||||||
Diactoros\ConfigProvider::class,
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
Common\ConfigProvider::class,
|
// Routes have to be loaded last
|
||||||
Config\ConfigProvider::class,
|
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
|
||||||
Importer\ConfigProvider::class,
|
],
|
||||||
IpGeolocation\ConfigProvider::class,
|
cachedConfigFile: 'data/cache/app_config.php',
|
||||||
EventDispatcher\ConfigProvider::class,
|
postProcessors: [
|
||||||
Core\ConfigProvider::class,
|
Core\Config\PostProcessor\BasePathPrefixer::class,
|
||||||
CLI\ConfigProvider::class,
|
Core\Config\PostProcessor\MultiSegmentSlugProcessor::class,
|
||||||
Rest\ConfigProvider::class,
|
Core\Config\PostProcessor\ShortUrlMethodsProcessor::class,
|
||||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
],
|
||||||
$isTestEnv
|
))->getMergedConfig();
|
||||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
|
||||||
: new ConfigAggregator\ArrayProvider([]),
|
|
||||||
], 'data/cache/app_config.php', [
|
|
||||||
Core\Config\BasePathPrefixer::class,
|
|
||||||
]))->getMergedConfig();
|
|
||||||
|
|||||||
@@ -4,19 +4,20 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Fig\Http\Message\StatusCodeInterface;
|
use Shlinkio\Shlink\Core\Util\RedirectStatus;
|
||||||
|
|
||||||
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||||
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||||
const MIN_SHORT_CODES_LENGTH = 4;
|
const MIN_SHORT_CODES_LENGTH = 4;
|
||||||
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
|
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302;
|
||||||
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||||
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||||
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
|
const LOOSE_URI_MATCHER = '/(.+)\:(.+)/i'; // Matches anything starting with a schema.
|
||||||
const DEFAULT_QR_CODE_SIZE = 300;
|
const DEFAULT_QR_CODE_SIZE = 300;
|
||||||
const DEFAULT_QR_CODE_MARGIN = 0;
|
const DEFAULT_QR_CODE_MARGIN = 0;
|
||||||
const DEFAULT_QR_CODE_FORMAT = 'png';
|
const DEFAULT_QR_CODE_FORMAT = 'png';
|
||||||
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
||||||
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
||||||
const MIN_TASK_WORKERS = 4;
|
const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = true;
|
||||||
const MIGRATIONS_TABLE = 'migrations';
|
const DEFAULT_QR_CODE_COLOR = '#000000'; // Black
|
||||||
|
const DEFAULT_QR_CODE_BG_COLOR = '#ffffff'; // White
|
||||||
|
|||||||
@@ -6,14 +6,24 @@ use Laminas\ServiceManager\ServiceManager;
|
|||||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Config\loadEnvVarsFromConfig;
|
||||||
|
use function Shlinkio\Shlink\Core\enumValues;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\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
|
// Promote env vars from installer, dev config or test config
|
||||||
date_default_timezone_set(EnvVars::TIMEZONE()->loadFromEnv(date_default_timezone_get()));
|
loadEnvVarsFromConfig(
|
||||||
|
EnvVars::isTestEnv() ? 'config/test/shlink_test_env.php' : 'config/params/*.php',
|
||||||
|
enumValues(EnvVars::class),
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is one of the first files loaded. Configure the timezone and memory limit here
|
||||||
|
ini_set('memory_limit', EnvVars::MEMORY_LIMIT->loadFromEnv());
|
||||||
|
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv());
|
||||||
|
|
||||||
// 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
|
||||||
@@ -21,7 +31,6 @@ if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
|||||||
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build container
|
|
||||||
return (static function (): ServiceManager {
|
return (static function (): ServiceManager {
|
||||||
$config = require __DIR__ . '/config.php';
|
$config = require __DIR__ . '/config.php';
|
||||||
$container = new ServiceManager($config['dependencies']);
|
$container = new ServiceManager($config['dependencies']);
|
||||||
|
|||||||
1
config/params/.gitignore
vendored
1
config/params/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
!*.dist
|
||||||
|
|||||||
76
config/params/shlink_dev_env.php.dist
Normal file
76
config/params/shlink_dev_env.php.dist
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
EnvVars::APP_ENV->value => 'dev',
|
||||||
|
// EnvVars::GEOLITE_LICENSE_KEY->value => '',
|
||||||
|
|
||||||
|
// URL shortener
|
||||||
|
EnvVars::DEFAULT_DOMAIN->value => 'localhost:8800',
|
||||||
|
EnvVars::IS_HTTPS_ENABLED->value => false,
|
||||||
|
|
||||||
|
// Database - MySQL
|
||||||
|
EnvVars::DB_DRIVER->value => 'mysql',
|
||||||
|
EnvVars::DB_USER->value => 'root',
|
||||||
|
EnvVars::DB_PASSWORD->value => 'root',
|
||||||
|
EnvVars::DB_NAME->value => 'shlink',
|
||||||
|
// EnvVars::DB_NAME->value => 'shlink_foo',
|
||||||
|
EnvVars::DB_HOST->value => 'shlink_db_mysql',
|
||||||
|
|
||||||
|
// Database - Maria
|
||||||
|
// EnvVars::DB_DRIVER->value => 'maria',
|
||||||
|
// EnvVars::DB_USER->value => 'root',
|
||||||
|
// EnvVars::DB_PASSWORD->value => 'root',
|
||||||
|
// EnvVars::DB_NAME->value => 'shlink_foo',
|
||||||
|
// EnvVars::DB_HOST->value => 'shlink_db_maria',
|
||||||
|
|
||||||
|
// Database - Postgres
|
||||||
|
// EnvVars::DB_DRIVER->value => 'postgres',
|
||||||
|
// EnvVars::DB_USER->value => 'postgres',
|
||||||
|
// EnvVars::DB_PASSWORD->value => 'root',
|
||||||
|
// EnvVars::DB_NAME->value => 'shlink_foo',
|
||||||
|
// EnvVars::DB_HOST->value => 'shlink_db_postgres',
|
||||||
|
|
||||||
|
// Database - MSSQL
|
||||||
|
// EnvVars::DB_DRIVER->value => 'mssql',
|
||||||
|
// EnvVars::DB_USER->value => 'sa',
|
||||||
|
// EnvVars::DB_PASSWORD->value => 'Passw0rd!',
|
||||||
|
// EnvVars::DB_NAME->value => 'shlink_foo',
|
||||||
|
// EnvVars::DB_HOST->value => 'shlink_db_ms',
|
||||||
|
|
||||||
|
// Matomo
|
||||||
|
// Dev matomo instance needs to be manually configured once before enabling the configuration below:
|
||||||
|
// 1. Go to http://localhost:8003 and follow the installation instructions.
|
||||||
|
// 2. Open data/infra/matomo/config/config.ini.php and replace `trusted_hosts[] = "localhost"` with
|
||||||
|
// `trusted_hosts[] = "localhost:8003"` (see https://github.com/matomo-org/matomo/issues/9549)
|
||||||
|
// 3. Go to http://localhost:8003/index.php?module=SitesManager&action=index and paste the ID for the site you just
|
||||||
|
// created into the `MATOMO_SITE_ID` var below.
|
||||||
|
// 4. Go to http://localhost:8003/index.php?module=UsersManager&action=userSecurity, scroll down, click
|
||||||
|
// "Create new token" and once generated, paste the token into the `MATOMO_API_TOKEN` var below.
|
||||||
|
// 5. Copy the config below and paste it in a new shlink-dev.local.env file.
|
||||||
|
EnvVars::MATOMO_ENABLED->value => false,
|
||||||
|
EnvVars::MATOMO_BASE_URL->value => 'http://shlink_matomo',
|
||||||
|
// EnvVars::MATOMO_SITE_ID->value => ,
|
||||||
|
// EnvVars::MATOMO_API_TOKEN->value => ,
|
||||||
|
|
||||||
|
// Mercure
|
||||||
|
EnvVars::MERCURE_PUBLIC_HUB_URL->value => 'http://localhost:8002',
|
||||||
|
EnvVars::MERCURE_INTERNAL_HUB_URL->value => 'http://shlink_mercure_proxy',
|
||||||
|
EnvVars::MERCURE_JWT_SECRET->value => 'mercure_jwt_key_long_enough_to_avoid_error',
|
||||||
|
|
||||||
|
// RabbitMQ
|
||||||
|
EnvVars::RABBITMQ_ENABLED->value => true,
|
||||||
|
EnvVars::RABBITMQ_HOST->value => 'shlink_rabbitmq',
|
||||||
|
EnvVars::RABBITMQ_PORT->value => 5672,
|
||||||
|
EnvVars::RABBITMQ_USER->value => 'rabbit',
|
||||||
|
EnvVars::RABBITMQ_PASSWORD->value => 'rabbit',
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
EnvVars::REDIS_PUB_SUB_ENABLED->value => true,
|
||||||
|
EnvVars::REDIS_SERVERS->value => 'tcp://shlink_redis:6379',
|
||||||
|
|
||||||
|
];
|
||||||
42
config/roadrunner/.rr.dev.yml
Normal file
42
config/roadrunner/.rr.dev.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
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
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
debug: true
|
||||||
|
timeout: 300
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
mode: development
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
mode: 'off' # Disable logging as Shlink handles it internally
|
||||||
|
server:
|
||||||
|
level: info
|
||||||
|
metrics:
|
||||||
|
level: debug
|
||||||
|
jobs:
|
||||||
|
level: debug
|
||||||
49
config/roadrunner/.rr.test.yml
Normal file
49
config/roadrunner/.rr.test.yml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
############################################################################################
|
||||||
|
# Routes here need to be relative to the project root, as API tests are run with `-w .` #
|
||||||
|
# See https://github.com/orgs/roadrunner-server/discussions/1440#discussioncomment-8486186 #
|
||||||
|
############################################################################################
|
||||||
|
|
||||||
|
rpc:
|
||||||
|
listen: tcp://127.0.0.1:6001
|
||||||
|
|
||||||
|
server:
|
||||||
|
command: 'php ./bin/roadrunner-worker.php'
|
||||||
|
|
||||||
|
http:
|
||||||
|
address: '0.0.0.0:9999'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: './public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
debug: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
debug: false
|
||||||
|
timeout: 300
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
encoding: json
|
||||||
|
mode: development
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
mode: 'off' # Disable logging as Shlink handles it internally
|
||||||
|
server:
|
||||||
|
encoding: json
|
||||||
|
level: info
|
||||||
|
metrics:
|
||||||
|
level: panic
|
||||||
|
jobs:
|
||||||
|
level: panic
|
||||||
38
config/roadrunner/.rr.yml
Normal file
38
config/roadrunner/.rr.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
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:-8080}'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: '../../public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: ${WEB_WORKER_NUM:-0}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
timeout: 300 # 5 minutes
|
||||||
|
pool:
|
||||||
|
num_workers: ${TASK_WORKER_NUM:-0}
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
mode: production
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
mode: 'off' # Disable logging as Shlink handles it internally
|
||||||
|
server:
|
||||||
|
level: info
|
||||||
|
jobs:
|
||||||
|
level: debug
|
||||||
@@ -7,12 +7,6 @@ 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\SWOOLE_TESTING_HOST;
|
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_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);
|
||||||
@@ -20,14 +14,11 @@ $config = $container->get('config');
|
|||||||
$em = $container->get(EntityManager::class);
|
$em = $container->get(EntityManager::class);
|
||||||
$httpClient = $container->get('shlink_test_api_client');
|
$httpClient = $container->get('shlink_test_api_client');
|
||||||
|
|
||||||
// Dump code coverage when process shuts down
|
$testHelper->createTestDb(
|
||||||
register_shutdown_function(function () use ($httpClient): void {
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
$httpClient->request(
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
'GET',
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
sprintf('http://%s:%s/api-tests/stop-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
$testHelper->createTestDb(['bin/cli', 'db:create'], ['bin/cli', 'db:migrate']);
|
|
||||||
ApiTest\ApiTestCase::setApiClient($httpClient);
|
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(
|
||||||
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
runSqlCommand: ['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(['bin/cli', 'db:create'], ['bin/cli', 'db:migrate']);
|
$container->get(Helper\TestHelper::class)->createTestDb(
|
||||||
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));
|
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));
|
||||||
|
|||||||
@@ -4,5 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkioTest\Shlink;
|
namespace ShlinkioTest\Shlink;
|
||||||
|
|
||||||
const SWOOLE_TESTING_HOST = '127.0.0.1';
|
const API_TESTS_HOST = '127.0.0.1';
|
||||||
const SWOOLE_TESTING_PORT = 9999;
|
const API_TESTS_PORT = 9999;
|
||||||
|
|
||||||
|
const ANDROID_USER_AGENT = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
. 'Chrome/109.0.5414.86 Mobile Safari/537.36';
|
||||||
|
const IOS_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 '
|
||||||
|
. '(KHTML, like Gecko) FxiOS/109.0 Mobile/15E148 Safari/605.1.15';
|
||||||
|
const DESKTOP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like '
|
||||||
|
. 'Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61';
|
||||||
|
|||||||
15
config/test/shlink_test_env.php
Normal file
15
config/test/shlink_test_env.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
EnvVars::APP_ENV->value => 'test',
|
||||||
|
|
||||||
|
// URL shortener
|
||||||
|
EnvVars::DEFAULT_DOMAIN->value => 's.test',
|
||||||
|
EnvVars::IS_HTTPS_ENABLED->value => false,
|
||||||
|
|
||||||
|
];
|
||||||
@@ -6,37 +6,38 @@ namespace Shlinkio\Shlink;
|
|||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\HtmlResponse;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Mezzio\Router\FastRouteRouter;
|
||||||
use Monolog\Logger;
|
use Monolog\Level;
|
||||||
use PHPUnit\Runner\Version;
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Shlinkio\Shlink\TestUtils\ApiTest\CoverageMiddleware;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Shlinkio\Shlink\TestUtils\CliTest\CliCoverageDelegator;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Shlinkio\Shlink\TestUtils\Helper\CoverageHelper;
|
||||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
use Symfony\Component\Console\Application;
|
||||||
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 function Laminas\Stratigility\middleware;
|
use function Laminas\Stratigility\middleware;
|
||||||
use function Shlinkio\Shlink\Config\env;
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function sleep;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function sys_get_temp_dir;
|
|
||||||
|
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||||
|
|
||||||
$isApiTest = env('TEST_ENV') === 'api';
|
$testEnv = env('TEST_ENV');
|
||||||
$generateCoverage = env('GENERATE_COVERAGE') === 'yes';
|
$isApiTest = $testEnv === 'api';
|
||||||
if ($isApiTest && $generateCoverage) {
|
$isCliTest = $testEnv === 'cli';
|
||||||
$filter = new Filter();
|
$isE2eTest = $isApiTest || $isCliTest;
|
||||||
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
|
|
||||||
$filter->includeDirectory(__DIR__ . '/../../module/Rest/src');
|
$coverageType = env('GENERATE_COVERAGE');
|
||||||
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
$generateCoverage = $coverageType === 'yes';
|
||||||
}
|
$coverage = $isE2eTest && $generateCoverage ? CoverageHelper::createCoverageForDirectories(
|
||||||
|
[
|
||||||
|
__DIR__ . '/../../module/Core/src',
|
||||||
|
__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src',
|
||||||
|
],
|
||||||
|
__DIR__ . '/../../build/coverage-' . $testEnv,
|
||||||
|
) : null;
|
||||||
|
|
||||||
$buildDbConnection = static function (): array {
|
$buildDbConnection = static function (): array {
|
||||||
$driver = env('DB_DRIVER', 'sqlite');
|
$driver = env('DB_DRIVER', 'sqlite');
|
||||||
@@ -46,12 +47,12 @@ $buildDbConnection = static function (): array {
|
|||||||
return match ($driver) {
|
return match ($driver) {
|
||||||
'sqlite' => [
|
'sqlite' => [
|
||||||
'driver' => 'pdo_sqlite',
|
'driver' => 'pdo_sqlite',
|
||||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
'memory' => 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',
|
||||||
'port' => $isCi ? '5433' : '5432',
|
'port' => $isCi ? '5434' : '5432',
|
||||||
'user' => 'postgres',
|
'user' => 'postgres',
|
||||||
'password' => 'root',
|
'password' => 'root',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
@@ -63,6 +64,9 @@ $buildDbConnection = static function (): array {
|
|||||||
'user' => 'sa',
|
'user' => 'sa',
|
||||||
'password' => 'Passw0rd!',
|
'password' => 'Passw0rd!',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
|
'driverOptions' => [
|
||||||
|
'TrustServerCertificate' => 'true',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
default => [ // mysql and maria
|
default => [ // mysql and maria
|
||||||
'driver' => 'pdo_mysql',
|
'driver' => 'pdo_mysql',
|
||||||
@@ -76,100 +80,54 @@ $buildDbConnection = static function (): array {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
$buildTestLoggerConfig = fn (string $handlerName, string $filename) => [
|
$buildTestLoggerConfig = static fn (string $filename) => [
|
||||||
'handlers' => [
|
'level' => Level::Debug->value,
|
||||||
$handlerName => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'name' => StreamHandler::class,
|
'destination' => sprintf('data/log/api-tests/%s', $filename),
|
||||||
'params' => [
|
'add_new_line' => true,
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
'stream' => sprintf('data/log/api-tests/%s', $filename),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'debug' => true,
|
'debug' => true,
|
||||||
ConfigAggregator::ENABLE_CACHE => false,
|
ConfigAggregator::ENABLE_CACHE => false,
|
||||||
|
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
||||||
|
|
||||||
'url_shortener' => [
|
'routes' => [
|
||||||
'domain' => [
|
// This route is used to test that title resolution is skipped if the long URL times out
|
||||||
'schema' => 'http',
|
|
||||||
'hostname' => 'doma.in',
|
|
||||||
],
|
|
||||||
'validate_url' => true,
|
|
||||||
],
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'enable_coroutine' => false,
|
|
||||||
'swoole-http-server' => [
|
|
||||||
'host' => SWOOLE_TESTING_HOST,
|
|
||||||
'port' => SWOOLE_TESTING_PORT,
|
|
||||||
'process-name' => 'shlink_test',
|
|
||||||
'options' => [
|
|
||||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
|
||||||
'log_file' => __DIR__ . '/../../data/log/api-tests/output.log',
|
|
||||||
'enable_coroutine' => false,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'routes' => !$isApiTest ? [] : [
|
|
||||||
[
|
[
|
||||||
'name' => 'dump_coverage',
|
'name' => 'long_url_with_timeout',
|
||||||
'path' => '/api-tests/stop-coverage',
|
'path' => '/api-tests/long-url-with-timeout',
|
||||||
'middleware' => middleware(static function () use (&$coverage) {
|
|
||||||
// TODO I have tried moving this block to a listener so that it's invoked automatically,
|
|
||||||
// but then the coverage is generated empty ¯\_(ツ)_/¯
|
|
||||||
if ($coverage) { // @phpstan-ignore-line
|
|
||||||
$basePath = __DIR__ . '/../../build/coverage-api';
|
|
||||||
|
|
||||||
(new PHP())->process($coverage, $basePath . '.cov');
|
|
||||||
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
|
||||||
(new Html())->process($coverage, $basePath . '/coverage-html');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EmptyResponse();
|
|
||||||
}),
|
|
||||||
'allowed_methods' => ['GET'],
|
'allowed_methods' => ['GET'],
|
||||||
|
'middleware' => middleware(static function () {
|
||||||
|
sleep(5); // Title resolution times out at 3 seconds
|
||||||
|
return new HtmlResponse('<title>The title</title>');
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'middleware_pipeline' => !$isApiTest ? [] : [
|
'middleware_pipeline' => !$isApiTest ? [] : [
|
||||||
'capture_code_coverage' => [
|
'capture_code_coverage' => [
|
||||||
'middleware' => middleware(static function (
|
'middleware' => new CoverageMiddleware($coverage),
|
||||||
ServerRequestInterface $req,
|
|
||||||
RequestHandlerInterface $handler,
|
|
||||||
) use (&$coverage): ResponseInterface {
|
|
||||||
$coverage?->start($req->getHeaderLine('x-coverage-id'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
return $handler->handle($req);
|
|
||||||
} finally {
|
|
||||||
$coverage?->stop();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
'priority' => 9999,
|
'priority' => 9999,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'mercure' => [
|
|
||||||
'public_hub_url' => null,
|
|
||||||
'internal_hub_url' => null,
|
|
||||||
'jwt_secret' => null,
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'services' => [
|
'services' => [
|
||||||
'shlink_test_api_client' => new Client([
|
'shlink_test_api_client' => new Client([
|
||||||
'base_uri' => sprintf('http://%s:%s/', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
'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 => [
|
||||||
|
new CliCoverageDelegator($coverage),
|
||||||
|
],
|
||||||
|
] : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
@@ -178,13 +136,14 @@ return [
|
|||||||
|
|
||||||
'data_fixtures' => [
|
'data_fixtures' => [
|
||||||
'paths' => [
|
'paths' => [
|
||||||
|
// TODO These are used for other module's tests, so maybe should be somewhere else
|
||||||
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => $buildTestLoggerConfig('shlink_handler', 'shlink.log'),
|
'Shlink' => $buildTestLoggerConfig('shlink.log'),
|
||||||
'Access' => $buildTestLoggerConfig('access_handler', 'access.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/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
|
curl https://packages.microsoft.com/config/ubuntu/22.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 msodbcsql18
|
||||||
apt-get install unixodbc-dev
|
# apt-get install unixodbc-dev
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
ServerName doma.in
|
ServerName s.test
|
||||||
DocumentRoot "/path/to/shlink/public"
|
DocumentRoot "/path/to/shlink/public"
|
||||||
|
|
||||||
<Directory "/path/to/shlink/public">
|
<Directory "/path/to/shlink/public">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
server {
|
server {
|
||||||
server_name doma.in;
|
server_name s.test;
|
||||||
listen 80;
|
listen 80;
|
||||||
root /path/to/shlink/public;
|
root /path/to/shlink/public;
|
||||||
index index.php;
|
index index.php;
|
||||||
@@ -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/php8.0-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi.conf;
|
include fastcgi.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
/var/log/shlink/shlink_openswoole.log {
|
|
||||||
su root root
|
|
||||||
daily
|
|
||||||
missingok
|
|
||||||
rotate 120
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 0640 root root
|
|
||||||
postrotate
|
|
||||||
/etc/init.d/shlink_openswoole restart
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
### BEGIN INIT INFO
|
|
||||||
# Provides: shlink_openswoole
|
|
||||||
# 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 openswoole
|
|
||||||
### END INIT INFO
|
|
||||||
|
|
||||||
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
|
|
||||||
RUNAS=root
|
|
||||||
|
|
||||||
PIDFILE=/var/run/shlink_openswoole.pid
|
|
||||||
LOGDIR=/var/log/shlink
|
|
||||||
LOGFILE=${LOGDIR}/shlink_openswoole.log
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
|
||||||
echo 'Shlink with openswoole already running' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo 'Starting shlink with openswoole' >&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 openswoole not running' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo 'Stopping shlink with openswoole' >&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
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
FROM php:8.1.5-fpm-alpine3.15
|
FROM php:8.3-fpm-alpine3.19
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.21
|
ENV APCU_VERSION 5.1.23
|
||||||
ENV PDO_SQLSRV_VERSION 5.10.0
|
ENV PDO_SQLSRV_VERSION 5.12.0
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
@@ -30,7 +31,9 @@ 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 apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
RUN docker-php-ext-install bcmath
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
@@ -43,14 +46,15 @@ RUN mkdir -p /usr/src/php/ext/apcu \
|
|||||||
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
&& 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
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
# Install pcov and sqlsrv driver
|
# Install xdebug 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 && \
|
RUN apk add --update linux-headers && \
|
||||||
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
apk add --allow-untrusted msodbcsql${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 pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} xdebug && \
|
||||||
docker-php-ext-enable pdo_sqlsrv pcov && \
|
docker-php-ext-enable pdo_sqlsrv xdebug && \
|
||||||
apk del .phpize-deps && \
|
apk del .phpize-deps && \
|
||||||
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
# Install composer
|
# Install composer
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
display_errors=On
|
display_errors=On
|
||||||
error_reporting=-1
|
error_reporting=-1
|
||||||
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
|
|
||||||
|
|||||||
2
data/infra/redis/redis-acl.conf
Normal file
2
data/infra/redis/redis-acl.conf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
user foo allcommands allkeys on >bar
|
||||||
|
requirepass barbar
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
FROM php:8.1.5-alpine3.15
|
FROM php:8.3-alpine3.19
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.21
|
ENV APCU_VERSION 5.1.23
|
||||||
ENV INOTIFY_VERSION 3.0.0
|
ENV PDO_SQLSRV_VERSION 5.12.0
|
||||||
ENV OPENSWOOLE_VERSION 4.11.0
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
ENV PDO_SQLSRV_VERSION 5.10.0
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
@@ -32,7 +31,9 @@ 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 apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
RUN docker-php-ext-install bcmath
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
@@ -45,22 +46,15 @@ RUN mkdir -p /usr/src/php/ext/apcu \
|
|||||||
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
&& 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
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
# Install inotify extension
|
# Install xdebug and sqlsrv driver
|
||||||
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
RUN apk add --update linux-headers && \
|
||||||
RUN mkdir -p /usr/src/php/ext/inotify \
|
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1 \
|
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
&& docker-php-ext-configure inotify \
|
|
||||||
&& docker-php-ext-install inotify \
|
|
||||||
&& rm /tmp/inotify.tar.gz
|
|
||||||
|
|
||||||
# Install openswoole, pcov and mssql 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 && \
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} xdebug && \
|
||||||
docker-php-ext-enable openswoole pdo_sqlsrv pcov && \
|
docker-php-ext-enable pdo_sqlsrv xdebug && \
|
||||||
apk del .phpize-deps && \
|
apk del .phpize-deps && \
|
||||||
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
# Install composer
|
# Install composer
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
@@ -71,12 +65,15 @@ RUN chmod 777 /home
|
|||||||
VOLUME /home/shlink
|
VOLUME /home/shlink
|
||||||
WORKDIR /home/shlink
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
# Expose openswoole port
|
# Expose roadrunner 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, openswoole might think it is already in execution
|
# Download roadrunner binary
|
||||||
# This forces the app to be started every second until the exit code is 0
|
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; fi && \
|
||||||
until php ./vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
# Create env file if it does not exist yet
|
||||||
|
if [[ ! -f "./config/params/shlink_dev_env.php" ]]; then cp ./config/params/shlink_dev_env.php.dist ./config/params/shlink_dev_env.php ; fi && \
|
||||||
|
# Run with `exec` so that signals are properly handled
|
||||||
|
exec ./bin/rr serve -c config/roadrunner/.rr.dev.yml
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80 default_server;
|
|
||||||
|
|
||||||
error_log /home/shlink/www/data/infra/nginx/swoole_proxy.error.log;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_pass http://shlink_swoole:8080;
|
|
||||||
proxy_read_timeout 90s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Exception;
|
|
||||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
|
||||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
use function is_subclass_of;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20160819142757 extends AbstractMigration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$platformClass = $this->connection->getDatabasePlatform();
|
|
||||||
$table = $schema->getTable('short_urls');
|
|
||||||
$column = $table->getColumn('short_code');
|
|
||||||
|
|
||||||
match (true) {
|
|
||||||
is_subclass_of($platformClass, MySQLPlatform::class) => $column
|
|
||||||
->setPlatformOption('charset', 'utf8mb4')
|
|
||||||
->setPlatformOption('collation', 'utf8mb4_bin'),
|
|
||||||
is_subclass_of($platformClass, SqlitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'),
|
|
||||||
default => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Nothing to roll back
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Types\Types;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20160820191203 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Check if the tables already exist
|
|
||||||
$tables = $schema->getTables();
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
if ($table->getName() === 'tags') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->createTagsTable($schema);
|
|
||||||
$this->createShortUrlsInTagsTable($schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createTagsTable(Schema $schema): void
|
|
||||||
{
|
|
||||||
$table = $schema->createTable('tags');
|
|
||||||
$table->addColumn('id', Types::BIGINT, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'autoincrement' => true,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
$table->addColumn('name', Types::STRING, [
|
|
||||||
'length' => 255,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
$table->addUniqueIndex(['name']);
|
|
||||||
|
|
||||||
$table->setPrimaryKey(['id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createShortUrlsInTagsTable(Schema $schema): void
|
|
||||||
{
|
|
||||||
$table = $schema->createTable('short_urls_in_tags');
|
|
||||||
$table->addColumn('short_url_id', Types::BIGINT, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
$table->addColumn('tag_id', Types::BIGINT, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$table->addForeignKeyConstraint('tags', ['tag_id'], ['id'], [
|
|
||||||
'onDelete' => 'CASCADE',
|
|
||||||
'onUpdate' => 'RESTRICT',
|
|
||||||
]);
|
|
||||||
$table->addForeignKeyConstraint('short_urls', ['short_url_id'], ['id'], [
|
|
||||||
'onDelete' => 'CASCADE',
|
|
||||||
'onUpdate' => 'RESTRICT',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$table->setPrimaryKey(['short_url_id', 'tag_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$schema->dropTable('short_urls_in_tags');
|
|
||||||
$schema->dropTable('tags');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\DBAL\Types\Types;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20171021093246 extends AbstractMigration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if ($shortUrls->hasColumn('valid_since')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->addColumn('valid_since', Types::DATETIME_MUTABLE, [
|
|
||||||
'notnull' => false,
|
|
||||||
]);
|
|
||||||
$shortUrls->addColumn('valid_until', Types::DATETIME_MUTABLE, [
|
|
||||||
'notnull' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if (! $shortUrls->hasColumn('valid_since')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->dropColumn('valid_since');
|
|
||||||
$shortUrls->dropColumn('valid_until');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\DBAL\Types\Types;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20171022064541 extends AbstractMigration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if ($shortUrls->hasColumn('max_visits')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->addColumn('max_visits', Types::INTEGER, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'notnull' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if (! $shortUrls->hasColumn('max_visits')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->dropColumn('max_visits');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20180801183328 extends AbstractMigration
|
|
||||||
{
|
|
||||||
private const NEW_SIZE = 255;
|
|
||||||
private const OLD_SIZE = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->setSize($schema, self::NEW_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->setSize($schema, self::OLD_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
private function setSize(Schema $schema, int $size): void
|
|
||||||
{
|
|
||||||
$schema->getTable('short_urls')->getColumn('short_code')->setLength($size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Exception;
|
|
||||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
use PDO;
|
|
||||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
|
||||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20180913205455 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Nothing to create
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function postUp(Schema $schema): void
|
|
||||||
{
|
|
||||||
$qb = $this->connection->createQueryBuilder();
|
|
||||||
$qb->select('id', 'remote_addr')
|
|
||||||
->from('visits');
|
|
||||||
$st = $this->connection->executeQuery($qb->getSQL());
|
|
||||||
|
|
||||||
$qb = $this->connection->createQueryBuilder();
|
|
||||||
$qb->update('visits', 'v')
|
|
||||||
->set('v.remote_addr', ':obfuscatedAddr')
|
|
||||||
->where('v.id=:id');
|
|
||||||
|
|
||||||
while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
|
|
||||||
$addr = $row['remote_addr'] ?? null;
|
|
||||||
if ($addr === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$qb->setParameters([
|
|
||||||
'id' => $row['id'],
|
|
||||||
'obfuscatedAddr' => $this->determineAddress((string) $addr),
|
|
||||||
])->execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function determineAddress(string $addr): ?string
|
|
||||||
{
|
|
||||||
if ($addr === IpAddress::LOCALHOST) {
|
|
||||||
return $addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (string) IpAddress::fromString($addr)->getAnonymizedCopy();
|
|
||||||
} catch (InvalidArgumentException) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Nothing to rollback
|
|
||||||
}
|
|
||||||
|
|
||||||
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