mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
1284 Commits
v2.9.0-alp
...
v3.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8888e33ccc | ||
|
|
250f9f2d89 | ||
|
|
9fd864df0b | ||
|
|
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 | ||
|
|
68e0aa1ea9 | ||
|
|
ceaf64c9b3 | ||
|
|
e015b1bec5 | ||
|
|
ca9726c997 | ||
|
|
85c79abd30 | ||
|
|
54c1c7ad84 | ||
|
|
af15e31b42 | ||
|
|
99b4f9f4dd | ||
|
|
9a0e5ea626 | ||
|
|
984205e02c | ||
|
|
e11bf6ac67 | ||
|
|
e029d91544 | ||
|
|
011856cbfa | ||
|
|
9ce8164013 | ||
|
|
dca6b7bbf5 | ||
|
|
2511ec3395 | ||
|
|
9f6ffc7186 | ||
|
|
622f0217fa | ||
|
|
09eba49bab | ||
|
|
c20c3801a8 | ||
|
|
f8208b7288 | ||
|
|
3db8a65ddb | ||
|
|
0495b6f298 | ||
|
|
52c55f385d | ||
|
|
fe28d6fba0 | ||
|
|
0294e49d4a | ||
|
|
cbaf51d3ef | ||
|
|
efb604a381 | ||
|
|
da87f05126 | ||
|
|
21534b78cb | ||
|
|
ab65593f7d | ||
|
|
3a82691503 | ||
|
|
430e2ff0b5 | ||
|
|
7d572e7988 | ||
|
|
1449e24b66 | ||
|
|
6a671760da | ||
|
|
613bdd82b0 | ||
|
|
01bae358f9 | ||
|
|
3a8e560dc5 | ||
|
|
a0c538d9ee | ||
|
|
07c30f86e9 | ||
|
|
c22e38f9a0 | ||
|
|
4d082a87a1 | ||
|
|
1b6512fc8d | ||
|
|
7502e8a1e4 | ||
|
|
5a25211371 | ||
|
|
6983f9b2bf | ||
|
|
5affe64b61 | ||
|
|
c52f3c396b | ||
|
|
e1ebbaa52f | ||
|
|
7abe6af5ec | ||
|
|
c98ea6055b | ||
|
|
3e3d255edf | ||
|
|
816d4851e7 | ||
|
|
9e32886f60 | ||
|
|
79af315b9f | ||
|
|
4110c702c0 | ||
|
|
57eb29c3c8 | ||
|
|
5267c4eee6 | ||
|
|
1453ebe8ca | ||
|
|
3b5cea5768 | ||
|
|
a89f67348d | ||
|
|
af1ae0399c | ||
|
|
5bf84144e7 | ||
|
|
d9adff5749 | ||
|
|
a1cd8baf3e | ||
|
|
87cadce0ac | ||
|
|
f22f50afa2 | ||
|
|
d0fa6f7e03 | ||
|
|
d29c58dce5 | ||
|
|
9ea8f3b590 | ||
|
|
ffffc68144 | ||
|
|
086de9f2a0 | ||
|
|
1b731aa4a3 | ||
|
|
12913f6b90 | ||
|
|
1d4186392c | ||
|
|
48d3ab0cb4 | ||
|
|
a9d04729eb | ||
|
|
7adf2292bd | ||
|
|
c8f55f9c05 | ||
|
|
93de62f81d | ||
|
|
9766231d41 | ||
|
|
9df80e5bec | ||
|
|
81b00e4302 | ||
|
|
25ac7c31c4 | ||
|
|
11c6c9a2b8 | ||
|
|
066268765a | ||
|
|
356b33ced0 | ||
|
|
77088d55f9 | ||
|
|
8d965655a8 | ||
|
|
3ace4952e6 | ||
|
|
299f9f3a10 | ||
|
|
0e6790cdab | ||
|
|
1f90af3aec | ||
|
|
cdb18a5baf | ||
|
|
8adb6596fb | ||
|
|
dd6bcd68cc | ||
|
|
1c9ce0ede0 | ||
|
|
6b409b06cc | ||
|
|
361e864415 | ||
|
|
d5606114cd | ||
|
|
afca66d655 | ||
|
|
33a6c9fda7 | ||
|
|
a198484ab6 | ||
|
|
dd5bce9694 | ||
|
|
bef17ff76d | ||
|
|
7202605fc8 | ||
|
|
747dac531d | ||
|
|
07d24f70e1 | ||
|
|
d0546a2ea2 | ||
|
|
9e9621e7b2 | ||
|
|
d39f3b4265 | ||
|
|
223339cd61 | ||
|
|
dc430bae10 | ||
|
|
661b07e12f | ||
|
|
0727c7bdfb | ||
|
|
b4c52116b4 | ||
|
|
89dc6108b7 | ||
|
|
492eba3a8b | ||
|
|
77fee1390f | ||
|
|
bfb54189b8 | ||
|
|
fb43885d85 | ||
|
|
7c1f705e64 | ||
|
|
fe1fa7689a | ||
|
|
8b79eee081 | ||
|
|
4a3e04ced9 | ||
|
|
61618250ec | ||
|
|
60c0ca3ae5 | ||
|
|
3436405c55 | ||
|
|
d43c3ec865 | ||
|
|
545da96d15 | ||
|
|
f53305c404 | ||
|
|
199d976e3d | ||
|
|
a1366f0ef1 | ||
|
|
91192a8a8f | ||
|
|
c6f16b0558 | ||
|
|
0d37eb65c9 | ||
|
|
f7e3a74794 | ||
|
|
976b07cd61 | ||
|
|
cff9cd5fb8 | ||
|
|
f0fd947046 | ||
|
|
7f4ada9c4b | ||
|
|
db4ef328b1 | ||
|
|
b438802e71 | ||
|
|
632a19ceeb | ||
|
|
629f8ece7a | ||
|
|
9215f9beb5 | ||
|
|
154431e86c | ||
|
|
8cfb14198b | ||
|
|
2ed475fc76 | ||
|
|
34512da2fb | ||
|
|
5b3c6f7752 | ||
|
|
f4dd27ca3f | ||
|
|
ce47d8c591 | ||
|
|
b941ee9aa9 | ||
|
|
45de3f0128 | ||
|
|
41d3826c1a | ||
|
|
f2ff6e6a70 | ||
|
|
e47c90c645 | ||
|
|
d2fef20239 | ||
|
|
3b359cfc4f | ||
|
|
acfc5a4676 | ||
|
|
a6b1647f27 | ||
|
|
d5851bbb6a | ||
|
|
397bbe2655 | ||
|
|
95d8d3ef72 | ||
|
|
1b51a1aedd | ||
|
|
ff75b3cd1f | ||
|
|
2abcaf02e2 | ||
|
|
d0c9f5a776 | ||
|
|
a46d510e2b | ||
|
|
2d861b4077 | ||
|
|
470c62d993 | ||
|
|
364734094b | ||
|
|
a667c957ee | ||
|
|
dc648b0142 | ||
|
|
1d14140986 | ||
|
|
2b693dc492 | ||
|
|
38bea6c086 | ||
|
|
cbdc5f121e | ||
|
|
562763199a | ||
|
|
107c09604a | ||
|
|
2b0567b368 | ||
|
|
d00a56bec0 | ||
|
|
ead8cc6cec | ||
|
|
806ff9daaf | ||
|
|
b3863a3e10 | ||
|
|
5559107776 | ||
|
|
af1cf806f0 | ||
|
|
0cf33c6119 | ||
|
|
b38b8a3365 | ||
|
|
e998c8434d | ||
|
|
4b90cf93d3 | ||
|
|
3dd4e33758 | ||
|
|
6caeb11598 | ||
|
|
11a383b7e5 | ||
|
|
fd2a2530b1 | ||
|
|
2f42b2d072 | ||
|
|
775f58f972 | ||
|
|
5c0abb3d96 | ||
|
|
3dc46bc5a3 | ||
|
|
e2871fc048 | ||
|
|
44e3f9b49f | ||
|
|
d3f4263639 | ||
|
|
9dec05f62d | ||
|
|
0447aa07fa | ||
|
|
0e25af790d | ||
|
|
d8484e777f | ||
|
|
665a3dbcbf | ||
|
|
103af2e2c1 | ||
|
|
d0daeb0078 | ||
|
|
a9aa49c2e5 | ||
|
|
aad24389a7 | ||
|
|
4b4f6f3201 | ||
|
|
81f82d3b73 | ||
|
|
4103ccf791 | ||
|
|
e2ed11f960 | ||
|
|
8e1cd67a3d | ||
|
|
18b4caa55e | ||
|
|
30207ce0c2 | ||
|
|
0f37f1cb23 | ||
|
|
99a905cdee | ||
|
|
6eac079440 | ||
|
|
4a1e7b8d5a | ||
|
|
5e781a9010 | ||
|
|
277d817429 | ||
|
|
970f202757 | ||
|
|
2c6b2b47a4 | ||
|
|
5c8be4b21f | ||
|
|
558a4a2b30 | ||
|
|
203ad7d594 | ||
|
|
04cf1aed9c | ||
|
|
8c14526f85 | ||
|
|
1ff241411b | ||
|
|
351e36b273 | ||
|
|
ca06040efc | ||
|
|
2102cc4e9a | ||
|
|
14d3493db8 | ||
|
|
d082d208e1 | ||
|
|
959efd17c8 | ||
|
|
30a7c55e84 | ||
|
|
2aec759857 | ||
|
|
54dcaaac0c | ||
|
|
8e5730f374 | ||
|
|
cb1705b6e8 | ||
|
|
0bcefda60d | ||
|
|
966620f840 | ||
|
|
bd3bb67949 | ||
|
|
69f4daa9d2 | ||
|
|
ec11155c9c | ||
|
|
c48a3a24f7 | ||
|
|
1b8bc9f0ff | ||
|
|
5bf25c7eca | ||
|
|
5a7f0ad340 | ||
|
|
8a93922da0 | ||
|
|
295de5be8e | ||
|
|
5c114b584d | ||
|
|
dad58b7610 | ||
|
|
23c1dadb4c | ||
|
|
05332e0606 | ||
|
|
453842246f | ||
|
|
38280b9027 | ||
|
|
7d7c0011bb | ||
|
|
de2d87a6d9 | ||
|
|
537152450f | ||
|
|
87f6b19207 | ||
|
|
064fef5d8a | ||
|
|
6aebaa94af | ||
|
|
a1a6ac9c08 | ||
|
|
0d936425c2 | ||
|
|
00f867c6ee | ||
|
|
bfea3f35f0 | ||
|
|
3f3cf5e20e | ||
|
|
0786a962e7 | ||
|
|
f7c0486101 | ||
|
|
2e3798b282 | ||
|
|
181740c3e9 | ||
|
|
23c51a1d5f | ||
|
|
15ce529c09 | ||
|
|
0fd941401b | ||
|
|
808ae6a442 | ||
|
|
ada8d18fa1 | ||
|
|
9752abff19 | ||
|
|
ee43e68a57 | ||
|
|
348ac78f5a | ||
|
|
0b22fb933c | ||
|
|
cbd4b4849f | ||
|
|
f8a48c16f0 | ||
|
|
8cc4e4bfca | ||
|
|
6c01bb87bf | ||
|
|
02d5a6f15e | ||
|
|
f361403888 | ||
|
|
3a4550fe24 | ||
|
|
5e722c830f | ||
|
|
5a56982ad9 | ||
|
|
13d70cd12a | ||
|
|
bb87bdce8a | ||
|
|
cc7ded1be7 | ||
|
|
d8735e6a91 | ||
|
|
813ae71aad | ||
|
|
1a75bd87d8 | ||
|
|
bdc89e2056 | ||
|
|
bf09990f9c | ||
|
|
81ba8dc518 | ||
|
|
e519aaaf1e | ||
|
|
5a90a5e6c7 | ||
|
|
b855ea92a9 | ||
|
|
7e74d06cdd | ||
|
|
1e7602bc36 | ||
|
|
7477e672fe | ||
|
|
4a4522dfa3 | ||
|
|
8afe058cfc | ||
|
|
e13103a925 | ||
|
|
8e167ff174 | ||
|
|
c0dcd31819 | ||
|
|
a83ae996db | ||
|
|
a66ddabe8a | ||
|
|
cdab1e9cae | ||
|
|
f2140d1eb0 | ||
|
|
4a3fa85b5f | ||
|
|
ade23a9650 | ||
|
|
fc547e6c47 | ||
|
|
f532b5edee | ||
|
|
da76eb5cf4 | ||
|
|
ac89f352ce | ||
|
|
198b2a2ace | ||
|
|
93a3d78111 | ||
|
|
494997d021 | ||
|
|
eb1345e5c3 | ||
|
|
dc8f5d002d | ||
|
|
9030e5e6eb | ||
|
|
2b827baeed | ||
|
|
cc6fa312f0 | ||
|
|
b8eba5b643 | ||
|
|
0c3f98cc37 | ||
|
|
cd35770d26 | ||
|
|
bd3a59e9ca | ||
|
|
ff50d601b3 | ||
|
|
a4fde0f9e6 | ||
|
|
c7a621cb31 | ||
|
|
6f62d62909 | ||
|
|
c3aa2df4e9 | ||
|
|
f4fbf2da75 | ||
|
|
288de8acaa | ||
|
|
750e6cff45 | ||
|
|
f49e94052d | ||
|
|
ceb642b745 | ||
|
|
ed1d886f01 | ||
|
|
db98d811b0 | ||
|
|
14ba11e1ab | ||
|
|
483bdddb18 | ||
|
|
d16fda3f16 | ||
|
|
c718b94937 | ||
|
|
bb21ab073f | ||
|
|
3ffe530461 | ||
|
|
95cf0d86bc | ||
|
|
9899a5fc56 | ||
|
|
952648185c | ||
|
|
69740493b7 | ||
|
|
994a28f31d | ||
|
|
b0a8a03f0a | ||
|
|
36e740f4cc | ||
|
|
a5874a3f80 | ||
|
|
0c95b978b4 | ||
|
|
e21f9dd1fb | ||
|
|
74a08b86ce | ||
|
|
8212d3c540 | ||
|
|
1ed6458b39 | ||
|
|
60c8f23a63 | ||
|
|
5e627641ea | ||
|
|
abc954aa47 | ||
|
|
3bfa27e682 | ||
|
|
4b7e122254 | ||
|
|
cfd3c13751 | ||
|
|
6a1ee2b894 | ||
|
|
cbec4a4e81 | ||
|
|
c7d8c1cab5 | ||
|
|
c39e1e649d | ||
|
|
95ab64ba77 | ||
|
|
1f8fcdb0f3 | ||
|
|
fb26a8ae50 | ||
|
|
42dbeaa1a5 | ||
|
|
3305f4c03a | ||
|
|
f5beec70c8 | ||
|
|
c2cd21c15e | ||
|
|
633e389275 | ||
|
|
f5aaf298e1 | ||
|
|
7db6136436 | ||
|
|
ce7296eebb | ||
|
|
c6226547f7 | ||
|
|
e7ec8f0489 | ||
|
|
dc466f238b | ||
|
|
f164656874 | ||
|
|
ef3c59152f | ||
|
|
14c6ead389 | ||
|
|
b0d33f3a85 | ||
|
|
066cc20ee6 | ||
|
|
0f51b5b1ce | ||
|
|
ebcf3e0119 | ||
|
|
6ee248d656 | ||
|
|
8a46b410f6 | ||
|
|
cd06cea153 | ||
|
|
8393d44c50 | ||
|
|
3e8ce80f80 | ||
|
|
80e033c91d | ||
|
|
a7dd441333 | ||
|
|
48efaa9fd7 | ||
|
|
92e831175f | ||
|
|
9b75e076b5 | ||
|
|
2c5d6d1651 | ||
|
|
c5cf116f33 | ||
|
|
66a4a9bce6 | ||
|
|
7e7ef64c79 | ||
|
|
9a31f53d4d | ||
|
|
ff6747dab5 |
@@ -1,3 +1,4 @@
|
|||||||
|
bin/rr
|
||||||
config/autoload/*local*
|
config/autoload/*local*
|
||||||
data/infra
|
data/infra
|
||||||
data/cache/*
|
data/cache/*
|
||||||
@@ -9,6 +10,7 @@ data/GeoLite2-City*
|
|||||||
data/database.sqlite
|
data/database.sqlite
|
||||||
data/shlink-tests.db
|
data/shlink-tests.db
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
CONTRIBUTING.md
|
||||||
UPGRADE.md
|
UPGRADE.md
|
||||||
composer.lock
|
composer.lock
|
||||||
vendor
|
vendor
|
||||||
@@ -21,4 +23,4 @@ infection*
|
|||||||
**/test*
|
**/test*
|
||||||
build*
|
build*
|
||||||
**/.*
|
**/.*
|
||||||
bin/helper
|
!config/roadrunner/.rr.yml
|
||||||
|
|||||||
51
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
51
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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 openswoole
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Openswoole Docker image
|
||||||
|
- 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/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 swoole|Docker image
|
|
||||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Provide a summary describing the problem you are experiencing. -->
|
|
||||||
|
|
||||||
#### Current behavior
|
|
||||||
|
|
||||||
<!-- How is it actually behaving (and it shouldn't)? -->
|
|
||||||
|
|
||||||
#### Expected behavior
|
|
||||||
|
|
||||||
<!-- How did you expected to behave? -->
|
|
||||||
|
|
||||||
#### How to reproduce
|
|
||||||
|
|
||||||
<!-- Provide steps to reproduce the bug. -->
|
|
||||||
64
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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 openswoole
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Openswoole Docker image
|
||||||
|
- 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: How to reproduce
|
||||||
|
value: '<!-- Provide steps to reproduce the bug. -->'
|
||||||
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 swoole|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
|
||||||
|
default: ''
|
||||||
|
extensions-cache-key:
|
||||||
|
description: 'The key used to cache PHP extensions. If empty value is provided, extension caching is disabled'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Setup cache environment
|
||||||
|
id: extcache
|
||||||
|
uses: shivammathur/cache-extensions@v1
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
key: ${{ inputs.extensions-cache-key }}
|
||||||
|
- name: Cache extensions
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.extcache.outputs.dir }}
|
||||||
|
key: ${{ steps.extcache.outputs.key }}
|
||||||
|
restore-keys: ${{ steps.extcache.outputs.key }}
|
||||||
|
- name: Use PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
tools: composer
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
coverage: pcov
|
||||||
|
ini-values: pcov.directory=module
|
||||||
|
- name: Install dependencies
|
||||||
|
if: ${{ inputs.install-deps == 'yes' }}
|
||||||
|
run: composer install --no-interaction --prefer-dist ${{ inputs.php-version == '8.3' && '--ignore-platform-reqs' || '' }}
|
||||||
|
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-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
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: openswoole-22.1.0, pdo_sqlsrv-5.11.1
|
||||||
|
extensions-cache-key: db-tests-extensions-${{ matrix.php-version }}-${{ inputs.platform }}
|
||||||
|
- name: Create test database
|
||||||
|
if: ${{ inputs.platform == 'ms' }}
|
||||||
|
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||||
|
- name: Run tests
|
||||||
|
run: composer test:db:${{ inputs.platform }}
|
||||||
|
- name: Upload code coverage
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ matrix.php-version == '8.2' && inputs.platform == 'sqlite:ci' }}
|
||||||
|
with:
|
||||||
|
name: coverage-db
|
||||||
|
path: |
|
||||||
|
build/coverage-db
|
||||||
|
build/coverage-db.cov
|
||||||
14
.github/workflows/ci-docker-image-build.yml
vendored
Normal file
14
.github/workflows/ci-docker-image-build.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: Build docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'Dockerfile'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-docker-image:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- run: docker build -t shlink-docker-image:temp .
|
||||||
46
.github/workflows/ci-mutation-tests.yml
vendored
Normal file
46
.github/workflows/ci-mutation-tests.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Mutation tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, db, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mutation-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-22.1.0
|
||||||
|
extensions-cache-key: mutation-tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: build
|
||||||
|
- name: Resolve infection args
|
||||||
|
id: infection_args
|
||||||
|
run: echo "args=--logger-github=false" >> $GITHUB_OUTPUT
|
||||||
|
# TODO Try to filter mutation tests to improve execution times. Investigate why --git-diff-lines --git-diff-base=develop does not work
|
||||||
|
# run: |
|
||||||
|
# BRANCH="${GITHUB_REF#refs/heads/}" |
|
||||||
|
# if [[ $BRANCH == 'main' || $BRANCH == 'develop' ]]; then
|
||||||
|
# echo "args=--logger-github=false" >> $GITHUB_OUTPUT
|
||||||
|
# else
|
||||||
|
# echo "args=--logger-github=false --git-diff-lines --git-diff-base=develop" >> $GITHUB_OUTPUT
|
||||||
|
# fi;
|
||||||
|
shell: bash
|
||||||
|
- if: ${{ inputs.test-group == 'unit' }}
|
||||||
|
run: composer infect:ci:unit -- ${{ steps.infection_args.outputs.args }}
|
||||||
|
env:
|
||||||
|
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
|
||||||
|
- if: ${{ inputs.test-group != 'unit' }}
|
||||||
|
run: composer infect:ci:${{ inputs.test-group }} -- ${{ steps.infection_args.outputs.args }}
|
||||||
38
.github/workflows/ci-tests.yml
vendored
Normal file
38
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
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 }}
|
||||||
|
php-extensions: openswoole-22.1.0
|
||||||
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- run: composer test:${{ inputs.test-group }}:ci
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ matrix.php-version == '8.2' }}
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: |
|
||||||
|
build/coverage-${{ inputs.test-group }}
|
||||||
|
build/coverage-${{ inputs.test-group }}.cov
|
||||||
349
.github/workflows/ci.yml
vendored
349
.github/workflows/ci.yml
vendored
@@ -1,266 +1,160 @@
|
|||||||
name: Continuous integration
|
name: Continuous integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request: null
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.json5'
|
||||||
|
- '*.neon'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- develop
|
- develop
|
||||||
|
- 2.x
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.json5'
|
||||||
|
- '*.neon'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['8.0']
|
|
||||||
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: swoole-4.6.7
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer cs
|
|
||||||
|
|
||||||
static-analysis:
|
static-analysis:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.2']
|
||||||
|
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
|
php-extensions: openswoole-22.1.0
|
||||||
extensions: swoole-4.6.7
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
|
||||||
coverage: none
|
- run: composer ${{ matrix.command }}
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer stan
|
|
||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: unit
|
||||||
|
|
||||||
|
cli-tests:
|
||||||
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: cli
|
||||||
|
|
||||||
|
openswoole-api-tests:
|
||||||
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: api
|
||||||
|
|
||||||
|
roadrunner-api-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # rr get-binary picks this env automatically
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||||
- name: Use PHP
|
- uses: shivammathur/setup-php@v2
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
tools: composer
|
||||||
extensions: swoole-4.6.7
|
- run: composer install --no-interaction --prefer-dist --ignore-platform-req=ext-openswoole ${{ matrix.php-version == '8.3' && '--ignore-platform-reqs' || '' }}
|
||||||
coverage: pcov
|
- run: ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr
|
||||||
ini-values: pcov.directory=module
|
- run: composer test:api:rr
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:unit:ci
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
with:
|
|
||||||
name: coverage-unit
|
|
||||||
path: |
|
|
||||||
build/coverage-unit
|
|
||||||
build/coverage-unit.cov
|
|
||||||
|
|
||||||
db-tests-sqlite:
|
sqlite-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'sqlite:ci'
|
||||||
php-version: ['8.0']
|
|
||||||
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: swoole-4.6.7
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:sqlite:ci
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
with:
|
|
||||||
name: coverage-db
|
|
||||||
path: |
|
|
||||||
build/coverage-db
|
|
||||||
build/coverage-db.cov
|
|
||||||
|
|
||||||
db-tests-mysql:
|
mysql-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'mysql'
|
||||||
php-version: ['8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.6.7
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:mysql
|
|
||||||
|
|
||||||
db-tests-maria:
|
maria-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'maria'
|
||||||
php-version: ['8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.6.7
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:maria
|
|
||||||
|
|
||||||
db-tests-postgres:
|
postgres-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'postgres'
|
||||||
php-version: ['8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.6.7
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:postgres
|
|
||||||
|
|
||||||
db-tests-ms:
|
ms-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'ms'
|
||||||
php-version: ['8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Install MSSQL ODBC
|
|
||||||
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_ms
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.6.7, pdo_sqlsrv-5.9.0
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- name: Create test database
|
|
||||||
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
|
||||||
- run: composer test:db:ms
|
|
||||||
|
|
||||||
api-tests:
|
unit-mutation-tests:
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.6.7
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: bin/test/run-api-tests.sh
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
with:
|
|
||||||
name: coverage-api
|
|
||||||
path: |
|
|
||||||
build/coverage-api
|
|
||||||
build/coverage-api.cov
|
|
||||||
|
|
||||||
mutation-tests:
|
|
||||||
needs:
|
needs:
|
||||||
- unit-tests
|
- unit-tests
|
||||||
- db-tests-sqlite
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
- api-tests
|
with:
|
||||||
runs-on: ubuntu-20.04
|
test-group: unit
|
||||||
strategy:
|
|
||||||
matrix:
|
db-mutation-tests:
|
||||||
php-version: ['8.0']
|
needs:
|
||||||
test-group: ['unit', 'db']
|
- sqlite-db-tests
|
||||||
steps:
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
- name: Checkout code
|
with:
|
||||||
uses: actions/checkout@v2
|
test-group: db
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
api-mutation-tests:
|
||||||
with:
|
needs:
|
||||||
php-version: ${{ matrix.php-version }}
|
- openswoole-api-tests
|
||||||
tools: composer
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
extensions: swoole-4.6.7
|
with:
|
||||||
coverage: pcov
|
test-group: api
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
cli-mutation-tests:
|
||||||
- uses: actions/download-artifact@v2
|
needs:
|
||||||
with:
|
- cli-tests
|
||||||
path: build
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
- run: composer infect:ci:${{ matrix.test-group }}
|
with:
|
||||||
|
test-group: cli
|
||||||
|
|
||||||
upload-coverage:
|
upload-coverage:
|
||||||
needs:
|
needs:
|
||||||
- unit-tests
|
- unit-tests
|
||||||
- db-tests-sqlite
|
- openswoole-api-tests
|
||||||
- api-tests
|
- cli-tests
|
||||||
runs-on: ubuntu-20.04
|
- sqlite-db-tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.2']
|
||||||
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: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
coverage: pcov
|
coverage: pcov
|
||||||
ini-values: pcov.directory=module
|
ini-values: pcov.directory=module
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v4
|
||||||
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: wget https://phar.phpunit.de/phpcov-9.0.0.phar
|
||||||
|
- run: php phpcov-9.0.0.phar merge build --clover build/clover.xml
|
||||||
- name: Publish coverage
|
- name: Publish coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with:
|
||||||
@@ -268,29 +162,14 @@ jobs:
|
|||||||
|
|
||||||
delete-artifacts:
|
delete-artifacts:
|
||||||
needs:
|
needs:
|
||||||
- mutation-tests
|
- unit-mutation-tests
|
||||||
|
- db-mutation-tests
|
||||||
|
- api-mutation-tests
|
||||||
|
- cli-mutation-tests
|
||||||
- upload-coverage
|
- upload-coverage
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.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-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
- name: Login to docker hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
- name: Build the image
|
|
||||||
run: bash ./docker/build
|
|
||||||
34
.github/workflows/publish-docker-image.yml
vendored
Normal file
34
.github/workflows/publish-docker-image.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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'
|
||||||
|
- runtime: 'openswoole'
|
||||||
|
tag-suffix: 'openswoole'
|
||||||
|
platforms: 'linux/arm/v7,linux/arm64/v8,linux/amd64'
|
||||||
|
- runtime: 'rr'
|
||||||
|
tag-suffix: 'non-root'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
user-id: '1001'
|
||||||
|
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
image-name: shlinkio/shlink
|
||||||
|
version-arg-name: SHLINK_VERSION
|
||||||
|
platforms: ${{ matrix.platforms }}
|
||||||
|
tags-suffix: ${{ matrix.tag-suffix }}
|
||||||
|
extra-build-args: |
|
||||||
|
SHLINK_RUNTIME=${{ matrix.runtime }}
|
||||||
|
SHLINK_USER_ID=${{ matrix.user-id && matrix.user-id || 'root' }}
|
||||||
34
.github/workflows/publish-release.yml
vendored
34
.github/workflows/publish-release.yml
vendored
@@ -7,36 +7,34 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.2', '8.3']
|
||||||
swoole: ['yes', 'no']
|
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
|
php-extensions: openswoole-22.1.0
|
||||||
extensions: swoole-4.6.7
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
|
install-deps: 'no'
|
||||||
- if: ${{ matrix.swoole == 'yes' }}
|
- if: ${{ matrix.swoole == 'yes' }}
|
||||||
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||||
- if: ${{ matrix.swoole == 'no' }}
|
- if: ${{ matrix.swoole == 'no' }}
|
||||||
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||||
path: build
|
path: build
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: ['build']
|
needs: ['build']
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.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 +48,8 @@ jobs:
|
|||||||
|
|
||||||
delete-artifacts:
|
delete-artifacts:
|
||||||
needs: ['publish']
|
needs: ['publish']
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: [ '8.0' ]
|
|
||||||
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-*
|
||||||
|
|||||||
36
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
36
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Publish swagger spec
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Determine version
|
||||||
|
id: determine_version
|
||||||
|
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-22.1.0
|
||||||
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
|
- run: composer swagger:inline
|
||||||
|
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
||||||
|
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
|
||||||
|
- name: Publish spec
|
||||||
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
||||||
|
repository-name: 'shlinkio/shlink-open-api-specs'
|
||||||
|
branch: main
|
||||||
|
folder: ${{ steps.determine_version.outputs.version }}
|
||||||
|
target-folder: specs/${{ steps.determine_version.outputs.version }}
|
||||||
|
clean: false
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
.idea
|
.idea
|
||||||
|
bin/rr
|
||||||
|
config/roadrunner/.pid
|
||||||
build
|
build
|
||||||
!docker/build
|
!docker/build
|
||||||
composer.lock
|
composer.lock
|
||||||
@@ -7,7 +9,9 @@ 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
|
docker-compose.override.yml
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
docs/swagger/swagger-inlined.json
|
||||||
|
|||||||
923
CHANGELOG.md
923
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
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ Then you will have to follow these steps:
|
|||||||
For example the `common.local.php.dist` file should be copied as `common.local.php`.
|
For example the `common.local.php.dist` file should be copied as `common.local.php`.
|
||||||
|
|
||||||
* Copy the file `docker-compose.override.yml.dist` by also removing the `dist` extension.
|
* Copy the file `docker-compose.override.yml.dist` by also removing the `dist` extension.
|
||||||
* 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 +31,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 swoole.
|
Once you finish this, you will have the project exposed in ports `8800` through RoadRunner, `8080` through openswoole 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.
|
||||||
|
|
||||||
@@ -47,18 +47,17 @@ This is a simplified version of the project structure:
|
|||||||
shlink
|
shlink
|
||||||
├── bin
|
├── bin
|
||||||
│ ├── cli
|
│ ├── cli
|
||||||
│ ├── install
|
│ └── [...]
|
||||||
│ └── update
|
|
||||||
├── 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
|
||||||
@@ -69,18 +68,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, while `install` and `update` are helper tools used to install and update shlink when not using the docker image.
|
* `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 subfolder 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 swoole.
|
* `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 or openswoole.
|
||||||
|
|
||||||
## Project tests
|
## Project tests
|
||||||
|
|
||||||
@@ -96,7 +96,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 swoole, 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 or openswoole, and test it from the outside by interacting with the REST API.
|
||||||
|
|
||||||
These are the best tests to catch regressions, and to verify everything behaves as expected.
|
These are the best tests to catch regressions, and to verify everything behaves as expected.
|
||||||
|
|
||||||
@@ -104,7 +104,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.
|
||||||
|
|
||||||
@@ -121,15 +123,15 @@ 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 test:cli` to run CLI E2E tests. For these, the Maria DB database engine is used.
|
||||||
* Run `./indocker composer infect:test` to run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
* Run `./indocker composer infect:test` to run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
||||||
* Run `./indocker composer ci` to run all previous commands together. 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.
|
|
||||||
|
|
||||||
> Note: Due to some limitations in the tooling used by shlink, the testing databases need to exist beforehand, both for db and api tests (except sqlite).
|
## Testing endpoints
|
||||||
>
|
|
||||||
> However, they just need to be created empty, with no tables. Also, once created, they are automatically reset before every new execution.
|
The project provides a Swagger UI container for dev envs, which can be accessed in http://localhost:8005.
|
||||||
>
|
|
||||||
> The testing database is always called `shlink_test`. You can create it using the database client of your choice. [DBeaver](https://dbeaver.io/) is a good multi-platform desktop database client which supports all the engines supported by shlink.
|
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
|
||||||
|
|
||||||
@@ -141,7 +143,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
|
||||||
|
|
||||||
|
|||||||
98
Dockerfile
98
Dockerfile
@@ -1,58 +1,60 @@
|
|||||||
FROM php:8.0.9-alpine3.14 as base
|
FROM php:8.2-alpine3.17 as base
|
||||||
|
|
||||||
ARG SHLINK_VERSION=latest
|
ARG SHLINK_VERSION=latest
|
||||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||||
ENV SWOOLE_VERSION 4.7.0
|
ARG SHLINK_RUNTIME=rr
|
||||||
ENV PDO_SQLSRV_VERSION 5.9.0
|
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.1
|
ARG SHLINK_USER_ID='root'
|
||||||
ENV LC_ALL "C"
|
ENV SHLINK_USER_ID ${SHLINK_USER_ID}
|
||||||
|
|
||||||
|
ENV OPENSWOOLE_VERSION 22.1.0
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
|
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 \
|
||||||
# Install mysql and calendar
|
# Temp install dev dependencies needed to compile the extensions
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar && \
|
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev linux-headers && \
|
||||||
# Install sqlite
|
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
||||||
apk add --no-cache sqlite-libs sqlite-dev && \
|
apk add --no-cache sqlite-libs && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||||
# Install postgres
|
# Remove temp dev extensions, and install prod equivalents that are required at runtime
|
||||||
apk add --no-cache postgresql-dev && \
|
apk del .dev-deps && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_pgsql && \
|
apk add --no-cache postgresql icu libzip libpng
|
||||||
# Install intl
|
|
||||||
apk add --no-cache icu-dev && \
|
|
||||||
docker-php-ext-install -j"$(nproc)" intl && \
|
|
||||||
# Install zip and gd
|
|
||||||
apk add --no-cache libzip-dev zlib-dev libpng-dev && \
|
|
||||||
docker-php-ext-install -j"$(nproc)" zip gd && \
|
|
||||||
# Install gmp
|
|
||||||
apk add --no-cache gmp-dev && \
|
|
||||||
docker-php-ext-install -j"$(nproc)" gmp
|
|
||||||
|
|
||||||
# Install sqlsrv driver
|
# Install openswoole and sqlsrv driver for x86_64 builds
|
||||||
RUN if [ $(uname -m) == "x86_64" ]; then \
|
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
||||||
wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||||
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
# Openswoole is deprecated. Remove in v4.0.0
|
||||||
apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
pecl install openswoole-${OPENSWOOLE_VERSION} && \
|
||||||
|
docker-php-ext-enable openswoole ; \
|
||||||
|
fi; \
|
||||||
|
if [ $(uname -m) == "x86_64" ]; then \
|
||||||
|
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 && \
|
||||||
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 && \
|
||||||
apk del .phpize-deps && \
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
|
||||||
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
|
fi; \
|
||||||
fi
|
|
||||||
|
|
||||||
# Install swoole
|
|
||||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
|
|
||||||
pecl install swoole-${SWOOLE_VERSION} && \
|
|
||||||
docker-php-ext-enable swoole && \
|
|
||||||
apk del .phpize-deps
|
apk del .phpize-deps
|
||||||
|
|
||||||
|
|
||||||
# Install shlink
|
# Install shlink
|
||||||
FROM base as builder
|
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 && \
|
# FIXME Ignoring ext-openswoole platform req, as it makes install fail with roadrunner, even though it's a dev dependency and we are passing --no-dev
|
||||||
|
php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction --ignore-platform-req=ext-openswoole && \
|
||||||
|
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||||
|
# Openswoole is deprecated. Remove in v4.0.0
|
||||||
|
php composer.phar remove spiral/roadrunner spiral/roadrunner-jobs spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction ; \
|
||||||
|
elif [ "$SHLINK_RUNTIME" == 'rr' ]; then \
|
||||||
|
php composer.phar remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction --ignore-platform-req=ext-openswoole ; \
|
||||||
|
fi; \
|
||||||
php composer.phar clear-cache && \
|
php composer.phar clear-cache && \
|
||||||
rm -r docker composer.* && \
|
rm -r docker composer.* && \
|
||||||
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
||||||
@@ -62,30 +64,20 @@ RUN apk add --no-cache git && \
|
|||||||
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=${SHLINK_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 swoole port
|
# Expose default port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Expose params config dir, since the user is expected to provide custom config from there
|
|
||||||
VOLUME /etc/shlink/config/params
|
|
||||||
# Expose data dir to allow persistent runtime data and SQLite db
|
|
||||||
VOLUME /etc/shlink/data
|
|
||||||
|
|
||||||
# Copy config specific for the image
|
# Copy config specific for the image
|
||||||
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
|
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
|
||||||
COPY docker/config/shlink_in_docker.local.php config/autoload/shlink_in_docker.local.php
|
COPY docker/config/shlink_in_docker.local.php config/autoload/shlink_in_docker.local.php
|
||||||
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
||||||
|
|
||||||
# Change the ownership of /etc/shlink/data to be writable, then change the user to non-root
|
USER ${SHLINK_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-2023 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
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -1,19 +1,22 @@
|
|||||||

|

|
||||||
|
|
||||||
[](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://slnk.to/donate)
|
[](https://slnk.to/donate)
|
||||||
|
|
||||||
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain.
|
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [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)
|
||||||
@@ -25,20 +28,22 @@ This document contains the very basics to get started with Shlink. If you want t
|
|||||||
|
|
||||||
## Docker image
|
## Docker image
|
||||||
|
|
||||||
Starting with version 1.15.0, an official docker image is provided. You can learn how to use it by reading [the docs](https://shlink.io/documentation/install-docker-image/).
|
You can learn how to use the official docker image by reading [the docs](https://shlink.io/documentation/install-docker-image/).
|
||||||
|
|
||||||
The idea is that you can just generate a container using the image and provide the custom config via env vars.
|
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
|
* PHP 8.2 or 8.3
|
||||||
* The next PHP extensions: json, curl, pdo, intl, gd and gmp.
|
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
||||||
* apcu extension is recommended if you don't plan to use swoole.
|
* apcu extension is recommended if you don't plan to use openswoole.
|
||||||
* xml extension is required if you want to generate QR codes in svg format.
|
* xml extension is required if you want to generate QR codes in svg format.
|
||||||
* MySQL, MariaDB, PostgreSQL, Microsoft SQL Server or SQLite.
|
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||||
* The web server of your choice with PHP integration (Apache or Nginx recommended).
|
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
|
||||||
|
* You will also need the corresponding pdo variation for the database you are planning to use: `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv` or `pdo_sqlite`.
|
||||||
|
* The [openswoole](https://openswoole.com/) PHP extension (if you plan to serve Shlink with openswoole) or the web server of your choice with PHP integration (like Apache or Nginx).
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
@@ -48,7 +53,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 swoole 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 and with/without openswoole integration.
|
||||||
|
|
||||||
Finally, decompress the file in the location of your choice.
|
Finally, decompress the file in the location of your choice.
|
||||||
|
|
||||||
@@ -58,11 +63,13 @@ In order to run Shlink, you will need a built version of the project. There are
|
|||||||
|
|
||||||
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
||||||
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
|
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
|
||||||
* Run `./build.sh 1.0.0`, replacing the version with the version number you are going to build (the version number is used as part of the generated dist file name, and to set the value returned when running `shlink -V` from the command line).
|
* Run `./build.sh 3.0.0`, replacing the version with the version number you are going to build (the version number is used as part of the generated dist file name, and to set the value returned when running `shlink -V` from the command line).
|
||||||
|
|
||||||
After that, you will have a 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
|
||||||
|
|
||||||
@@ -70,24 +77,24 @@ Despite how you built the project, you now need to configure it, by following th
|
|||||||
|
|
||||||
* If you are going to use MySQL, MariaDB, PostgreSQL or Microsoft SQL Server, create an empty database with the name of your choice.
|
* If you are going to use MySQL, MariaDB, PostgreSQL or Microsoft SQL Server, create an empty database with the name of your choice.
|
||||||
* Recursively grant write permissions to the `data` directory. Shlink uses it to cache some information.
|
* Recursively grant write permissions to the `data` directory. Shlink uses it to cache some information.
|
||||||
* Setup the application by running the `bin/install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
|
* Set up the application by running the `vendor/bin/shlink-installer install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
|
||||||
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with shlink's API.
|
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with Shlink's API.
|
||||||
|
|
||||||
## Using shlink
|
## Using shlink
|
||||||
|
|
||||||
Once shlink is installed, there are two main ways to interact with it:
|
Once shlink is installed, there are two main ways to interact with it:
|
||||||
|
|
||||||
* **The command line**. Try running `bin/cli` and see all the [available commands](#shlink-cli-help).
|
* **The command line**: Try running `bin/cli` to see all the available commands.
|
||||||
|
|
||||||
All of those commands can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
|
All of them can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
|
||||||
|
|
||||||
It is probably a good idea to symlink the CLI entry point (`bin/cli`) to somewhere in your path, so that you can run shlink from any directory.
|
It is probably a good idea to symlink the CLI entry point (`bin/cli`) to somewhere in your path, so that you can run shlink from any directory.
|
||||||
|
|
||||||
* **The REST API**. The complete docs on how to use the API can be found [here](https://shlink.io/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
|
* **The REST API**: The complete docs on how to use the API can be found [here](https://shlink.io/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
|
||||||
|
|
||||||
However, you probably don't want to consume the raw API yourself. That's why a nice [web client](https://github.com/shlinkio/shlink-web-client) is provided that can be directly used from [https://app.shlink.io](https://app.shlink.io), or hosted by yourself.
|
However, you probably don't want to consume the raw API yourself. That's why a nice [web client](https://github.com/shlinkio/shlink-web-client) is provided that can be directly used from [https://app.shlink.io](https://app.shlink.io), or hosted by yourself.
|
||||||
|
|
||||||
Both the API and CLI allow you to do the same operations, except for API key management, which can be done from the command line interface only.
|
Both the API and CLI allow you to do mostly the same operations, except for API key management, which can be done from the command line interface only.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
50
UPGRADE.md
50
UPGRADE.md
@@ -1,5 +1,53 @@
|
|||||||
# Upgrading
|
# Upgrading
|
||||||
|
|
||||||
|
## From v2.x to v3.x
|
||||||
|
|
||||||
|
### Changes in REST API
|
||||||
|
|
||||||
|
* The `type` property returned when trying to delete a URL that reached the visits threshold, when using the `DELETE /short-urls/{shortCode}` endpoint, is now `INVALID_SHORT_URL_DELETION` instead of `INVALID_SHORTCODE_DELETION`.
|
||||||
|
* The `INVALID_AUTHORIZATION` error no longer includes the `expectedTypes` property. Use `expectedHeaders` one instead.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint no longer allows ordering by `visitsCount`, `visitCount` or `originalUrl`. Use `visits` instead of the first two, and `longUrl` instead of the last one.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint no longer allows providing the ordering params with array notation, as in `/shortUrls?orderBy[longUrl]=DESC`. Instead, use the following notation `/shortUrls?orderBy=longUrl-DESC`.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint now has a default ordering of newest-to-oldest. Use `/shortUrls?orderBy=dateCreated-ASC` in order to keep the oldest-to-newest behavior.
|
||||||
|
* Requests expecting a body no longer support url-encoded payloads. Instead, always use JSON bodies with `Content-Type: application/json`.
|
||||||
|
* The next endpoints have been removed:
|
||||||
|
* `PUT /rest/v2/short-urls/{shortCode}/tags`: Use the `PATCH /rest/v2/short-urls/{shortCode}` endpoint to set the short URL tags.
|
||||||
|
* `POST /rest/v2/tags`: Use `POST /rest/v2/short-urls` or `PATCH /rest/v2/short-urls/{shortCodes}` to create new tags already attached to a short URL. Creating orphan tags makes no sense.
|
||||||
|
|
||||||
|
### Changes in CLI
|
||||||
|
|
||||||
|
* The next commands have been removed:
|
||||||
|
* `short-url:generate`: Use `short-url:create` instead.
|
||||||
|
* `tag:create`: Creating orphan tags makes no sense.
|
||||||
|
* Params in camelCase format are no longer supported. They all have an equivalent kebab-case replacement. (for example, from `--startDate` to `--start-date`).
|
||||||
|
* The `short-url:create` command no longer accepts the `--no-validate-url` flag. Now URLs are never validated, unless `--validate-url` is passed.
|
||||||
|
* The CLI installer tool entry-points have changed.
|
||||||
|
* `bin/install`: replaced by `vendor/bin/shlink-installer install`
|
||||||
|
* `bin/update`: replaced by `vendor/bin/shlink-installer update`
|
||||||
|
* `bin/set-option`: replaced by `vendor/bin/shlink-installer set-option`
|
||||||
|
|
||||||
|
### Changes in config
|
||||||
|
|
||||||
|
* The next env vars have been removed:
|
||||||
|
* `INVALID_SHORT_URL_REDIRECT_TO`: Replaced by `DEFAULT_INVALID_SHORT_URL_REDIRECT`.
|
||||||
|
* `REGULAR_404_REDIRECT_TO`: Replaced by `DEFAULT_REGULAR_404_REDIRECT`.
|
||||||
|
* `BASE_URL_REDIRECT_TO`: Replaced by `DEFAULT_BASE_URL_REDIRECT`.
|
||||||
|
* `SHORT_DOMAIN_HOST`: Replaced by `DEFAULT_DOMAIN`.
|
||||||
|
* `SHORT_DOMAIN_SCHEMA`: Replaced by `IS_HTTPS_ENABLED`.
|
||||||
|
* `USE_HTTPS`: Replaced by `IS_HTTPS_ENABLED`.
|
||||||
|
* `VALIDATE_URLS`: There's no replacement. URLs are not validated, unless explicitly requested during creation or edition.
|
||||||
|
* The next env vars behavior has changed:
|
||||||
|
* `DELETE_SHORT_URL_THRESHOLD`: Now, if this env var is not provided, the "visits threshold" won't be checked at all when deleting short URLs. Make sure you explicitly provide a value if you want to enable this feature.
|
||||||
|
* Environment variables now have precedence over configuration set via the installer tool.
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
* A default GeoLite2 license key is no longer provided. If you don't provide your own as explained in [the docs](https://shlink.io/documentation/geolite-license-key/), Shlink will not try to update the file anymore.
|
||||||
|
* The docker image no longer accepts providing configuration via json files mounted in the `config/params` folder. Only env vars are supported now.
|
||||||
|
* If you were manually serving Shlink with swoole, the entry script has to be changed from `/path/to/shlink/vendor/bin/mezzio-swoole start` to `/path/to/shlink/vendor/bin/laminas mezzio:swoole:start`
|
||||||
|
* The `GET /{shortCode}/qr-code/{size}` url has been removed. Use `GET /{shortCode}/qr-code?size={size}` instead.
|
||||||
|
* Regular swoole extension is no longer supported. Use openswoole instead, as a direct replacement. In most of the cases you just need to uninstall one and install the other, the rest is transparent.
|
||||||
|
|
||||||
## From v1.x to v2.x
|
## From v1.x to v2.x
|
||||||
|
|
||||||
### PHP 7.4 required
|
### PHP 7.4 required
|
||||||
@@ -28,7 +76,7 @@ These routes have been removed, but have a direct replacement:
|
|||||||
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
||||||
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
||||||
|
|
||||||
When using the old ones, a 404 status will me returned now.
|
When using the old ones, a 404 status will be returned now.
|
||||||
|
|
||||||
### Removed command and route aliases
|
### Removed command and route aliases
|
||||||
|
|
||||||
|
|||||||
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));
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated To be removed with Shlink 3.0.0
|
|
||||||
* This script is provided to keep backwards compatibility on how to run shlink with swoole while being still able to
|
|
||||||
* update to mezzio/mezzio-swoole 3.x
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Mezzio\Swoole\Command;
|
|
||||||
|
|
||||||
use Laminas\ServiceManager\ServiceManager;
|
|
||||||
use PackageVersions\Versions;
|
|
||||||
use Symfony\Component\Console\Application as CommandLine;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
|
||||||
|
|
||||||
use function explode;
|
|
||||||
use function Functional\filter;
|
|
||||||
use function str_starts_with;
|
|
||||||
use function strstr;
|
|
||||||
|
|
||||||
/** @var ServiceManager $container */
|
|
||||||
$container = require __DIR__ . '/../../config/container.php';
|
|
||||||
$version = strstr(Versions::getVersion('mezzio/mezzio-swoole'), '@', true);
|
|
||||||
$commandsPrefix = 'mezzio:swoole:';
|
|
||||||
$commands = filter(
|
|
||||||
$container->get('config')['laminas-cli']['commands'] ?? [],
|
|
||||||
fn ($c, string $command) => str_starts_with($command, $commandsPrefix),
|
|
||||||
);
|
|
||||||
$registeredCommands = [];
|
|
||||||
|
|
||||||
foreach ($commands as $newName => $commandServiceName) {
|
|
||||||
[, $oldName] = explode($commandsPrefix, $newName);
|
|
||||||
$registeredCommands[$oldName] = $commandServiceName;
|
|
||||||
|
|
||||||
$container->addDelegator($commandServiceName, static function ($c, $n, callable $factory) use ($oldName) {
|
|
||||||
/** @var Command $command */
|
|
||||||
$command = $factory();
|
|
||||||
$command->setAliases([$oldName]);
|
|
||||||
|
|
||||||
return $command;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$commandLine = new CommandLine('Mezzio web server', $version);
|
|
||||||
$commandLine->setAutoExit(true);
|
|
||||||
$commandLine->setCommandLoader(new ContainerCommandLoader($container, $registeredCommands));
|
|
||||||
$commandLine->run();
|
|
||||||
12
bin/install
12
bin/install
@@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[$install] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$install();
|
|
||||||
32
bin/roadrunner-worker.php
Normal file
32
bin/roadrunner-worker.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Mezzio\Application;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Shlinkio\Shlink\EventDispatcher\RoadRunner\RoadRunnerTaskConsumerToListener;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
|
||||||
|
(static function (): void {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
$rrMode = env('RR_MODE');
|
||||||
|
|
||||||
|
if ($rrMode === 'http') {
|
||||||
|
// This was spin-up as a web worker
|
||||||
|
$app = $container->get(Application::class);
|
||||||
|
$worker = $container->get(PSR7Worker::class);
|
||||||
|
|
||||||
|
while ($req = $worker->waitRequest()) {
|
||||||
|
try {
|
||||||
|
$worker->respond($app->handle($req));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$worker->getWorker()->error((string) $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Installer\Command\SetOptionCommand;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[,, $installer] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$installer(SetOptionCommand::NAME);
|
|
||||||
@@ -1,21 +1,38 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
export APP_ENV=test
|
|
||||||
export DB_DRIVER=postgres
|
|
||||||
export TEST_ENV=api
|
|
||||||
|
|
||||||
|
export APP_ENV=test
|
||||||
|
export TEST_ENV=api
|
||||||
|
export TEST_RUNTIME="${TEST_RUNTIME:-"openswoole"}" # Openswoole is deprecated. Remove in v4.0.0
|
||||||
|
export DB_DRIVER="${DB_DRIVER:-"postgres"}"
|
||||||
|
export GENERATE_COVERAGE="${GENERATE_COVERAGE:-"no"}"
|
||||||
|
|
||||||
|
# Reset logs
|
||||||
|
OUTPUT_LOGS=data/log/api-tests/output.log
|
||||||
rm -rf data/log/api-tests
|
rm -rf data/log/api-tests
|
||||||
|
mkdir data/log/api-tests
|
||||||
|
touch $OUTPUT_LOGS
|
||||||
|
|
||||||
# Try to stop server just in case it hanged in last execution
|
# Try to stop server just in case it hanged in last execution
|
||||||
vendor/bin/laminas mezzio:swoole:stop
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||||
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f
|
||||||
|
|
||||||
echo 'Starting server...'
|
echo 'Starting server...'
|
||||||
vendor/bin/laminas mezzio:swoole:start -d
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:start -d
|
||||||
sleep 2
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -c=config/roadrunner/.rr.dev.yml \
|
||||||
|
-o=http.address=0.0.0.0:9999 \
|
||||||
|
-o=logs.encoding=json \
|
||||||
|
-o=logs.channels.http.encoding=json \
|
||||||
|
-o=logs.channels.server.encoding=json \
|
||||||
|
-o=logs.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.http.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.server.output="${PWD}/${OUTPUT_LOGS}" &
|
||||||
|
sleep 2 # Let's give the server a couple of seconds to start
|
||||||
|
|
||||||
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 --colors=always --log-junit=build/coverage-api/junit.xml $*
|
||||||
testsExitCode=$?
|
testsExitCode=$?
|
||||||
|
|
||||||
vendor/bin/laminas mezzio:swoole:stop
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||||
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -c config/roadrunner/.rr.dev.yml -o=http.address=0.0.0.0:9999
|
||||||
|
|
||||||
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
||||||
exit $testsExitCode
|
exit $testsExitCode
|
||||||
|
|||||||
12
bin/update
12
bin/update
@@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[, $update] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$update();
|
|
||||||
16
build.sh
16
build.sh
@@ -10,7 +10,8 @@ fi
|
|||||||
version=$1
|
version=$1
|
||||||
noSwoole=$2
|
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="_swoole"
|
# Openswoole is deprecated. Remove in v4.0.0
|
||||||
|
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_openswoole"
|
||||||
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
||||||
builtContent="./build/${distId}"
|
builtContent="./build/${distId}"
|
||||||
projectdir=$(pwd)
|
projectdir=$(pwd)
|
||||||
@@ -24,28 +25,31 @@ 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"
|
# Deprecated. Do not ignore PHP platform req for Shlink v4.0.0
|
||||||
|
composerFlags="--optimize-autoloader --no-progress --no-interaction --ignore-platform-req=php+"
|
||||||
${composerBin} self-update
|
${composerBin} self-update
|
||||||
${composerBin} install --no-dev --prefer-dist $composerFlags
|
${composerBin} install --no-dev --prefer-dist $composerFlags
|
||||||
|
|
||||||
if [[ $noSwoole ]]; then
|
if [[ $noSwoole ]]; then
|
||||||
# If generating a dist not for swoole, uninstall mezzio-swoole
|
# If generating a dist not for openswoole, uninstall mezzio-swoole
|
||||||
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
|
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
|
||||||
else
|
else
|
||||||
# Copy mezzio helper script to vendor (deprecated - Remove with Shlink 3.0.0)
|
# Deprecated. Remove in Shlink v4.0.0
|
||||||
cp "${projectdir}/bin/helper/mezzio-swoole" "./vendor/bin"
|
# If generating a dist for openswoole, uninstall RoadRunner
|
||||||
|
${composerBin} remove spiral/roadrunner spiral/roadrunner-jobs spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies --update-no-dev $composerFlags
|
||||||
fi
|
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 in config
|
||||||
sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
||||||
|
|
||||||
# Compressing file
|
# Compressing file
|
||||||
|
|||||||
214
composer.json
214
composer.json
@@ -12,69 +12,75 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0",
|
"php": "^8.2",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"akrabat/ip-address-middleware": "^2.0",
|
"akrabat/ip-address-middleware": "^2.1",
|
||||||
"cakephp/chronos": "^2.2",
|
"cakephp/chronos": "^3.0.2",
|
||||||
"cocur/slugify": "^4.0",
|
"doctrine/migrations": "^3.6",
|
||||||
"doctrine/migrations": "^3.2",
|
"doctrine/orm": "^2.16",
|
||||||
"doctrine/orm": "^2.9",
|
"endroid/qr-code": "^4.8",
|
||||||
"endroid/qr-code": "^4.2",
|
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||||
"geoip2/geoip2": "^2.11",
|
"geoip2/geoip2": "^3.0",
|
||||||
"guzzlehttp/guzzle": "^7.3",
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
"happyr/doctrine-specification": "^2.0",
|
"happyr/doctrine-specification": "^2.0",
|
||||||
"jaybizzle/crawler-detect": "^1.2",
|
"jaybizzle/crawler-detect": "^1.2.116",
|
||||||
"laminas/laminas-config": "^3.5",
|
"laminas/laminas-config": "^3.8",
|
||||||
"laminas/laminas-config-aggregator": "^1.5",
|
"laminas/laminas-config-aggregator": "^1.13",
|
||||||
"laminas/laminas-diactoros": "^2.6",
|
"laminas/laminas-diactoros": "^3.3",
|
||||||
"laminas/laminas-inputfilter": "^2.12",
|
"laminas/laminas-inputfilter": "^2.27",
|
||||||
"laminas/laminas-servicemanager": "^3.7",
|
"laminas/laminas-servicemanager": "^3.21",
|
||||||
"laminas/laminas-stdlib": "^3.5",
|
"laminas/laminas-stdlib": "^3.17",
|
||||||
"lcobucci/jwt": "^4.1",
|
"league/uri": "^6.8",
|
||||||
"league/uri": "^6.4",
|
"matomo/matomo-php-tracker": "^3.2",
|
||||||
"lstrojny/functional-php": "^1.17",
|
"mezzio/mezzio": "^3.17",
|
||||||
"mezzio/mezzio": "^3.5",
|
"mezzio/mezzio-fastroute": "^3.10",
|
||||||
"mezzio/mezzio-fastroute": "^3.2",
|
"mezzio/mezzio-problem-details": "^1.13",
|
||||||
"mezzio/mezzio-problem-details": "^1.4",
|
"mezzio/mezzio-swoole": "^4.7",
|
||||||
"mezzio/mezzio-swoole": "^3.3",
|
"mlocati/ip-lib": "^1.18",
|
||||||
"monolog/monolog": "^2.3",
|
"mobiledetect/mobiledetectlib": "^4.8",
|
||||||
"nikolaposa/monolog-factory": "^3.1",
|
"pagerfanta/core": "^3.8",
|
||||||
"ocramius/proxy-manager": "^2.11",
|
|
||||||
"pagerfanta/core": "^2.7",
|
|
||||||
"php-middleware/request-id": "^4.1",
|
"php-middleware/request-id": "^4.1",
|
||||||
"predis/predis": "^1.1",
|
"pugx/shortid-php": "^1.1",
|
||||||
"pugx/shortid-php": "^0.7",
|
"ramsey/uuid": "^4.7",
|
||||||
"ramsey/uuid": "^3.9",
|
"shlinkio/shlink-common": "^5.7.1",
|
||||||
"shlinkio/shlink-common": "dev-main#2832a4a as 4.0",
|
"shlinkio/shlink-config": "^2.5",
|
||||||
"shlinkio/shlink-config": "^1.2",
|
"shlinkio/shlink-event-dispatcher": "^3.1",
|
||||||
"shlinkio/shlink-event-dispatcher": "^2.1",
|
"shlinkio/shlink-importer": "^5.2.1",
|
||||||
"shlinkio/shlink-importer": "^2.3.1",
|
"shlinkio/shlink-installer": "^8.7",
|
||||||
"shlinkio/shlink-installer": "^6.1",
|
"shlinkio/shlink-ip-geolocation": "^4.0",
|
||||||
"shlinkio/shlink-ip-geolocation": "^2.0",
|
"shlinkio/shlink-json": "^1.1",
|
||||||
"symfony/console": "^5.3",
|
"spiral/roadrunner": "^2023.2",
|
||||||
"symfony/filesystem": "^5.3",
|
"spiral/roadrunner-cli": "^2.5",
|
||||||
"symfony/lock": "^5.3",
|
"spiral/roadrunner-http": "^3.1",
|
||||||
"symfony/mercure": "^0.5.3",
|
"spiral/roadrunner-jobs": "^4.0",
|
||||||
"symfony/process": "^5.3",
|
"symfony/console": "^6.3",
|
||||||
"symfony/string": "^5.3"
|
"symfony/filesystem": "^6.3",
|
||||||
|
"symfony/lock": "^6.3",
|
||||||
|
"symfony/process": "^6.3",
|
||||||
|
"symfony/string": "^6.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"devizzent/cebe-php-openapi": "^1.0.1",
|
||||||
"devster/ubench": "^2.1",
|
"devster/ubench": "^2.1",
|
||||||
"dms/phpunit-arraysubset-asserts": "^0.3.0",
|
"infection/infection": "^0.27",
|
||||||
"eaglewu/swoole-ide-helper": "dev-master",
|
"openswoole/ide-helper": "~22.0.0",
|
||||||
"infection/infection": "^0.24.0",
|
"phpstan/phpstan": "^1.10",
|
||||||
"phpspec/prophecy-phpunit": "^2.0",
|
"phpstan/phpstan-doctrine": "^1.3",
|
||||||
"phpstan/phpstan": "^0.12.94",
|
"phpstan/phpstan-phpunit": "^1.3",
|
||||||
"phpstan/phpstan-doctrine": "^0.12.42",
|
"phpstan/phpstan-symfony": "^1.3",
|
||||||
"phpstan/phpstan-symfony": "^0.12.41",
|
"phpunit/php-code-coverage": "^10.1",
|
||||||
"phpunit/php-code-coverage": "^9.2",
|
"phpunit/phpunit": "^10.4",
|
||||||
"phpunit/phpunit": "^9.5",
|
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"shlinkio/php-coding-standard": "~2.1.1",
|
"shlinkio/php-coding-standard": "~2.3.0",
|
||||||
"shlinkio/shlink-test-utils": "^2.2",
|
"shlinkio/shlink-test-utils": "^3.8.1",
|
||||||
"symfony/var-dumper": "^5.3",
|
"symfony/var-dumper": "^6.3",
|
||||||
"veewee/composer-run-parallel": "^1.0"
|
"veewee/composer-run-parallel": "^1.3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/var-exporter": ">=6.3.9,<=6.4.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -83,18 +89,21 @@
|
|||||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"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",
|
||||||
"ShlinkioTest\\Shlink\\Core\\": [
|
"ShlinkioDbTest\\Shlink\\Rest\\": "module/Rest/test-db",
|
||||||
"module/Core/test",
|
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
||||||
"module/Core/test-db"
|
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db",
|
||||||
]
|
"ShlinkioApiTest\\Shlink\\Core\\": "module/Core/test-api"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/test/constants.php"
|
"config/test/constants.php"
|
||||||
@@ -102,31 +111,19 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ci": [
|
"ci": [
|
||||||
"@cs",
|
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||||
"@stan",
|
"@parallel infect:test:api infect:test:cli infect:ci:unit infect:ci:db"
|
||||||
"@test:ci",
|
|
||||||
"@infect:ci"
|
|
||||||
],
|
],
|
||||||
"ci:parallel": [
|
"cs": "phpcs -s",
|
||||||
"@parallel cs stan test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
|
||||||
"@parallel 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": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test* module/*/config module/*/migrations config docker/config --level=8",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:unit",
|
"@parallel test:unit test:db",
|
||||||
"@test:db",
|
"@parallel test:api test:cli"
|
||||||
"@test:api"
|
|
||||||
],
|
],
|
||||||
"test:ci": [
|
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --testdox",
|
||||||
"@test:unit:ci",
|
"test:unit:ci": "@test:unit --coverage-php=build/coverage-unit.cov --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
||||||
"@test:db",
|
"test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
|
||||||
"@test:api"
|
|
||||||
],
|
|
||||||
"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": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit-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": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
||||||
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
"test:db: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",
|
||||||
@@ -135,43 +132,80 @@
|
|||||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
||||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
||||||
"test:api": "bin/test/run-api-tests.sh",
|
"test:api": "bin/test/run-api-tests.sh",
|
||||||
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --only-covering-test-cases --skip-initial-tests",
|
"test:api:rr": "TEST_RUNTIME=rr bin/test/run-api-tests.sh",
|
||||||
|
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
|
||||||
|
"test:api:pretty": "GENERATE_COVERAGE=pretty composer test:api",
|
||||||
|
"test:cli": "APP_ENV=test DB_DRIVER=maria TEST_ENV=cli php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-cli.xml --log-junit=build/coverage-cli/junit.xml",
|
||||||
|
"test:cli:ci": "GENERATE_COVERAGE=yes composer test:cli",
|
||||||
|
"test:cli:pretty": "GENERATE_COVERAGE=pretty composer test:cli",
|
||||||
|
"infect:ci:base": "infection --threads=max --only-covered --skip-initial-tests",
|
||||||
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
||||||
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
|
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5",
|
||||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db",
|
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=95 --configuration=infection-api.json5",
|
||||||
|
"infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=90 --configuration=infection-cli.json5",
|
||||||
|
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli",
|
||||||
"infect:test": [
|
"infect:test": [
|
||||||
"@parallel test:unit:ci test:db:sqlite:ci",
|
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
||||||
"@infect:ci"
|
"@infect:ci"
|
||||||
],
|
],
|
||||||
|
"infect:test:unit": [
|
||||||
|
"@test:unit:ci",
|
||||||
|
"@infect:ci:unit"
|
||||||
|
],
|
||||||
|
"infect:test:db": [
|
||||||
|
"@test:db:sqlite:ci",
|
||||||
|
"@infect:ci:db"
|
||||||
|
],
|
||||||
|
"infect:test:api": [
|
||||||
|
"@test:api:ci",
|
||||||
|
"@infect:ci:api"
|
||||||
|
],
|
||||||
|
"infect:test:cli": [
|
||||||
|
"@test:cli:ci",
|
||||||
|
"@infect:ci:cli"
|
||||||
|
],
|
||||||
|
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
||||||
|
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
||||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||||
},
|
},
|
||||||
"scripts-descriptions": {
|
"scripts-descriptions": {
|
||||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"</>",
|
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
|
||||||
"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:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB, PostgreSQL and MsSQL</>",
|
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB, PostgreSQL and MsSQL</>",
|
||||||
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
||||||
"test:db:sqlite:ci": "<fg=blue;options=bold>Runs database test suites on a SQLite database, generating all needed reports and logs for CI envs</>",
|
"test:db:sqlite:ci": "<fg=blue;options=bold>Runs database test suites on a SQLite database, generating all needed reports and logs for CI envs</>",
|
||||||
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
||||||
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
|
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
|
||||||
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
||||||
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a 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:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage for CI</>",
|
||||||
|
"test:api:pretty": "<fg=blue;options=bold>Runs API test suites, and generates code coverage in HTML format</>",
|
||||||
|
"test:cli": "<fg=blue;options=bold>Runs CLI test suites</>",
|
||||||
|
"test:cli:ci": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage for CI</>",
|
||||||
|
"test:cli:pretty": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage in HTML format</>",
|
||||||
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
||||||
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
||||||
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
||||||
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
||||||
|
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
||||||
|
"swagger:inline": "<fg=blue;options=bold>Inlines swagger docs in a single file</>",
|
||||||
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"platform-check": false
|
"platform-check": false,
|
||||||
|
"allow-plugins": {
|
||||||
|
"composer/package-versions-deprecated": true,
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
|
"infection/extension-installer": true,
|
||||||
|
"veewee/composer-run-parallel": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
config/autoload/app_options.local.php.dist
Normal file
11
config/autoload/app_options.local.php.dist
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'app_options' => [
|
||||||
|
'version' => 'latest',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
25
config/autoload/cache.global.php
Normal file
25
config/autoload/cache.global.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?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(false)];
|
||||||
|
$cacheRedisBlock = $redisServers === null ? [] : [
|
||||||
|
'redis' => [
|
||||||
|
'servers' => $redisServers,
|
||||||
|
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||||
|
'decode_credentials' => (bool) EnvVars::REDIS_DECODE_CREDENTIALS->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'cache' => [
|
||||||
|
'namespace' => EnvVars::CACHE_NAMESPACE->loadFromEnv('Shlink'),
|
||||||
|
...$cacheRedisBlock,
|
||||||
|
],
|
||||||
|
'redis' => $redis,
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -8,8 +8,8 @@ return [
|
|||||||
|
|
||||||
'debug' => false,
|
'debug' => false,
|
||||||
|
|
||||||
// Disabling config cache for cli, ensures it's never used for swoole and also that console commands don't generate
|
// Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console
|
||||||
// a cache file that's then used by non-swoole web executions
|
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||||
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
return [
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
'delete_short_urls' => [
|
return (static function (): array {
|
||||||
'visits_threshold' => 15,
|
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD->loadFromEnv();
|
||||||
'check_visits_threshold' => true,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
return [
|
||||||
|
|
||||||
|
'delete_short_urls' => [
|
||||||
|
'check_visits_threshold' => $threshold !== null,
|
||||||
|
'visits_threshold' => (int) ($threshold ?? DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,14 +3,25 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
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 Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
use Spiral\RoadRunner\WorkerInterface;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
PSR7Worker::class => ConfigAbstractFactory::class,
|
||||||
|
],
|
||||||
|
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Mezzio\Application::class => [
|
Application::class => [
|
||||||
Container\ApplicationConfigInjectionDelegator::class,
|
Container\ApplicationConfigInjectionDelegator::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -26,4 +37,13 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
PSR7Worker::class => [
|
||||||
|
WorkerInterface::class,
|
||||||
|
ServerRequestFactoryInterface::class,
|
||||||
|
StreamFactoryInterface::class,
|
||||||
|
UploadedFileFactoryInterface::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,24 +2,62 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Common;
|
|
||||||
|
|
||||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||||
|
|
||||||
'entity_manager' => [
|
return (static function (): array {
|
||||||
'orm' => [
|
$driver = EnvVars::DB_DRIVER->loadFromEnv();
|
||||||
'proxies_dir' => 'data/proxies',
|
$isMysqlCompatible = contains($driver, ['maria', 'mysql']);
|
||||||
'load_mappings_using_functional_style' => true,
|
|
||||||
'default_repository_classname' => EntitySpecificationRepository::class,
|
$resolveDriver = static fn () => match ($driver) {
|
||||||
|
'postgres' => 'pdo_pgsql',
|
||||||
|
'mssql' => 'pdo_sqlsrv',
|
||||||
|
default => 'pdo_mysql',
|
||||||
|
};
|
||||||
|
$resolveDefaultPort = static fn () => match ($driver) {
|
||||||
|
'postgres' => '5432',
|
||||||
|
'mssql' => '1433',
|
||||||
|
default => '3306',
|
||||||
|
};
|
||||||
|
$resolveCharset = static fn () => match ($driver) {
|
||||||
|
// This does not determine charsets or collations in tables or columns, but the charset used in the data
|
||||||
|
// flowing in the connection, so it has to match what has been set in the database.
|
||||||
|
'maria', 'mysql' => 'utf8mb4',
|
||||||
|
'postgres' => 'utf8',
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
$resolveConnection = static fn () => match ($driver) {
|
||||||
|
null, 'sqlite' => [
|
||||||
|
'driver' => 'pdo_sqlite',
|
||||||
|
'path' => 'data/database.sqlite',
|
||||||
],
|
],
|
||||||
'connection' => [
|
default => [
|
||||||
'user' => '',
|
'driver' => $resolveDriver(),
|
||||||
'password' => '',
|
'dbname' => EnvVars::DB_NAME->loadFromEnv('shlink'),
|
||||||
'dbname' => 'shlink',
|
'user' => EnvVars::DB_USER->loadFromEnv(),
|
||||||
'charset' => 'utf8',
|
'password' => EnvVars::DB_PASSWORD->loadFromEnv(),
|
||||||
|
'host' => EnvVars::DB_HOST->loadFromEnv(EnvVars::DB_UNIX_SOCKET->loadFromEnv()),
|
||||||
|
'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
|
||||||
|
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
|
||||||
|
'charset' => $resolveCharset(),
|
||||||
|
'driverOptions' => $driver !== 'mssql' ? [] : [
|
||||||
|
'TrustServerCertificate' => 'true',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
};
|
||||||
|
|
||||||
];
|
return [
|
||||||
|
|
||||||
|
'entity_manager' => [
|
||||||
|
'orm' => [
|
||||||
|
'proxies_dir' => 'data/proxies',
|
||||||
|
'load_mappings_using_functional_style' => true,
|
||||||
|
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||||
|
],
|
||||||
|
'connection' => $resolveConnection(),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -6,10 +6,40 @@ return [
|
|||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
'connection' => [
|
'connection' => [
|
||||||
|
// MySQL
|
||||||
'user' => 'root',
|
'user' => 'root',
|
||||||
'password' => 'root',
|
'password' => 'root',
|
||||||
'driver' => 'pdo_mysql',
|
'driver' => 'pdo_mysql',
|
||||||
'host' => 'shlink_db',
|
'host' => 'shlink_db_mysql',
|
||||||
|
'dbname' => 'shlink',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
|
||||||
|
// MariaDB
|
||||||
|
// 'user' => 'root',
|
||||||
|
// 'password' => 'root',
|
||||||
|
// 'driver' => 'pdo_mysql',
|
||||||
|
// 'host' => 'shlink_db_maria',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
// 'charset' => 'utf8mb4',
|
||||||
|
|
||||||
|
// Postgres
|
||||||
|
// 'user' => 'postgres',
|
||||||
|
// 'password' => 'root',
|
||||||
|
// 'driver' => 'pdo_pgsql',
|
||||||
|
// 'host' => 'shlink_db_postgres',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
// 'charset' => 'utf8',
|
||||||
|
|
||||||
|
// MSSQL
|
||||||
|
// 'user' => 'sa',
|
||||||
|
// 'password' => 'Passw0rd!',
|
||||||
|
// 'driver' => 'pdo_sqlsrv',
|
||||||
|
// 'host' => 'shlink_db_ms',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
// 'driverOptions' => [
|
||||||
|
// 'TrustServerCertificate' => 'true',
|
||||||
|
// ],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ use Laminas\Stratigility\Middleware\ErrorHandler;
|
|||||||
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
|
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
|
||||||
use Shlinkio\Shlink\Common\Logger;
|
use Shlinkio\Shlink\Common\Logger;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Core\toProblemDetailsType;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'problem-details' => [
|
'problem-details' => [
|
||||||
'default_types_map' => [
|
'default_types_map' => [
|
||||||
404 => 'NOT_FOUND',
|
404 => toProblemDetailsType('not-found'),
|
||||||
500 => 'INTERNAL_SERVER_ERROR',
|
500 => toProblemDetailsType('internal-server-error'),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'geolite2' => [
|
'geolite2' => [
|
||||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||||
'temp_dir' => __DIR__ . '/../../data',
|
'temp_dir' => __DIR__ . '/../../data',
|
||||||
'license_key' => 'G4Lm0C60yJsnkdPi', // Deprecated. Remove hardcoded license on v3
|
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ return [
|
|||||||
|
|
||||||
'installer' => [
|
'installer' => [
|
||||||
'enabled_options' => [
|
'enabled_options' => [
|
||||||
|
Option\Server\RuntimeConfigOption::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,
|
||||||
@@ -18,21 +19,23 @@ return [
|
|||||||
Option\Database\DatabaseUserConfigOption::class,
|
Option\Database\DatabaseUserConfigOption::class,
|
||||||
Option\Database\DatabasePasswordConfigOption::class,
|
Option\Database\DatabasePasswordConfigOption::class,
|
||||||
Option\Database\DatabaseUnixSocketConfigOption::class,
|
Option\Database\DatabaseUnixSocketConfigOption::class,
|
||||||
Option\Database\DatabaseSqlitePathConfigOption::class,
|
|
||||||
Option\Database\DatabaseMySqlOptionsConfigOption::class,
|
|
||||||
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
||||||
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
||||||
Option\UrlShortener\ValidateUrlConfigOption::class,
|
|
||||||
Option\Visit\VisitsWebhooksConfigOption::class,
|
Option\Visit\VisitsWebhooksConfigOption::class,
|
||||||
|
Option\Visit\OrphanVisitsWebhooksConfigOption::class,
|
||||||
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
||||||
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
||||||
Option\Redirect\Regular404RedirectConfigOption::class,
|
Option\Redirect\Regular404RedirectConfigOption::class,
|
||||||
Option\Visit\CheckVisitsThresholdConfigOption::class,
|
|
||||||
Option\Visit\VisitsThresholdConfigOption::class,
|
Option\Visit\VisitsThresholdConfigOption::class,
|
||||||
Option\BasePathConfigOption::class,
|
Option\BasePathConfigOption::class,
|
||||||
|
Option\TimezoneConfigOption::class,
|
||||||
|
Option\Cache\CacheNamespaceConfigOption::class,
|
||||||
Option\Worker\TaskWorkerNumConfigOption::class,
|
Option\Worker\TaskWorkerNumConfigOption::class,
|
||||||
Option\Worker\WebWorkerNumConfigOption::class,
|
Option\Worker\WebWorkerNumConfigOption::class,
|
||||||
Option\RedisServersConfigOption::class,
|
Option\Redis\RedisServersConfigOption::class,
|
||||||
|
Option\Redis\RedisDecodeCredentialsConfigOption::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,
|
||||||
@@ -43,25 +46,58 @@ 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\Tracking\IpAnonymizationConfigOption::class,
|
Option\Tracking\IpAnonymizationConfigOption::class,
|
||||||
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
||||||
Option\Tracking\DisableTrackParamConfigOption::class,
|
Option\Tracking\DisableTrackParamConfigOption::class,
|
||||||
|
Option\Tracking\DisableTrackingFromConfigOption::class,
|
||||||
Option\Tracking\DisableTrackingConfigOption::class,
|
Option\Tracking\DisableTrackingConfigOption::class,
|
||||||
Option\Tracking\DisableIpTrackingConfigOption::class,
|
Option\Tracking\DisableIpTrackingConfigOption::class,
|
||||||
Option\Tracking\DisableReferrerTrackingConfigOption::class,
|
Option\Tracking\DisableReferrerTrackingConfigOption::class,
|
||||||
Option\Tracking\DisableUaTrackingConfigOption::class,
|
Option\Tracking\DisableUaTrackingConfigOption::class,
|
||||||
|
Option\QrCode\DefaultSizeConfigOption::class,
|
||||||
|
Option\QrCode\DefaultMarginConfigOption::class,
|
||||||
|
Option\QrCode\DefaultFormatConfigOption::class,
|
||||||
|
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
||||||
|
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
||||||
|
Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqUseSslConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqPasswordConfigOption::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,12 +3,13 @@
|
|||||||
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\RetryLockStoreDelegatorFactory;
|
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 Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -22,18 +23,15 @@ 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' => [
|
||||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
||||||
'lock_store' => 'local_lock_store',
|
|
||||||
|
|
||||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
'redis_lock_store' => NamespacedStore::class,
|
||||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||||
],
|
],
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Lock\Store\RedisStore::class => [
|
|
||||||
RetryLockStoreDelegatorFactory::class,
|
|
||||||
],
|
|
||||||
Lock\LockFactory::class => [
|
Lock\LockFactory::class => [
|
||||||
LoggerAwareDelegatorFactory::class,
|
LoggerAwareDelegatorFactory::class,
|
||||||
],
|
],
|
||||||
@@ -42,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,64 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Formatter;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use Monolog\Handler;
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Monolog\Processor;
|
|
||||||
use MonologFactory\DiContainerLoggerFactory;
|
|
||||||
use PhpMiddleware\RequestId;
|
use PhpMiddleware\RequestId;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||||
|
|
||||||
use const PHP_EOL;
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
|
||||||
$processors = [
|
return (static function (): array {
|
||||||
'exception_with_new_line' => [
|
$common = [
|
||||||
'name' => Common\Logger\Processor\ExceptionWithNewLineProcessor::class,
|
'level' => Level::Info->value,
|
||||||
],
|
'processors' => [RequestId\MonologProcessor::class],
|
||||||
'psr3' => [
|
'line_format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%',
|
||||||
'name' => Processor\PsrLogMessageProcessor::class,
|
];
|
||||||
],
|
|
||||||
'request_id' => RequestId\MonologProcessor::class,
|
|
||||||
];
|
|
||||||
$formatter = [
|
|
||||||
'name' => Formatter\LineFormatter::class,
|
|
||||||
'params' => [
|
|
||||||
'format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%' . PHP_EOL,
|
|
||||||
'allow_inline_line_breaks' => true,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'name' => 'Shlink',
|
'type' => LoggerType::FILE->value,
|
||||||
'handlers' => [
|
...$common,
|
||||||
'shlink_handler' => [
|
],
|
||||||
'name' => Handler\RotatingFileHandler::class,
|
'Access' => [
|
||||||
'params' => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'level' => Logger::INFO,
|
'destination' => 'php://stderr',
|
||||||
'filename' => 'data/log/shlink_log.log',
|
'add_new_line' => ! runningInRoadRunner(),
|
||||||
'max_files' => 30,
|
...$common,
|
||||||
'file_permission' => 0666,
|
],
|
||||||
],
|
],
|
||||||
'formatter' => $formatter,
|
|
||||||
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
'Logger_Shlink' => [LoggerFactory::class, 'Shlink'],
|
||||||
|
'Logger_Access' => [LoggerFactory::class, 'Access'],
|
||||||
|
NullLogger::class => InvokableFactory::class,
|
||||||
|
],
|
||||||
|
'aliases' => [
|
||||||
|
'logger' => 'Logger_Shlink',
|
||||||
|
Logger::class => 'Logger_Shlink',
|
||||||
|
LoggerInterface::class => 'Logger_Shlink',
|
||||||
|
AccessLogMiddleware::LOGGER_SERVICE_NAME => 'Logger_Access',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Deprecated. Remove in Shlink 4.0.0
|
||||||
|
'mezzio-swoole' => [
|
||||||
|
'swoole-http-server' => [
|
||||||
|
'logger' => [
|
||||||
|
// Let's disable mezio-swoole access logging, so that we can provide our own implementation,
|
||||||
|
// consistent for roadrunner and openswoole
|
||||||
|
'logger-name' => NullLogger::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'processors' => $processors,
|
|
||||||
],
|
],
|
||||||
'Access' => [
|
|
||||||
'name' => 'Access',
|
|
||||||
'handlers' => [
|
|
||||||
'access_handler' => [
|
|
||||||
'name' => Handler\StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
'formatter' => $formatter,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'processors' => $processors,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
];
|
||||||
'factories' => [
|
})();
|
||||||
'Logger_Shlink' => [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' => '%h %l %u "%r" %>s %b',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -2,33 +2,16 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
|
||||||
$isSwoole = extension_loaded('swoole');
|
|
||||||
|
|
||||||
// For swoole, send logs to standard output
|
|
||||||
$handler = $isSwoole
|
|
||||||
? [
|
|
||||||
'name' => StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'handlers' => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'shlink_handler' => $handler,
|
'destination' => 'php://stderr',
|
||||||
],
|
'level' => Level::Debug->value,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
16
config/autoload/matomo.global.php
Normal file
16
config/autoload/matomo.global.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'matomo' => [
|
||||||
|
'enabled' => (bool) EnvVars::MATOMO_ENABLED->loadFromEnv(false),
|
||||||
|
'base_url' => EnvVars::MATOMO_BASE_URL->loadFromEnv(),
|
||||||
|
'site_id' => EnvVars::MATOMO_SITE_ID->loadFromEnv(),
|
||||||
|
'api_token' => EnvVars::MATOMO_API_TOKEN->loadFromEnv(),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
26
config/autoload/matomo.local.php.dist
Normal file
26
config/autoload/matomo.local.php.dist
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 `site_id` field 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 `api_token` field below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'matomo' => [
|
||||||
|
// 'enabled' => true,
|
||||||
|
// 'base_url' => 'http://shlink_matomo',
|
||||||
|
// 'site_id' => '...',
|
||||||
|
// 'api_token' => '...',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -4,33 +4,38 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
||||||
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
||||||
|
use 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 [
|
return (static function (): array {
|
||||||
|
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv();
|
||||||
|
|
||||||
'mercure' => [
|
return [
|
||||||
'public_hub_url' => null,
|
|
||||||
'internal_hub_url' => null,
|
|
||||||
'jwt_secret' => null,
|
|
||||||
'jwt_issuer' => 'Shlink',
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
'mercure' => [
|
||||||
'delegators' => [
|
'public_hub_url' => $publicUrl,
|
||||||
LcobucciJwtProvider::class => [
|
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv($publicUrl),
|
||||||
LazyServiceFactory::class,
|
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
|
||||||
|
'jwt_issuer' => 'Shlink',
|
||||||
|
],
|
||||||
|
|
||||||
|
'dependencies' => [
|
||||||
|
'delegators' => [
|
||||||
|
LcobucciJwtProvider::class => [
|
||||||
|
LazyServiceFactory::class,
|
||||||
|
],
|
||||||
|
Hub::class => [
|
||||||
|
LazyServiceFactory::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
Hub::class => [
|
'lazy_services' => [
|
||||||
LazyServiceFactory::class,
|
'class_map' => [
|
||||||
|
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
||||||
|
Hub::class => HubInterface::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'lazy_services' => [
|
|
||||||
'class_map' => [
|
|
||||||
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
|
||||||
Hub::class => HubInterface::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => 'http://localhost:8001',
|
'public_hub_url' => 'http://localhost:8001',
|
||||||
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
||||||
'jwt_secret' => 'mercure_jwt_key',
|
'jwt_secret' => 'mercure_jwt_key_long_enough_to_avoid_error',
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Mezzio\ProblemDetails;
|
|||||||
use Mezzio\Router;
|
use Mezzio\Router;
|
||||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
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;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -16,15 +17,16 @@ return [
|
|||||||
'middleware_pipeline' => [
|
'middleware_pipeline' => [
|
||||||
'error-handler' => [
|
'error-handler' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
AccessLogMiddleware::class,
|
||||||
ContentLengthMiddleware::class,
|
ContentLengthMiddleware::class,
|
||||||
|
RequestIdMiddleware::class,
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
|
Rest\Middleware\CrossDomainMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'error-handler-rest' => [
|
'error-handler-rest' => [
|
||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Rest\Middleware\CrossDomainMiddleware::class,
|
|
||||||
RequestIdMiddleware::class,
|
|
||||||
ProblemDetails\ProblemDetailsMiddleware::class,
|
ProblemDetails\ProblemDetailsMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -45,6 +47,7 @@ return [
|
|||||||
'rest' => [
|
'rest' => [
|
||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
|
||||||
Router\Middleware\ImplicitOptionsMiddleware::class,
|
Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||||
Rest\Middleware\BodyParserMiddleware::class,
|
Rest\Middleware\BodyParserMiddleware::class,
|
||||||
Rest\Middleware\AuthenticationMiddleware::class,
|
Rest\Middleware\AuthenticationMiddleware::class,
|
||||||
|
|||||||
31
config/autoload/qr-codes.global.php
Normal file
31
config/autoload/qr-codes.global.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS;
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
'enabled_for_disabled_short_urls' => (bool) EnvVars::QR_CODE_FOR_DISABLED_SHORT_URLS->loadFromEnv(
|
||||||
|
DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
22
config/autoload/rabbit.global.php
Normal file
22
config/autoload/rabbit.global.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'rabbitmq' => [
|
||||||
|
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(false),
|
||||||
|
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
|
||||||
|
'use_ssl' => (bool) EnvVars::RABBITMQ_USE_SSL->loadFromEnv(false),
|
||||||
|
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv('5672'),
|
||||||
|
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
|
||||||
|
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||||
|
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
'legacy_visits_publishing' => (bool) EnvVars::RABBITMQ_LEGACY_VISITS_PUBLISHING->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
14
config/autoload/rabbit.local.php.dist
Normal file
14
config/autoload/rabbit.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'rabbitmq' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'host' => 'shlink_rabbitmq',
|
||||||
|
'user' => 'rabbit',
|
||||||
|
'password' => 'rabbit',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -2,12 +2,24 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'not_found_redirects' => [
|
'not_found_redirects' => [
|
||||||
'invalid_short_url' => null,
|
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT->loadFromEnv(),
|
||||||
'regular_404' => null,
|
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT->loadFromEnv(),
|
||||||
'base_url' => null,
|
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT->loadFromEnv(),
|
||||||
|
],
|
||||||
|
|
||||||
|
'redirects' => [
|
||||||
|
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE->value),
|
||||||
|
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
|
||||||
|
DEFAULT_REDIRECT_CACHE_LIFETIME,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ return [
|
|||||||
'cache' => [
|
'cache' => [
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'servers' => 'tcp://shlink_redis:6379',
|
'servers' => 'tcp://shlink_redis:6379',
|
||||||
// 'servers' => [
|
// 'servers' => 'tcp://barbar@shlink_redis_acl:6379',
|
||||||
// 'tcp://shlink_redis:6379',
|
// 'servers' => 'tcp://foo:bar@shlink_redis_acl:6379',
|
||||||
// ],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'pub_sub_enabled' => true,
|
||||||
|
],
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use PhpMiddleware\RequestId;
|
use PhpMiddleware\RequestId;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\Processor\BackwardsCompatibleMonologProcessorDelegator;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ return [
|
|||||||
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
||||||
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
|
'delegators' => [
|
||||||
|
RequestId\MonologProcessor::class => [
|
||||||
|
BackwardsCompatibleMonologProcessorDelegator::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
|
|||||||
@@ -3,14 +3,17 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Mezzio\Router\FastRouteRouter;
|
use Mezzio\Router\FastRouteRouter;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'router' => [
|
'router' => [
|
||||||
'base_path' => '',
|
'base_path' => EnvVars::BASE_PATH->loadFromEnv(''),
|
||||||
|
|
||||||
'fastroute' => [
|
'fastroute' => [
|
||||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
// Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console
|
||||||
|
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||||
|
FastRouteRouter::CONFIG_CACHE_ENABLED => PHP_SAPI !== 'cli',
|
||||||
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
115
config/autoload/routes.config.php
Normal file
115
config/autoload/routes.config.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?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 {
|
||||||
|
$contentNegotiationMiddleware = Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class;
|
||||||
|
$dropDomainMiddleware = Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class;
|
||||||
|
$overrideDomainMiddleware = Middleware\ShortUrl\OverrideDomainMiddleware::class;
|
||||||
|
|
||||||
|
// TODO This should be based on config, not the env var
|
||||||
|
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false) ? '[/]' : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// The order of the routes defined here matters. Changing it might cause path conflicts
|
||||||
|
'routes' => [
|
||||||
|
// Rest
|
||||||
|
...ConfigProvider::applyRoutesPrefix([
|
||||||
|
Action\HealthAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Visits.
|
||||||
|
// These 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
|
||||||
|
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(),
|
||||||
|
|
||||||
|
// Short URLs
|
||||||
|
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
||||||
|
$contentNegotiationMiddleware,
|
||||||
|
$dropDomainMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\SingleStepCreateShortUrlAction::getRouteDef([
|
||||||
|
$contentNegotiationMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\EditShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\DeleteShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ResolveShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ListShortUrlsAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
Action\Tag\ListTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\TagsStatsAction::getRouteDef(),
|
||||||
|
Action\Tag\DeleteTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\UpdateTagAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Domains
|
||||||
|
Action\Domain\ListDomainsAction::getRouteDef(),
|
||||||
|
Action\Domain\DomainRedirectsAction::getRouteDef(),
|
||||||
|
|
||||||
|
Action\MercureInfoAction::getRouteDef([NotConfiguredMercureErrorHandler::class]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Non-rest
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RobotsAction::class,
|
||||||
|
'path' => '/robots.txt',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\RobotsAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\PixelAction::class,
|
||||||
|
'path' => '/{shortCode}/track',
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
CoreAction\PixelAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\QrCodeAction::class,
|
||||||
|
'path' => '/{shortCode}/qr-code',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\QrCodeAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RedirectAction::class,
|
||||||
|
'path' => sprintf('/{shortCode}%s', $shortUrlRouteSuffix),
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
TrimTrailingSlashMiddleware::class,
|
||||||
|
CoreAction\RedirectAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -2,21 +2,33 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
use function Shlinkio\Shlink\Config\getOpenswooleConfigFromEnv;
|
||||||
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
|
||||||
'enable_coroutine' => false,
|
|
||||||
|
|
||||||
'swoole-http-server' => [
|
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
||||||
'host' => '0.0.0.0',
|
|
||||||
'process-name' => 'shlink',
|
|
||||||
|
|
||||||
'options' => [
|
return (static function (): array {
|
||||||
'worker_num' => 16,
|
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM->loadFromEnv(16);
|
||||||
'task_worker_num' => 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' => [
|
||||||
|
...getOpenswooleConfigFromEnv(),
|
||||||
|
'worker_num' => (int) EnvVars::WEB_WORKER_NUM->loadFromEnv(16),
|
||||||
|
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,30 +2,42 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
'tracking' => [
|
return (static function (): array {
|
||||||
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
/** @var string|null $disableTrackingFrom */
|
||||||
// This applies only if IP address tracking is enabled
|
$disableTrackingFrom = EnvVars::DISABLE_TRACKING_FROM->loadFromEnv();
|
||||||
'anonymize_remote_addr' => true,
|
|
||||||
|
|
||||||
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
return [
|
||||||
'track_orphan_visits' => true,
|
|
||||||
|
|
||||||
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
'tracking' => [
|
||||||
'disable_track_param' => null,
|
// 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),
|
||||||
|
|
||||||
// If true, visits will not be tracked at all
|
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||||
'disable_tracking' => false,
|
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
|
||||||
|
|
||||||
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||||
'disable_ip_tracking' => false,
|
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
|
||||||
|
|
||||||
// If true, the referrer will not be tracked
|
// If true, visits will not be tracked at all
|
||||||
'disable_referrer_tracking' => false,
|
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
// If true, the user agent will not be tracked
|
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||||
'disable_ua_tracking' => false,
|
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
|
||||||
],
|
|
||||||
|
|
||||||
];
|
// If true, the referrer will not be tracked
|
||||||
|
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// If true, the user agent will not be tracked
|
||||||
|
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
||||||
|
'disable_tracking_from' => $disableTrackingFrom === null
|
||||||
|
? []
|
||||||
|
: array_map(trim(...), explode(',', $disableTrackingFrom)),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,26 +2,34 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
|
||||||
|
|
||||||
return [
|
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
||||||
|
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
||||||
|
|
||||||
'url_shortener' => [
|
return (static function (): array {
|
||||||
'domain' => [
|
$shortCodesLength = max(
|
||||||
'schema' => 'https',
|
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
|
||||||
'hostname' => '',
|
MIN_SHORT_CODES_LENGTH,
|
||||||
|
);
|
||||||
|
$modeFromEnv = EnvVars::SHORT_URL_MODE->loadFromEnv(ShortUrlMode::STRICT->value);
|
||||||
|
$mode = ShortUrlMode::tryDeprecated($modeFromEnv) ?? ShortUrlMode::STRICT;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'url_shortener' => [
|
||||||
|
'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
|
||||||
|
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv(true)) ? 'https' : 'http',
|
||||||
|
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(''),
|
||||||
|
],
|
||||||
|
'default_short_codes_length' => $shortCodesLength,
|
||||||
|
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(false),
|
||||||
|
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(false),
|
||||||
|
'multi_segment_slugs_enabled' => (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(false),
|
||||||
|
'trailing_slash_enabled' => (bool) EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false),
|
||||||
|
'mode' => $mode,
|
||||||
],
|
],
|
||||||
'validate_url' => false, // Deprecated
|
|
||||||
'visits_webhooks' => [],
|
|
||||||
'default_short_codes_length' => DEFAULT_SHORT_CODES_LENGTH,
|
|
||||||
'auto_resolve_titles' => false,
|
|
||||||
'append_extra_path' => false,
|
|
||||||
|
|
||||||
// TODO Move these two options to their own config namespace. Maybe "redirects".
|
];
|
||||||
'redirect_status_code' => DEFAULT_REDIRECT_STATUS_CODE,
|
})();
|
||||||
'redirect_cache_lifetime' => DEFAULT_REDIRECT_CACHE_LIFETIME,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -2,16 +2,23 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
$isSwoole = extension_loaded('swoole');
|
use function Shlinkio\Shlink\Config\runningInOpenswoole;
|
||||||
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'domain' => [
|
'domain' => [
|
||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => sprintf('localhost:%s', $isSwoole ? '8080' : '8000'),
|
'hostname' => sprintf('localhost:%s', match (true) {
|
||||||
|
runningInRoadRunner() => '8800',
|
||||||
|
runningInOpenswoole() => '8080',
|
||||||
|
default => '8000',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
'auto_resolve_titles' => true,
|
'auto_resolve_titles' => true,
|
||||||
|
// 'multi_segment_slugs_enabled' => true,
|
||||||
|
// 'trailing_slash_enabled' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
20
config/autoload/webhooks.global.php
Normal file
20
config/autoload/webhooks.global.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
// Deprecated. Webhooks are no longer supported. To be removed in Shlink 4.0.0
|
||||||
|
return (static function (): array {
|
||||||
|
$webhooks = EnvVars::VISITS_WEBHOOKS->loadFromEnv();
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'visits_webhooks' => [
|
||||||
|
'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
|
||||||
|
'notify_orphan_visits_to_webhooks' =>
|
||||||
|
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Symfony\Component\Console\Application as CliApp;
|
use Symfony\Component\Console\Application as CliApp;
|
||||||
|
|
||||||
return (static function () {
|
return (static function (): CliApp {
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/container.php';
|
$container = include __DIR__ . '/container.php';
|
||||||
return $container->get(CliApp::class);
|
return $container->get(CliApp::class);
|
||||||
|
|||||||
@@ -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,32 +8,53 @@ use Laminas\ConfigAggregator;
|
|||||||
use Laminas\Diactoros;
|
use Laminas\Diactoros;
|
||||||
use Mezzio;
|
use Mezzio;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
use Mezzio\Swoole\ConfigProvider as SwooleConfigProvider;
|
use Mezzio\Swoole;
|
||||||
|
use Shlinkio\Shlink\Config\ConfigAggregator\EnvVarLoaderProvider;
|
||||||
|
|
||||||
use function class_exists;
|
use function class_exists;
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function Shlinkio\Shlink\Config\openswooleIsInstalled;
|
||||||
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
use function Shlinkio\Shlink\Core\enumValues;
|
||||||
|
|
||||||
return (new ConfigAggregator\ConfigAggregator([
|
use const PHP_SAPI;
|
||||||
Mezzio\ConfigProvider::class,
|
|
||||||
Mezzio\Router\ConfigProvider::class,
|
$isTestEnv = env('APP_ENV') === 'test';
|
||||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
$enableSwoole = PHP_SAPI === 'cli' && openswooleIsInstalled() && ! runningInRoadRunner();
|
||||||
class_exists(SwooleConfigProvider::class) ? SwooleConfigProvider::class : new ConfigAggregator\ArrayProvider([]),
|
|
||||||
ProblemDetails\ConfigProvider::class,
|
return (new ConfigAggregator\ConfigAggregator(
|
||||||
Diactoros\ConfigProvider::class,
|
providers: [
|
||||||
Common\ConfigProvider::class,
|
! $isTestEnv
|
||||||
Config\ConfigProvider::class,
|
? new EnvVarLoaderProvider('config/params/generated_config.php', enumValues(Core\Config\EnvVars::class))
|
||||||
Importer\ConfigProvider::class,
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
IpGeolocation\ConfigProvider::class,
|
Mezzio\ConfigProvider::class,
|
||||||
EventDispatcher\ConfigProvider::class,
|
Mezzio\Router\ConfigProvider::class,
|
||||||
Core\ConfigProvider::class,
|
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||||
CLI\ConfigProvider::class,
|
$enableSwoole && class_exists(Swoole\ConfigProvider::class)
|
||||||
Rest\ConfigProvider::class,
|
? Swoole\ConfigProvider::class
|
||||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
env('APP_ENV') === 'test'
|
ProblemDetails\ConfigProvider::class,
|
||||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
Diactoros\ConfigProvider::class,
|
||||||
: new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
Common\ConfigProvider::class,
|
||||||
], 'data/cache/app_config.php', [
|
Config\ConfigProvider::class,
|
||||||
Core\Config\SimplifiedConfigParser::class,
|
Importer\ConfigProvider::class,
|
||||||
Core\Config\BasePathPrefixer::class,
|
IpGeolocation\ConfigProvider::class,
|
||||||
Core\Config\DeprecatedConfigParser::class,
|
EventDispatcher\ConfigProvider::class,
|
||||||
]))->getMergedConfig();
|
Core\ConfigProvider::class,
|
||||||
|
CLI\ConfigProvider::class,
|
||||||
|
Rest\ConfigProvider::class,
|
||||||
|
new ConfigAggregator\PhpFileProvider('config/autoload/{,*.}global.php'),
|
||||||
|
// Local config should not be loaded during tests, whereas test config should be loaded ONLY during tests
|
||||||
|
new ConfigAggregator\PhpFileProvider(
|
||||||
|
$isTestEnv ? 'config/test/*.global.php' : 'config/autoload/{,*.}local.php',
|
||||||
|
),
|
||||||
|
// Routes have to be loaded last
|
||||||
|
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
|
||||||
|
],
|
||||||
|
cachedConfigFile: 'data/cache/app_config.php',
|
||||||
|
postProcessors: [
|
||||||
|
Core\Config\PostProcessor\BasePathPrefixer::class,
|
||||||
|
Core\Config\PostProcessor\MultiSegmentSlugProcessor::class,
|
||||||
|
Core\Config\PostProcessor\ShortUrlMethodsProcessor::class,
|
||||||
|
],
|
||||||
|
))->getMergedConfig();
|
||||||
|
|||||||
24
config/constants.php
Normal file
24
config/constants.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Util\RedirectStatus;
|
||||||
|
|
||||||
|
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||||
|
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||||
|
const MIN_SHORT_CODES_LENGTH = 4;
|
||||||
|
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302; // Deprecated. Default to 307 for Shlink v4
|
||||||
|
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||||
|
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||||
|
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
|
||||||
|
const LOOSE_URI_MATCHER = '/(.+)\:(.+)/i'; // Matches anything starting with a schema.
|
||||||
|
const DEFAULT_QR_CODE_SIZE = 300;
|
||||||
|
const DEFAULT_QR_CODE_MARGIN = 0;
|
||||||
|
const DEFAULT_QR_CODE_FORMAT = 'png';
|
||||||
|
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
||||||
|
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
||||||
|
// Deprecated. Shlink 4.0.0 should change default value to `true`
|
||||||
|
const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = false;
|
||||||
|
const MIN_TASK_WORKERS = 4;
|
||||||
@@ -3,22 +3,36 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\ServiceManager;
|
use Laminas\ServiceManager\ServiceManager;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
// Workaround to make this compatible with both openswoole 22 and earlier versions.
|
||||||
|
// Openswoole support is deprecated. Remove in v4.0.0
|
||||||
|
if (! function_exists('swoole_set_process_name')) {
|
||||||
|
// phpcs:disable
|
||||||
|
function swoole_set_process_name(string $name): void
|
||||||
|
{
|
||||||
|
OpenSwoole\Util::setProcessName($name);
|
||||||
|
}
|
||||||
|
// phpcs:enable
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is one of the first files loaded. Configure the timezone here
|
||||||
|
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv(date_default_timezone_get()));
|
||||||
|
|
||||||
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
||||||
// It needs to be placed here as individual config files will not be loaded once config is cached
|
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||||
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
||||||
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build container
|
return (static function (): ServiceManager {
|
||||||
return (function () {
|
|
||||||
$config = require __DIR__ . '/config.php';
|
$config = require __DIR__ . '/config.php';
|
||||||
$container = new ServiceManager($config['dependencies']);
|
$container = new ServiceManager($config['dependencies']);
|
||||||
$container->setService('config', $config);
|
$container->setService('config', $config);
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
return (static function () {
|
return (static function (): EntityManagerInterface {
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/container.php';
|
$container = include __DIR__ . '/container.php';
|
||||||
return $container->get(EntityManager::class);
|
return $container->get(EntityManager::class);
|
||||||
|
|||||||
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
|
||||||
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
|
||||||
@@ -10,8 +10,8 @@ use Psr\Container\ContainerInterface;
|
|||||||
use function register_shutdown_function;
|
use function register_shutdown_function;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = require __DIR__ . '/../container.php';
|
$container = require __DIR__ . '/../container.php';
|
||||||
@@ -20,15 +20,19 @@ $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');
|
||||||
|
|
||||||
// Start code coverage collecting on swoole process, and stop it when process shuts down
|
// Dump code coverage when process shuts down
|
||||||
$httpClient->request('GET', sprintf('http://%s:%s/api-tests/start-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT));
|
|
||||||
register_shutdown_function(function () use ($httpClient): void {
|
register_shutdown_function(function () use ($httpClient): void {
|
||||||
$httpClient->request(
|
$httpClient->request(
|
||||||
'GET',
|
'GET',
|
||||||
sprintf('http://%s:%s/api-tests/stop-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
sprintf('http://%s:%s/api-tests/stop-coverage', API_TESTS_HOST, API_TESTS_PORT),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$testHelper->createTestDb();
|
$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'],
|
||||||
|
);
|
||||||
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();
|
$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';
|
||||||
|
|||||||
@@ -8,43 +8,83 @@ use GuzzleHttp\Client;
|
|||||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use Laminas\Stdlib\Glob;
|
use League\Event\EventDispatcher;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
|
||||||
use PHPUnit\Runner\Version;
|
use PHPUnit\Runner\Version;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||||
use SebastianBergmann\CodeCoverage\Filter;
|
use SebastianBergmann\CodeCoverage\Filter;
|
||||||
|
use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html;
|
||||||
use SebastianBergmann\CodeCoverage\Report\PHP;
|
use SebastianBergmann\CodeCoverage\Report\PHP;
|
||||||
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||||
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
use function file_exists;
|
||||||
use function Laminas\Stratigility\middleware;
|
use function Laminas\Stratigility\middleware;
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function sys_get_temp_dir;
|
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';
|
$isApiTest = env('TEST_ENV') === 'api';
|
||||||
if ($isApiTest) {
|
$isCliTest = env('TEST_ENV') === 'cli';
|
||||||
|
$isE2eTest = $isApiTest || $isCliTest;
|
||||||
|
$coverageType = env('GENERATE_COVERAGE');
|
||||||
|
$generateCoverage = contains($coverageType, ['yes', 'pretty']);
|
||||||
|
|
||||||
|
$coverage = null;
|
||||||
|
if ($isE2eTest && $generateCoverage) {
|
||||||
$filter = new Filter();
|
$filter = new Filter();
|
||||||
foreach (Glob::glob(__DIR__ . '/../../module/*/src') as $item) {
|
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
|
||||||
$filter->includeDirectory($item);
|
$filter->includeDirectory(__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src');
|
||||||
}
|
|
||||||
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param 'api'|'cli' $type
|
||||||
|
*/
|
||||||
|
$exportCoverage = static function (string $type = 'api') use (&$coverage, $coverageType): void {
|
||||||
|
if ($coverage === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$basePath = __DIR__ . '/../../build/coverage-' . $type;
|
||||||
|
$covPath = $basePath . '.cov';
|
||||||
|
|
||||||
|
// Every CLI test runs on its own process and dumps the coverage afterwards.
|
||||||
|
// Try to load it and merge it, so that we end up with the whole coverage at the end.
|
||||||
|
if ($type === 'cli' && file_exists($covPath)) {
|
||||||
|
$coverage->merge(require $covPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($coverageType === 'pretty') {
|
||||||
|
(new Html())->process($coverage, $basePath . '/coverage-html');
|
||||||
|
} else {
|
||||||
|
(new PHP())->process($coverage, $covPath);
|
||||||
|
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$buildDbConnection = static function (): array {
|
$buildDbConnection = static function (): array {
|
||||||
$driver = env('DB_DRIVER', 'sqlite');
|
$driver = env('DB_DRIVER', 'sqlite');
|
||||||
$isCi = env('CI', false);
|
$isCi = env('CI', false);
|
||||||
$getMysqlHost = static fn (string $driver) => sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria');
|
|
||||||
$getCiMysqlPort = static fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
$getCiMysqlPort = static fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
||||||
|
|
||||||
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',
|
||||||
@@ -61,29 +101,27 @@ $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',
|
||||||
'host' => $isCi ? '127.0.0.1' : $getMysqlHost($driver),
|
'host' => $isCi ? '127.0.0.1' : sprintf('shlink_db_%s', $driver),
|
||||||
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
||||||
'user' => 'root',
|
'user' => 'root',
|
||||||
'password' => 'root',
|
'password' => 'root',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
'charset' => 'utf8',
|
'charset' => 'utf8mb4',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
$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 [
|
||||||
@@ -94,19 +132,19 @@ return [
|
|||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'domain' => [
|
'domain' => [
|
||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => 'doma.in',
|
'hostname' => 's.test',
|
||||||
],
|
],
|
||||||
'validate_url' => true,
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
'mezzio-swoole' => [
|
||||||
'enable_coroutine' => false,
|
'enable_coroutine' => false,
|
||||||
'swoole-http-server' => [
|
'swoole-http-server' => [
|
||||||
'host' => SWOOLE_TESTING_HOST,
|
'host' => API_TESTS_HOST,
|
||||||
'port' => SWOOLE_TESTING_PORT,
|
'port' => API_TESTS_PORT,
|
||||||
'process-name' => 'shlink_test',
|
'process-name' => 'shlink_test',
|
||||||
'options' => [
|
'options' => [
|
||||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
||||||
|
'log_file' => __DIR__ . '/../../data/log/api-tests/output.log',
|
||||||
'enable_coroutine' => false,
|
'enable_coroutine' => false,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -114,30 +152,33 @@ return [
|
|||||||
|
|
||||||
'routes' => !$isApiTest ? [] : [
|
'routes' => !$isApiTest ? [] : [
|
||||||
[
|
[
|
||||||
'name' => 'start_collecting_coverage',
|
'name' => 'dump_coverage',
|
||||||
'path' => '/api-tests/start-coverage',
|
'path' => '/api-tests/stop-coverage',
|
||||||
'middleware' => middleware(static function () use (&$coverage) {
|
'middleware' => middleware(static function () use ($exportCoverage) {
|
||||||
if ($coverage) { // @phpstan-ignore-line
|
// TODO I have tried moving this block to a listener so that it's invoked automatically,
|
||||||
$coverage->start('API tests');
|
// but then the coverage is generated empty ¯\_(ツ)_/¯
|
||||||
}
|
$exportCoverage();
|
||||||
return new EmptyResponse();
|
return new EmptyResponse();
|
||||||
}),
|
}),
|
||||||
'allowed_methods' => ['GET'],
|
'allowed_methods' => ['GET'],
|
||||||
],
|
],
|
||||||
[
|
],
|
||||||
'name' => 'dump_coverage',
|
|
||||||
'path' => '/api-tests/stop-coverage',
|
|
||||||
'middleware' => middleware(static function () use (&$coverage) {
|
|
||||||
if ($coverage) { // @phpstan-ignore-line
|
|
||||||
$basePath = __DIR__ . '/../../build/coverage-api';
|
|
||||||
$coverage->stop();
|
|
||||||
(new PHP())->process($coverage, $basePath . '.cov');
|
|
||||||
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EmptyResponse();
|
'middleware_pipeline' => !$isApiTest ? [] : [
|
||||||
|
'capture_code_coverage' => [
|
||||||
|
'middleware' => middleware(static function (
|
||||||
|
ServerRequestInterface $req,
|
||||||
|
RequestHandlerInterface $handler,
|
||||||
|
) use (&$coverage): ResponseInterface {
|
||||||
|
$coverage?->start($req->getHeaderLine('x-coverage-id'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $handler->handle($req);
|
||||||
|
} finally {
|
||||||
|
$coverage?->stop();
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
'allowed_methods' => ['GET'],
|
'priority' => 9999,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -150,13 +191,69 @@ return [
|
|||||||
'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 => [
|
||||||
|
static function (
|
||||||
|
ContainerInterface $c,
|
||||||
|
string $serviceName,
|
||||||
|
callable $callback,
|
||||||
|
) use (
|
||||||
|
&$coverage,
|
||||||
|
$exportCoverage,
|
||||||
|
) {
|
||||||
|
/** @var Application $app */
|
||||||
|
$app = $callback();
|
||||||
|
$wrappedEventDispatcher = new EventDispatcher();
|
||||||
|
|
||||||
|
// When the command starts, start collecting coverage
|
||||||
|
$wrappedEventDispatcher->subscribeTo(
|
||||||
|
ConsoleCommandEvent::class,
|
||||||
|
static function () use (&$coverage): void {
|
||||||
|
$id = env('COVERAGE_ID');
|
||||||
|
if ($id === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage?->start($id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// When the command ends, stop collecting coverage
|
||||||
|
$wrappedEventDispatcher->subscribeTo(
|
||||||
|
ConsoleTerminateEvent::class,
|
||||||
|
static function () use (&$coverage, $exportCoverage): void {
|
||||||
|
$id = env('COVERAGE_ID');
|
||||||
|
if ($id === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage?->stop();
|
||||||
|
$exportCoverage('cli');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->setDispatcher(new class ($wrappedEventDispatcher) implements EventDispatcherInterface {
|
||||||
|
public function __construct(private EventDispatcher $wrappedDispatcher)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(object $event, ?string $eventName = null): object
|
||||||
|
{
|
||||||
|
$this->wrappedDispatcher->dispatch($event);
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
] : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
@@ -165,13 +262,14 @@ return [
|
|||||||
|
|
||||||
'data_fixtures' => [
|
'data_fixtures' => [
|
||||||
'paths' => [
|
'paths' => [
|
||||||
|
// TODO These are used for CLI tests too, so maybe should be somewhere else
|
||||||
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'logger' => [
|
'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.2-fpm.sock;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi.conf;
|
include fastcgi.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/var/log/shlink/shlink_swoole.log {
|
/var/log/shlink/shlink_openswoole.log {
|
||||||
su root root
|
su root root
|
||||||
daily
|
daily
|
||||||
missingok
|
missingok
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
notifempty
|
notifempty
|
||||||
create 0640 root root
|
create 0640 root root
|
||||||
postrotate
|
postrotate
|
||||||
/etc/init.d/shlink_swoole restart
|
/etc/init.d/shlink_openswoole restart
|
||||||
endscript
|
endscript
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
### BEGIN INIT INFO
|
### BEGIN INIT INFO
|
||||||
# Provides: shlink_swoole
|
# Provides: shlink_openswoole
|
||||||
# Required-Start: $local_fs $network $named $time $syslog
|
# Required-Start: $local_fs $network $named $time $syslog
|
||||||
# Required-Stop: $local_fs $network $named $time $syslog
|
# Required-Stop: $local_fs $network $named $time $syslog
|
||||||
# Default-Start: 2 3 4 5
|
# Default-Start: 2 3 4 5
|
||||||
# Default-Stop: 0 1 6
|
# Default-Stop: 0 1 6
|
||||||
# Description: Shlink non-blocking server with swoole
|
# Description: Shlink non-blocking server with openswoole
|
||||||
### END INIT INFO
|
### END INIT INFO
|
||||||
|
|
||||||
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
|
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
|
||||||
RUNAS=root
|
RUNAS=root
|
||||||
|
|
||||||
PIDFILE=/var/run/shlink_swoole.pid
|
PIDFILE=/var/run/shlink_openswoole.pid
|
||||||
LOGDIR=/var/log/shlink
|
LOGDIR=/var/log/shlink
|
||||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
LOGFILE=${LOGDIR}/shlink_openswoole.log
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||||
echo 'Shlink with swoole already running' >&2
|
echo 'Shlink with openswoole already running' >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo 'Starting shlink with swoole' >&2
|
echo 'Starting shlink with openswoole' >&2
|
||||||
mkdir -p "$LOGDIR"
|
mkdir -p "$LOGDIR"
|
||||||
touch "$LOGFILE"
|
touch "$LOGFILE"
|
||||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||||
@@ -30,10 +30,10 @@ start() {
|
|||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||||
echo 'Shlink with swoole not running' >&2
|
echo 'Shlink with openswoole not running' >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo 'Stopping shlink with swoole' >&2
|
echo 'Stopping shlink with openswoole' >&2
|
||||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||||
echo 'Shlink stopped' >&2
|
echo 'Shlink stopped' >&2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
FROM php:8.0.9-fpm-alpine3.14
|
FROM php:8.2-fpm-alpine3.17
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.20
|
ENV APCU_VERSION 5.1.21
|
||||||
ENV PDO_SQLSRV_VERSION 5.9.0
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.1
|
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
|
||||||
|
|
||||||
# Install common php extensions
|
# Install common php extensions
|
||||||
RUN docker-php-ext-install pdo_mysql
|
RUN docker-php-ext-install pdo_mysql
|
||||||
RUN docker-php-ext-install iconv
|
|
||||||
RUN docker-php-ext-install calendar
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
RUN apk add --no-cache oniguruma-dev
|
RUN apk add --no-cache oniguruma-dev
|
||||||
@@ -31,8 +31,10 @@ 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 apk add --no-cache gmp-dev
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
RUN docker-php-ext-install gmp
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
@@ -45,13 +47,13 @@ RUN mkdir -p /usr/src/php/ext/apcu \
|
|||||||
&& 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 pcov and sqlsrv driver
|
||||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --allow-untrusted msodbcsql17_${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} pcov && \
|
||||||
docker-php-ext-enable pdo_sqlsrv pcov && \
|
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||||
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
|
||||||
|
|||||||
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
|
||||||
76
data/infra/roadrunner.Dockerfile
Normal file
76
data/infra/roadrunner.Dockerfile
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
FROM php:8.2-alpine3.17
|
||||||
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
|
ENV APCU_VERSION 5.1.21
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
|
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
|
||||||
|
|
||||||
|
# Install common php extensions
|
||||||
|
RUN docker-php-ext-install pdo_mysql
|
||||||
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
|
RUN apk add --no-cache oniguruma-dev
|
||||||
|
RUN docker-php-ext-install mbstring
|
||||||
|
|
||||||
|
RUN apk add --no-cache sqlite-libs
|
||||||
|
RUN apk add --no-cache sqlite-dev
|
||||||
|
RUN docker-php-ext-install pdo_sqlite
|
||||||
|
|
||||||
|
RUN apk add --no-cache icu-dev
|
||||||
|
RUN docker-php-ext-install intl
|
||||||
|
|
||||||
|
RUN apk add --no-cache libzip-dev zlib-dev
|
||||||
|
RUN docker-php-ext-install zip
|
||||||
|
|
||||||
|
RUN apk add --no-cache libpng-dev
|
||||||
|
RUN docker-php-ext-install gd
|
||||||
|
|
||||||
|
RUN apk add --no-cache postgresql-dev
|
||||||
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
|
RUN 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
|
||||||
|
|
||||||
|
# Install APCu extension
|
||||||
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
|
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||||
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||||
|
&& docker-php-ext-configure apcu \
|
||||||
|
&& docker-php-ext-install apcu \
|
||||||
|
&& rm /tmp/apcu.tar.gz \
|
||||||
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
|
# Install pcov and sqlsrv driver
|
||||||
|
RUN wget https://download.microsoft.com/download/${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 && \
|
||||||
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
|
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||||
|
apk del .phpize-deps && \
|
||||||
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
|
# Install composer
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Make home directory writable by anyone
|
||||||
|
RUN chmod 777 /home
|
||||||
|
|
||||||
|
VOLUME /home/shlink
|
||||||
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
|
# Expose roadrunner port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD \
|
||||||
|
# Install dependencies if the vendor dir does not exist
|
||||||
|
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||||
|
# Download roadrunner binary
|
||||||
|
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; fi && \
|
||||||
|
# This forces the app to be started every second until the exit code is 0
|
||||||
|
until ./bin/rr serve -c config/roadrunner/.rr.dev.yml; do sleep 1 ; done
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
FROM php:8.0.9-alpine3.14
|
FROM php:8.2-alpine3.17
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.20
|
ENV APCU_VERSION 5.1.21
|
||||||
ENV PDO_SQLSRV_VERSION 5.9.0
|
|
||||||
ENV INOTIFY_VERSION 3.0.0
|
ENV INOTIFY_VERSION 3.0.0
|
||||||
ENV SWOOLE_VERSION 4.7.0
|
ENV OPENSWOOLE_VERSION 22.1.0
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.1
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
|
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
|
||||||
|
|
||||||
# Install common php extensions
|
# Install common php extensions
|
||||||
RUN docker-php-ext-install pdo_mysql
|
RUN docker-php-ext-install pdo_mysql
|
||||||
RUN docker-php-ext-install iconv
|
|
||||||
RUN docker-php-ext-install calendar
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
RUN apk add --no-cache oniguruma-dev
|
RUN apk add --no-cache oniguruma-dev
|
||||||
@@ -33,8 +33,10 @@ 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 apk add --no-cache gmp-dev
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
RUN docker-php-ext-install gmp
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
@@ -54,14 +56,14 @@ RUN mkdir -p /usr/src/php/ext/inotify \
|
|||||||
&& docker-php-ext-install inotify \
|
&& docker-php-ext-install inotify \
|
||||||
&& rm /tmp/inotify.tar.gz
|
&& rm /tmp/inotify.tar.gz
|
||||||
|
|
||||||
# Install swoole, pcov and mssql driver
|
# Install openswoole, pcov and mssql driver
|
||||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --allow-untrusted msodbcsql17_${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 swoole-${SWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
docker-php-ext-enable swoole pdo_sqlsrv pcov && \
|
docker-php-ext-enable openswoole pdo_sqlsrv pcov && \
|
||||||
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
|
||||||
@@ -72,12 +74,12 @@ RUN chmod 777 /home
|
|||||||
VOLUME /home/shlink
|
VOLUME /home/shlink
|
||||||
WORKDIR /home/shlink
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
# Expose swoole port
|
# Expose openswoole port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD \
|
CMD \
|
||||||
# Install dependencies if the vendor dir does not exist
|
# Install dependencies if the vendor dir does not exist
|
||||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||||
# When restarting the container, swoole might think it is already in execution
|
# When restarting the container, openswoole might think it is already in execution
|
||||||
# This forces the app to be started every second until the exit code is 0
|
# This forces the app to be started every second until the exit code is 0
|
||||||
until php ./vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
until php ./vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Exception;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20160819142757 extends AbstractMigration
|
|
||||||
{
|
|
||||||
private const MYSQL = 'mysql';
|
|
||||||
private const SQLITE = 'sqlite';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$db = $this->connection->getDatabasePlatform()->getName();
|
|
||||||
$table = $schema->getTable('short_urls');
|
|
||||||
$column = $table->getColumn('short_code');
|
|
||||||
|
|
||||||
if ($db === self::MYSQL) {
|
|
||||||
$column->setPlatformOption('collation', 'utf8_bin');
|
|
||||||
} elseif ($db === self::SQLITE) {
|
|
||||||
$column->setPlatformOption('collate', 'BINARY');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$db = $this->connection->getDatabasePlatform()->getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace <namespace>;
|
namespace <namespace>;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
@@ -18,4 +19,9 @@ final class <className> extends AbstractMigration
|
|||||||
{
|
{
|
||||||
<down>
|
<down>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
shlink_db:
|
shlink_db_mysql:
|
||||||
environment:
|
environment:
|
||||||
MYSQL_DATABASE: shlink_test
|
MYSQL_DATABASE: shlink_test
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ services:
|
|||||||
- /etc/passwd:/etc/passwd:ro
|
- /etc/passwd:/etc/passwd:ro
|
||||||
- /etc/group:/etc/group:ro
|
- /etc/group:/etc/group:ro
|
||||||
|
|
||||||
shlink_db:
|
shlink_roadrunner:
|
||||||
|
user: 1000:1000
|
||||||
|
volumes:
|
||||||
|
- /etc/passwd:/etc/passwd:ro
|
||||||
|
- /etc/group:/etc/group:ro
|
||||||
|
|
||||||
|
shlink_db_mysql:
|
||||||
user: 1000:1000
|
user: 1000:1000
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/passwd:/etc/passwd:ro
|
- /etc/passwd:/etc/passwd:ro
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
shlink_nginx:
|
shlink_nginx:
|
||||||
container_name: shlink_nginx
|
container_name: shlink_nginx
|
||||||
image: nginx:1.19.6-alpine
|
image: nginx:1.25-alpine
|
||||||
ports:
|
ports:
|
||||||
- "8000:80"
|
- "8000:80"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -18,23 +18,30 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./data/infra/php.Dockerfile
|
dockerfile: ./data/infra/php.Dockerfile
|
||||||
|
ports:
|
||||||
|
- '8888:8888'
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/home/shlink/www
|
- ./:/home/shlink/www
|
||||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||||
links:
|
links:
|
||||||
- shlink_db
|
- shlink_db_mysql
|
||||||
- shlink_db_postgres
|
- shlink_db_postgres
|
||||||
- shlink_db_maria
|
- shlink_db_maria
|
||||||
- shlink_db_ms
|
- shlink_db_ms
|
||||||
- shlink_redis
|
- shlink_redis
|
||||||
|
- shlink_redis_acl
|
||||||
- shlink_mercure
|
- shlink_mercure
|
||||||
- shlink_mercure_proxy
|
- shlink_mercure_proxy
|
||||||
|
- shlink_rabbitmq
|
||||||
|
- shlink_matomo
|
||||||
environment:
|
environment:
|
||||||
LC_ALL: C
|
LC_ALL: C
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
|
||||||
shlink_swoole_proxy:
|
shlink_swoole_proxy:
|
||||||
container_name: shlink_swoole_proxy
|
container_name: shlink_swoole_proxy
|
||||||
image: nginx:1.19.6-alpine
|
image: nginx:1.25-alpine
|
||||||
ports:
|
ports:
|
||||||
- "8002:80"
|
- "8002:80"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -55,19 +62,50 @@ services:
|
|||||||
- ./:/home/shlink
|
- ./:/home/shlink
|
||||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||||
links:
|
links:
|
||||||
- shlink_db
|
- shlink_db_mysql
|
||||||
- shlink_db_postgres
|
- shlink_db_postgres
|
||||||
- shlink_db_maria
|
- shlink_db_maria
|
||||||
- shlink_db_ms
|
- shlink_db_ms
|
||||||
- shlink_redis
|
- shlink_redis
|
||||||
|
- shlink_redis_acl
|
||||||
- shlink_mercure
|
- shlink_mercure
|
||||||
- shlink_mercure_proxy
|
- shlink_mercure_proxy
|
||||||
|
- shlink_rabbitmq
|
||||||
|
- shlink_matomo
|
||||||
environment:
|
environment:
|
||||||
LC_ALL: C
|
LC_ALL: C
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
|
||||||
shlink_db:
|
shlink_roadrunner:
|
||||||
container_name: shlink_db
|
container_name: shlink_roadrunner
|
||||||
image: mysql:5.7
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./data/infra/roadrunner.Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8800:8080"
|
||||||
|
volumes:
|
||||||
|
- ./:/home/shlink
|
||||||
|
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||||
|
links:
|
||||||
|
- shlink_db_mysql
|
||||||
|
- shlink_db_postgres
|
||||||
|
- shlink_db_maria
|
||||||
|
- shlink_db_ms
|
||||||
|
- shlink_redis
|
||||||
|
- shlink_redis_acl
|
||||||
|
- shlink_mercure
|
||||||
|
- shlink_mercure_proxy
|
||||||
|
- shlink_rabbitmq
|
||||||
|
- shlink_matomo
|
||||||
|
environment:
|
||||||
|
LC_ALL: C
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
|
||||||
|
shlink_db_mysql:
|
||||||
|
container_name: shlink_db_mysql
|
||||||
|
image: mysql:8.0
|
||||||
ports:
|
ports:
|
||||||
- "3307:3306"
|
- "3307:3306"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -92,7 +130,7 @@ services:
|
|||||||
|
|
||||||
shlink_db_maria:
|
shlink_db_maria:
|
||||||
container_name: shlink_db_maria
|
container_name: shlink_db_maria
|
||||||
image: mariadb:10.5
|
image: mariadb:10.7
|
||||||
ports:
|
ports:
|
||||||
- "3308:3306"
|
- "3308:3306"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -114,13 +152,22 @@ services:
|
|||||||
|
|
||||||
shlink_redis:
|
shlink_redis:
|
||||||
container_name: shlink_redis
|
container_name: shlink_redis
|
||||||
image: redis:6.0-alpine
|
image: redis:6.2-alpine
|
||||||
ports:
|
ports:
|
||||||
- "6380:6379"
|
- "6380:6379"
|
||||||
|
|
||||||
|
shlink_redis_acl:
|
||||||
|
container_name: shlink_redis_acl
|
||||||
|
image: redis:6.2-alpine
|
||||||
|
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
|
||||||
|
ports:
|
||||||
|
- "6382:6379"
|
||||||
|
volumes:
|
||||||
|
- ./data/infra/redis/redis-acl.conf:/usr/local/etc/redis/redis.conf
|
||||||
|
|
||||||
shlink_mercure_proxy:
|
shlink_mercure_proxy:
|
||||||
container_name: shlink_mercure_proxy
|
container_name: shlink_mercure_proxy
|
||||||
image: nginx:1.19.6-alpine
|
image: nginx:1.25-alpine
|
||||||
ports:
|
ports:
|
||||||
- "8001:80"
|
- "8001:80"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -131,10 +178,47 @@ services:
|
|||||||
|
|
||||||
shlink_mercure:
|
shlink_mercure:
|
||||||
container_name: shlink_mercure
|
container_name: shlink_mercure
|
||||||
image: dunglas/mercure:v0.10
|
image: dunglas/mercure:v0.15
|
||||||
ports:
|
ports:
|
||||||
- "3080:80"
|
- "3080:80"
|
||||||
environment:
|
environment:
|
||||||
CORS_ALLOWED_ORIGINS: "*"
|
SERVER_NAME: ":80"
|
||||||
JWT_KEY: "mercure_jwt_key"
|
MERCURE_PUBLISHER_JWT_KEY: mercure_jwt_key_long_enough_to_avoid_error
|
||||||
USE_FORWARDED_HEADERS: "1"
|
MERCURE_SUBSCRIBER_JWT_KEY: mercure_jwt_key_long_enough_to_avoid_error
|
||||||
|
MERCURE_EXTRA_DIRECTIVES: "cors_origins https://app.shlink.io http://localhost:3000 http://127.0.0.1:3000"
|
||||||
|
|
||||||
|
shlink_rabbitmq:
|
||||||
|
container_name: shlink_rabbitmq
|
||||||
|
image: rabbitmq:3.11-management-alpine
|
||||||
|
ports:
|
||||||
|
- "15672:15672"
|
||||||
|
- "5672:5672"
|
||||||
|
environment:
|
||||||
|
RABBITMQ_DEFAULT_USER: "rabbit"
|
||||||
|
RABBITMQ_DEFAULT_PASS: "rabbit"
|
||||||
|
|
||||||
|
shlink_swagger_ui:
|
||||||
|
container_name: shlink_swagger_ui
|
||||||
|
image: swaggerapi/swagger-ui:v5.10.3
|
||||||
|
ports:
|
||||||
|
- "8005:8080"
|
||||||
|
volumes:
|
||||||
|
- ./docs/swagger:/app
|
||||||
|
|
||||||
|
shlink_matomo:
|
||||||
|
container_name: shlink_matomo
|
||||||
|
image: matomo:4.15-apache
|
||||||
|
ports:
|
||||||
|
- "8003:80"
|
||||||
|
volumes:
|
||||||
|
# Matomo does not persist port in trusted hosts. This volume is needed to edit config afterward
|
||||||
|
# https://github.com/matomo-org/matomo/issues/9549
|
||||||
|
- ./data/infra/matomo:/var/www/html
|
||||||
|
links:
|
||||||
|
- shlink_db_mysql
|
||||||
|
environment:
|
||||||
|
MATOMO_DATABASE_HOST: "shlink_db_mysql"
|
||||||
|
MATOMO_DATABASE_ADAPTER: "mysql"
|
||||||
|
MATOMO_DATABASE_DBNAME: "matomo"
|
||||||
|
MATOMO_DATABASE_USERNAME: "root"
|
||||||
|
MATOMO_DATABASE_PASSWORD: "root"
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
|
|
||||||
This image provides an easy way to set up [shlink](https://shlink.io) on a container-based runtime.
|
This image provides an easy way to set up [shlink](https://shlink.io) on a container-based runtime.
|
||||||
|
|
||||||
It exposes a shlink instance served with [swoole](https://www.swoole.co.uk/), which can be linked to external databases to persist data.
|
It exposes a shlink instance served with [RoadRunner](https://roadrunner.dev) or [openswoole](https://openswoole.com/), which can be linked to external databases to persist data.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The most basic way to run Shlink's docker image is by providing these mandatory env vars.
|
The most basic way to run Shlink's docker image is by providing these mandatory env vars.
|
||||||
|
|
||||||
* `SHORT_DOMAIN_HOST`: The custom short domain used for this shlink instance. For example **doma.in**.
|
* `DEFAULT_DOMAIN`: The default short domain used for this shlink instance. For example **s.test**.
|
||||||
* `SHORT_DOMAIN_SCHEMA`: Either **http** or **https**.
|
* `IS_HTTPS_ENABLED`: Either **true** or **false**. Tells if Shlink is being served with HTTPs or not.
|
||||||
* `GEOLITE_LICENSE_KEY`: Your GeoLite2 license key. [Learn more](https://shlink.io/documentation/geolite-license-key/) about this.
|
* `GEOLITE_LICENSE_KEY`: Your GeoLite2 license key. [Learn more](https://shlink.io/documentation/geolite-license-key/) about this.
|
||||||
|
|
||||||
To run shlink on top of a local docker service, and using an internal SQLite database, do the following:
|
To run shlink on top of a local docker service, and using an internal SQLite database, do the following:
|
||||||
@@ -21,8 +21,8 @@ To run shlink on top of a local docker service, and using an internal SQLite dat
|
|||||||
docker run \
|
docker run \
|
||||||
--name shlink \
|
--name shlink \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-e SHORT_DOMAIN_HOST=doma.in \
|
-e DEFAULT_DOMAIN=s.test \
|
||||||
-e SHORT_DOMAIN_SCHEMA=https \
|
-e IS_HTTPS_ENABLED=true \
|
||||||
-e GEOLITE_LICENSE_KEY=kjh23ljkbndskj345 \
|
-e GEOLITE_LICENSE_KEY=kjh23ljkbndskj345 \
|
||||||
shlinkio/shlink:stable
|
shlinkio/shlink:stable
|
||||||
```
|
```
|
||||||
|
|||||||
25
docker/build
25
docker/build
@@ -1,25 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
PLATFORMS="linux/arm/v7,linux/arm64/v8,linux/amd64"
|
|
||||||
DOCKER_IMAGE="shlinkio/shlink"
|
|
||||||
|
|
||||||
# If ref is not develop, then this is a tag. Build that docker tag and also "stable"
|
|
||||||
if [[ "$GITHUB_REF" != *"develop"* ]]; then
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/v}
|
|
||||||
TAGS="-t ${DOCKER_IMAGE}:${VERSION}"
|
|
||||||
# Push stable tag only if this is not an alpha or beta tag
|
|
||||||
[[ $GITHUB_REF != *"alpha"* && $GITHUB_REF != *"beta"* ]] && TAGS="${TAGS} -t ${DOCKER_IMAGE}:stable"
|
|
||||||
|
|
||||||
docker buildx build --push \
|
|
||||||
--build-arg SHLINK_VERSION=${VERSION} \
|
|
||||||
--platform ${PLATFORMS} \
|
|
||||||
${TAGS} .
|
|
||||||
|
|
||||||
# If build branch is develop, build latest
|
|
||||||
elif [[ "$GITHUB_REF" == *"develop"* ]]; then
|
|
||||||
docker buildx build --push \
|
|
||||||
--platform ${PLATFORMS} \
|
|
||||||
-t ${DOCKER_IMAGE}:latest .
|
|
||||||
fi
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
log_errors_max_len=0
|
log_errors_max_len=0
|
||||||
zend.assertions=1
|
zend.assertions=1
|
||||||
assert.exception=1
|
assert.exception=1
|
||||||
memory_limit=256M
|
memory_limit=512M
|
||||||
|
|||||||
@@ -4,173 +4,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Handler\StreamHandler;
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
use Monolog\Logger;
|
|
||||||
|
|
||||||
use function explode;
|
|
||||||
use function Functional\contains;
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_DELETE_SHORT_URL_THRESHOLD;
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
|
||||||
use const Shlinkio\Shlink\Core\MIN_SHORT_CODES_LENGTH;
|
|
||||||
|
|
||||||
$helper = new class {
|
|
||||||
private const DB_DRIVERS_MAP = [
|
|
||||||
'mysql' => 'pdo_mysql',
|
|
||||||
'maria' => 'pdo_mysql',
|
|
||||||
'postgres' => 'pdo_pgsql',
|
|
||||||
'mssql' => 'pdo_sqlsrv',
|
|
||||||
];
|
|
||||||
private const DB_PORTS_MAP = [
|
|
||||||
'mysql' => '3306',
|
|
||||||
'maria' => '3306',
|
|
||||||
'postgres' => '5432',
|
|
||||||
'mssql' => '1433',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function getDbConfig(): array
|
|
||||||
{
|
|
||||||
$driver = env('DB_DRIVER');
|
|
||||||
$isMysql = contains(['maria', 'mysql'], $driver);
|
|
||||||
if ($driver === null || $driver === 'sqlite') {
|
|
||||||
return [
|
|
||||||
'driver' => 'pdo_sqlite',
|
|
||||||
'path' => 'data/database.sqlite',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'driver' => self::DB_DRIVERS_MAP[$driver],
|
|
||||||
'dbname' => env('DB_NAME', 'shlink'),
|
|
||||||
'user' => env('DB_USER'),
|
|
||||||
'password' => env('DB_PASSWORD'),
|
|
||||||
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
|
|
||||||
'port' => env('DB_PORT', self::DB_PORTS_MAP[$driver]),
|
|
||||||
'unix_socket' => $isMysql ? env('DB_UNIX_SOCKET') : null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNotFoundRedirectsConfig(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO'),
|
|
||||||
'regular_404' => env('REGULAR_404_REDIRECT_TO'),
|
|
||||||
'base_url' => env('BASE_URL_REDIRECT_TO'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitsWebhooks(): array
|
|
||||||
{
|
|
||||||
$webhooks = env('VISITS_WEBHOOKS');
|
|
||||||
return $webhooks === null ? [] : explode(',', $webhooks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRedisConfig(): ?array
|
|
||||||
{
|
|
||||||
$redisServers = env('REDIS_SERVERS');
|
|
||||||
return $redisServers === null ? null : ['servers' => $redisServers];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDefaultShortCodesLength(): int
|
|
||||||
{
|
|
||||||
$value = (int) env('DEFAULT_SHORT_CODES_LENGTH', DEFAULT_SHORT_CODES_LENGTH);
|
|
||||||
return $value < MIN_SHORT_CODES_LENGTH ? MIN_SHORT_CODES_LENGTH : $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMercureConfig(): array
|
|
||||||
{
|
|
||||||
$publicUrl = env('MERCURE_PUBLIC_HUB_URL');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'public_hub_url' => $publicUrl,
|
|
||||||
'internal_hub_url' => env('MERCURE_INTERNAL_HUB_URL', $publicUrl),
|
|
||||||
'jwt_secret' => env('MERCURE_JWT_SECRET'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'delete_short_urls' => [
|
|
||||||
'check_visits_threshold' => true,
|
|
||||||
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
|
||||||
],
|
|
||||||
|
|
||||||
'entity_manager' => [
|
|
||||||
'connection' => $helper->getDbConfig(),
|
|
||||||
],
|
|
||||||
|
|
||||||
'url_shortener' => [
|
|
||||||
'domain' => [
|
|
||||||
'schema' => env('SHORT_DOMAIN_SCHEMA', 'http'),
|
|
||||||
'hostname' => env('SHORT_DOMAIN_HOST', ''),
|
|
||||||
],
|
|
||||||
'validate_url' => (bool) env('VALIDATE_URLS', false),
|
|
||||||
'visits_webhooks' => $helper->getVisitsWebhooks(),
|
|
||||||
'default_short_codes_length' => $helper->getDefaultShortCodesLength(),
|
|
||||||
'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false),
|
|
||||||
'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE),
|
|
||||||
'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME),
|
|
||||||
'append_extra_path' => (bool) env('REDIRECT_APPEND_EXTRA_PATH', false),
|
|
||||||
],
|
|
||||||
|
|
||||||
'tracking' => [
|
|
||||||
'anonymize_remote_addr' => (bool) env('ANONYMIZE_REMOTE_ADDR', true),
|
|
||||||
'track_orphan_visits' => (bool) env('TRACK_ORPHAN_VISITS', true),
|
|
||||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
|
||||||
'disable_tracking' => (bool) env('DISABLE_TRACKING', false),
|
|
||||||
'disable_ip_tracking' => (bool) env('DISABLE_IP_TRACKING', false),
|
|
||||||
'disable_referrer_tracking' => (bool) env('DISABLE_REFERRER_TRACKING', false),
|
|
||||||
'disable_ua_tracking' => (bool) env('DISABLE_UA_TRACKING', false),
|
|
||||||
],
|
|
||||||
|
|
||||||
'not_found_redirects' => $helper->getNotFoundRedirectsConfig(),
|
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'handlers' => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'shlink_handler' => [
|
'destination' => 'php://stderr',
|
||||||
'name' => StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'aliases' => env('REDIS_SERVERS') === null ? [] : [
|
|
||||||
'lock_store' => 'redis_lock_store',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'cache' => [
|
|
||||||
'redis' => $helper->getRedisConfig(),
|
|
||||||
],
|
|
||||||
|
|
||||||
'router' => [
|
|
||||||
'base_path' => env('BASE_PATH', ''),
|
|
||||||
],
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'swoole-http-server' => [
|
|
||||||
'port' => (int) env('PORT', 8080),
|
|
||||||
'options' => [
|
|
||||||
'worker_num' => (int) env('WEB_WORKER_NUM', 16),
|
|
||||||
'task_worker_num' => (int) env('TASK_WORKER_NUM', 16),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'geolite2' => [
|
|
||||||
'license_key' => env('GEOLITE_LICENSE_KEY', 'G4Lm0C60yJsnkdPi'), // Deprecated. Remove hardcoded license on v3
|
|
||||||
],
|
|
||||||
|
|
||||||
'mercure' => $helper->getMercureConfig(),
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,33 +3,36 @@ set -e
|
|||||||
|
|
||||||
cd /etc/shlink
|
cd /etc/shlink
|
||||||
|
|
||||||
echo "Creating fresh database if needed..."
|
# Create data directories if they do not exist. This allows data dir to be mounted as an empty dir if needed
|
||||||
php bin/cli db:create -n -q
|
mkdir -p data/cache data/locks data/log data/proxies
|
||||||
|
|
||||||
echo "Updating database..."
|
flags="--no-interaction --clear-db-cache"
|
||||||
php bin/cli db:migrate -n -q
|
|
||||||
|
|
||||||
echo "Generating proxies..."
|
# Skip downloading GeoLite2 db file if the license key env var was not defined or skipping was explicitly set
|
||||||
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n -q
|
if [ -z "${GEOLITE_LICENSE_KEY}" ] || [ "${SKIP_INITIAL_GEOLITE_DOWNLOAD}" = "true" ]; then
|
||||||
|
flags="${flags} --skip-download-geolite"
|
||||||
echo "Clearing entities cache..."
|
|
||||||
php vendor/doctrine/orm/bin/doctrine.php orm:clear-cache:metadata -n -q
|
|
||||||
|
|
||||||
# Try to download GeoLite2 db file only if the license key env var was defined
|
|
||||||
if [ ! -z "${GEOLITE_LICENSE_KEY}" ]; then
|
|
||||||
echo "Downloading GeoLite2 db file..."
|
|
||||||
php bin/cli visit:download-db -n -q
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Periodicaly run visit:locate every hour
|
# If INITIAL_API_KEY was provided, create an initial API key
|
||||||
# https://shlink.io/documentation/long-running-tasks/#locate-visits
|
if [ -n "${INITIAL_API_KEY}" ]; then
|
||||||
# set env var "ENABLE_PERIODIC_VISIT_LOCATE=true" to enable
|
flags="${flags} --initial-api-key=${INITIAL_API_KEY}"
|
||||||
if [ $ENABLE_PERIODIC_VISIT_LOCATE ]; then
|
fi
|
||||||
echo "Configuring periodic visit locate..."
|
|
||||||
|
php vendor/bin/shlink-installer init ${flags}
|
||||||
|
|
||||||
|
# Periodically run visit:locate every hour, if ENABLE_PERIODIC_VISIT_LOCATE=true was provided and running as root
|
||||||
|
# FIXME: ENABLE_PERIODIC_VISIT_LOCATE is deprecated. Remove cron support in Shlink 4.0.0
|
||||||
|
if [ "${ENABLE_PERIODIC_VISIT_LOCATE}" = "true" ] && [ "${SHLINK_USER_ID}" = "root" ]; then
|
||||||
|
echo "Configuring periodic visit location..."
|
||||||
echo "0 * * * * php /etc/shlink/bin/cli visit:locate -q" > /etc/crontabs/root
|
echo "0 * * * * php /etc/shlink/bin/cli visit:locate -q" > /etc/crontabs/root
|
||||||
/usr/sbin/crond &
|
/usr/sbin/crond &
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# When restarting the container, swoole might think it is already in execution
|
if [ "$SHLINK_RUNTIME" = 'openswoole' ]; then
|
||||||
# This forces the app to be started every second until the exit code is 0
|
# Openswoole is deprecated. Remove in Shlink 4.0.0
|
||||||
until php vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
# When restarting the container, openswoole might think it is already in execution
|
||||||
|
# This forces the app to be started every second until the exit code is 0
|
||||||
|
until php vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||||
|
elif [ "$SHLINK_RUNTIME" = 'rr' ]; then
|
||||||
|
./bin/rr serve -c config/roadrunner/.rr.yml
|
||||||
|
fi
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ The intention is to implement a system that allows adding to API keys as many of
|
|||||||
|
|
||||||
Supporting more restrictions in the future is also desirable.
|
Supporting more restrictions in the future is also desirable.
|
||||||
|
|
||||||
## Considered option
|
## Considered options
|
||||||
|
|
||||||
* Using an ACL/RBAC library, and checking roles in a middleware.
|
* Using an ACL/RBAC library, and checking roles in a middleware.
|
||||||
* Using a service that, provided an API key, tells if certain resource is reachable while it also allows building queries dynamically.
|
* Using a service that, provided an API key, tells if certain resource is reachable while it also allows building queries dynamically.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ However, it does not track visits to any of those, just to valid short URLs.
|
|||||||
|
|
||||||
The intention is to change that, and allow users to track the cases mentioned above.
|
The intention is to change that, and allow users to track the cases mentioned above.
|
||||||
|
|
||||||
## Considered option
|
## Considered options
|
||||||
|
|
||||||
* Create a new table to track visits o this kind.
|
* Create a new table to track visits o this kind.
|
||||||
* Reuse the existing `visits` table, by making `short_url_id` nullable and adding a couple of other fields.
|
* Reuse the existing `visits` table, by making `short_url_id` nullable and adding a couple of other fields.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user