mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
1409 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51de4b17c0 | ||
|
|
615b443652 | ||
|
|
4b7b530f49 | ||
|
|
fa7969c746 | ||
|
|
aef04af4f0 | ||
|
|
f118ea252c | ||
|
|
d514f39a82 | ||
|
|
e17556a7ae | ||
|
|
d79f11eeb8 | ||
|
|
1ec950ee1e | ||
|
|
14ba9fd6a4 | ||
|
|
83e8801827 | ||
|
|
be822646e4 | ||
|
|
3a4a27a60c | ||
|
|
1773e6ecae | ||
|
|
a8e4b2fceb | ||
|
|
15b53ef43c | ||
|
|
11a4702b10 | ||
|
|
6b15cd6d51 | ||
|
|
00169a5729 | ||
|
|
94702791d9 | ||
|
|
447cccacdf | ||
|
|
0413399102 | ||
|
|
9afc7876c4 | ||
|
|
187c17319a | ||
|
|
7310ecd886 | ||
|
|
620cd92d11 | ||
|
|
f9658c8da1 | ||
|
|
613b1d3045 | ||
|
|
d39711ec51 | ||
|
|
69dcab96f8 | ||
|
|
d76c96ad41 | ||
|
|
133efff2cd | ||
|
|
c10f0db170 | ||
|
|
037cd8a389 | ||
|
|
1d24750f43 | ||
|
|
b52ceaff9a | ||
|
|
6b0b52853c | ||
|
|
64d7ac7093 | ||
|
|
b9ba1246d4 | ||
|
|
7f9dc10f6a | ||
|
|
a1afc90150 | ||
|
|
df94c68e2e | ||
|
|
65ea1e00a6 | ||
|
|
5bccdded8a | ||
|
|
8917ed5c2e | ||
|
|
fabc752398 | ||
|
|
38d8086516 | ||
|
|
ae0ff5f23c | ||
|
|
7c659699f3 | ||
|
|
9e6cdcb838 | ||
|
|
7e2f755dfd | ||
|
|
ce2ed237c7 | ||
|
|
626caa4afa | ||
|
|
f4a7712ded | ||
|
|
bab6a3951e | ||
|
|
f49d98f2ea | ||
|
|
1312ea61f4 | ||
|
|
8d90661d0a | ||
|
|
b6b2530cb6 | ||
|
|
e4f66b7ce6 | ||
|
|
4b52c92e97 | ||
|
|
76c42bc17c | ||
|
|
c4f8da5f02 | ||
|
|
80bdeb280a | ||
|
|
99010b6eae | ||
|
|
b2dabf06bf | ||
|
|
67ae05f473 | ||
|
|
fb4fecf411 | ||
|
|
c855f011d1 | ||
|
|
02717eb2fb | ||
|
|
de70ebe769 | ||
|
|
c6109fd396 | ||
|
|
83584a3175 | ||
|
|
f5dcc52b3b | ||
|
|
1901964de1 | ||
|
|
80e9c2452b | ||
|
|
5ad4b39160 | ||
|
|
89b73a9cfa | ||
|
|
e2d8334d69 | ||
|
|
9b16d7acc0 | ||
|
|
6836840746 | ||
|
|
4084d301ca | ||
|
|
added21b18 | ||
|
|
8cd77391cc | ||
|
|
05ebfccc63 | ||
|
|
cb3a690294 | ||
|
|
194a7b0e57 | ||
|
|
98e4d01feb | ||
|
|
c22e3895b5 | ||
|
|
9a76c19615 | ||
|
|
59fa088975 | ||
|
|
163244f40f | ||
|
|
a89b53af4f | ||
|
|
35508e253d | ||
|
|
e586fec338 | ||
|
|
93fa27bdba | ||
|
|
048856c333 | ||
|
|
986f1162dd | ||
|
|
dc8dfa9f0c | ||
|
|
82e7094f3a | ||
|
|
f0e62004d5 | ||
|
|
bbdbafd8db | ||
|
|
6121efec59 | ||
|
|
4fdbcc25a0 | ||
|
|
ca42425b33 | ||
|
|
ce0f61b66d | ||
|
|
13ee71f351 | ||
|
|
c57494d7cd | ||
|
|
d2e74ab330 | ||
|
|
850dde1a06 | ||
|
|
5e83f301ff | ||
|
|
5e74dd7a6d | ||
|
|
8a273e01e9 | ||
|
|
75f6f8dd18 | ||
|
|
e1cf0c4ea7 | ||
|
|
cc134abd12 | ||
|
|
b7db676cba | ||
|
|
3881996560 | ||
|
|
527d28ad81 | ||
|
|
f2371e8a80 | ||
|
|
fd882834d3 | ||
|
|
f92a720d63 | ||
|
|
d6f58698b7 | ||
|
|
d090260b17 | ||
|
|
cd43c1c01f | ||
|
|
284b28e8d9 | ||
|
|
b50547d868 | ||
|
|
401046fbe5 | ||
|
|
6e82509964 | ||
|
|
ab6fa490e5 | ||
|
|
55e2780f50 | ||
|
|
f4803c675c | ||
|
|
90514c603f | ||
|
|
7f4137e7cc | ||
|
|
071cb9af2b | ||
|
|
6ce1550457 | ||
|
|
8cb5d44dc9 | ||
|
|
1331b3f87c | ||
|
|
ab96297e58 | ||
|
|
c4fd3a74c5 | ||
|
|
da922fb2a7 | ||
|
|
4a05c4be40 | ||
|
|
cef30c8e2d | ||
|
|
8417498f08 | ||
|
|
10e941cea6 | ||
|
|
3d7b1ca799 | ||
|
|
b236354fc7 | ||
|
|
6fbb5a380d | ||
|
|
054eb42613 | ||
|
|
6074f4475d | ||
|
|
7afd3fd6a2 | ||
|
|
7d415e40b2 | ||
|
|
3c89d252d2 | ||
|
|
f678873e9f | ||
|
|
17d37a062a | ||
|
|
14702063f2 | ||
|
|
c599d8a0ed | ||
|
|
207d5adceb | ||
|
|
b4c46ce222 | ||
|
|
6fe269193a | ||
|
|
d948543d5c | ||
|
|
a327e6c0a7 | ||
|
|
fbd35b7974 | ||
|
|
b94a22e6a7 | ||
|
|
63ea9e4a21 | ||
|
|
e028d8ea31 | ||
|
|
457a7a14e5 | ||
|
|
cd387328be | ||
|
|
5524476787 | ||
|
|
78526fb405 | ||
|
|
b2dee43bb0 | ||
|
|
60e9443b12 | ||
|
|
ab8fa52ca4 | ||
|
|
16f64f6247 | ||
|
|
98992c656f | ||
|
|
053e026982 | ||
|
|
74180a4381 | ||
|
|
293725f933 | ||
|
|
c33f8d0ea2 | ||
|
|
0f2cd3cb7f | ||
|
|
2441ac5e77 | ||
|
|
f248001460 | ||
|
|
1fe2c93946 | ||
|
|
a3d50605c1 | ||
|
|
5427152f15 | ||
|
|
a4e9c2fdde | ||
|
|
e244b2dc51 | ||
|
|
31dea8fa99 | ||
|
|
be8cf56240 | ||
|
|
0bc7412430 | ||
|
|
6d56e92306 | ||
|
|
97c94f8fcc | ||
|
|
92b5a5296d | ||
|
|
febca6d441 | ||
|
|
bf29abc468 | ||
|
|
97cb30565c | ||
|
|
9809f050ef | ||
|
|
7ecfb24584 | ||
|
|
4aa65f750e | ||
|
|
63c533fa62 | ||
|
|
8751d6c315 | ||
|
|
eb40dc2d5d | ||
|
|
c9d1a955b9 | ||
|
|
c346fd0602 | ||
|
|
a45550b0c6 | ||
|
|
a843c59d77 | ||
|
|
3bfb29a51c | ||
|
|
d8ede3263f | ||
|
|
c36e43e249 | ||
|
|
52150b3067 | ||
|
|
e7796cc917 | ||
|
|
7f560e6a65 | ||
|
|
8f233221e5 | ||
|
|
f700abd65d | ||
|
|
f9e4d6d617 | ||
|
|
d9286765e1 | ||
|
|
a7cde9364a | ||
|
|
070d74830b | ||
|
|
23c07c4e82 | ||
|
|
ab7824aa85 | ||
|
|
67bafbe44e | ||
|
|
c4805b8152 | ||
|
|
33729289c7 | ||
|
|
721e3d9ef9 | ||
|
|
a72e22e046 | ||
|
|
36749658da | ||
|
|
4ad3dc0bc7 | ||
|
|
73864b923d | ||
|
|
71277e979a | ||
|
|
60fef3de74 | ||
|
|
0fe503fa0e | ||
|
|
db02d9f1ba | ||
|
|
89a987d03a | ||
|
|
3284cea6f2 | ||
|
|
df5ad554c1 | ||
|
|
07ae92943d | ||
|
|
175712d4a9 | ||
|
|
3f1b253c31 | ||
|
|
202d0b86b3 | ||
|
|
4e87affb0b | ||
|
|
7f83d37b3c | ||
|
|
09e81b00c5 | ||
|
|
68b77e22c5 | ||
|
|
c5ddd8302a | ||
|
|
1a0fe0429a | ||
|
|
6646232311 | ||
|
|
c1e88c3e83 | ||
|
|
c91a534d1a | ||
|
|
752100f1ce | ||
|
|
dae083c540 | ||
|
|
857c3a4f8d | ||
|
|
acc4c4756e | ||
|
|
0bacb215c5 | ||
|
|
d1a6e60b01 | ||
|
|
8f954151ca | ||
|
|
145d4eaaed | ||
|
|
7673232793 | ||
|
|
f08951a9b9 | ||
|
|
ff963a9df4 | ||
|
|
f30c74b987 | ||
|
|
467dbdd183 | ||
|
|
0e78deb8f2 | ||
|
|
50cc7ae632 | ||
|
|
512d765d60 | ||
|
|
7b9331bd14 | ||
|
|
4f5ce9fb43 | ||
|
|
83f73eb631 | ||
|
|
3f1b89d665 | ||
|
|
8f6fc97fc8 | ||
|
|
a463e6f9d7 | ||
|
|
2a0364ca8f | ||
|
|
23e9ed93bb | ||
|
|
689343d1c9 | ||
|
|
d01dc334d7 | ||
|
|
58a3791a5c | ||
|
|
1a133af141 | ||
|
|
938fb6509e | ||
|
|
d3bfd99210 | ||
|
|
3a1740fdca | ||
|
|
e3de403c6c | ||
|
|
5c1ab02753 | ||
|
|
e5713df008 | ||
|
|
95ea64980b | ||
|
|
c0a77b790d | ||
|
|
e073b4331a | ||
|
|
e919901487 | ||
|
|
13f9f106be | ||
|
|
e9c7053ef5 | ||
|
|
62051c8809 | ||
|
|
0a6a794e23 | ||
|
|
01846657d1 | ||
|
|
dd7545afdf | ||
|
|
9296013596 | ||
|
|
8015c6cc88 | ||
|
|
8c93444286 | ||
|
|
96ed7cae0d | ||
|
|
72c4628b79 | ||
|
|
1117631717 | ||
|
|
60176060cb | ||
|
|
d949b54ef4 | ||
|
|
720db64a03 | ||
|
|
37e0978bfc | ||
|
|
cf355b0b69 | ||
|
|
f2edb54b8b | ||
|
|
13ec27039d | ||
|
|
ad3805a560 | ||
|
|
cc4afa7b62 | ||
|
|
7a6bfed445 | ||
|
|
f2a7b687a9 | ||
|
|
522d021264 | ||
|
|
14a0db1f34 | ||
|
|
430883987a | ||
|
|
f17b641d46 | ||
|
|
48a8290e92 | ||
|
|
46acf4de1c | ||
|
|
17792a1603 | ||
|
|
a8611f5d80 | ||
|
|
deef938e97 | ||
|
|
e014cfa72a | ||
|
|
aa242eba25 | ||
|
|
0ac5569d60 | ||
|
|
7c3e3442c2 | ||
|
|
0f894dcdfe | ||
|
|
361d987f47 | ||
|
|
6017db260a | ||
|
|
f9c9b3d981 | ||
|
|
e7b876f4e6 | ||
|
|
554b948775 | ||
|
|
9bdbb59401 | ||
|
|
377861c5f1 | ||
|
|
26c2aaf567 | ||
|
|
62b54ceaaf | ||
|
|
625eba76c7 | ||
|
|
e12bda3f42 | ||
|
|
0f0301ae5c | ||
|
|
8d1776af98 | ||
|
|
c597738915 | ||
|
|
639329dbe4 | ||
|
|
92b0525b6e | ||
|
|
06306aabd5 | ||
|
|
225905fcdb | ||
|
|
8ca2b3c641 | ||
|
|
ac1737492b | ||
|
|
a63075eb4c | ||
|
|
97e9dfad67 | ||
|
|
17c4f13568 | ||
|
|
3b5243689b | ||
|
|
4d28adf4a7 | ||
|
|
1b14bb07b1 | ||
|
|
3a43aa4d41 | ||
|
|
2340b4f601 | ||
|
|
664886eddf | ||
|
|
d3570dac0b | ||
|
|
1854cc2f19 | ||
|
|
bff4bd12ae | ||
|
|
549c6605f0 | ||
|
|
f50263d2d9 | ||
|
|
c80ec54508 | ||
|
|
a91a560651 | ||
|
|
a931c60230 | ||
|
|
479a331008 | ||
|
|
5d99b1aef0 | ||
|
|
17e0c9176e | ||
|
|
48d7388bdc | ||
|
|
aa01c034db | ||
|
|
9035161b65 | ||
|
|
df57ca5edb | ||
|
|
0511c73cc8 | ||
|
|
a3554eaf74 | ||
|
|
cb0bac55d2 | ||
|
|
bd5d3f6897 | ||
|
|
5e6e386c5a | ||
|
|
e783bdc456 | ||
|
|
316b88cea6 | ||
|
|
c03eea789c | ||
|
|
bd5d3cb6fa | ||
|
|
e1f2dcc136 | ||
|
|
5e6ebfa5a9 | ||
|
|
a7ed14a1c9 | ||
|
|
f88d57b2b6 | ||
|
|
9dbd15bc0c | ||
|
|
0edb3e5c2c | ||
|
|
7501eca71e | ||
|
|
b145d106b0 | ||
|
|
b4386a3508 | ||
|
|
36e2a9387d | ||
|
|
14c68b4bbe | ||
|
|
d6fedaf926 | ||
|
|
8d35c1dde2 | ||
|
|
85b5f760e5 | ||
|
|
1a4a107952 | ||
|
|
e431395a12 | ||
|
|
cfc3d54122 | ||
|
|
d9d6d5bd9c | ||
|
|
32f465f7a6 | ||
|
|
4cddb573a0 | ||
|
|
2cb8486bb3 | ||
|
|
2a782ab60b | ||
|
|
5bde273d59 | ||
|
|
41e322fd47 | ||
|
|
55885b0f25 | ||
|
|
d419b9d62d | ||
|
|
3bdc05fbc4 | ||
|
|
57053d66a4 | ||
|
|
9d8ea0a4f6 | ||
|
|
46354baae9 | ||
|
|
27c48414da | ||
|
|
25b1138000 | ||
|
|
4cf3bc08f9 | ||
|
|
7e093a3fd8 | ||
|
|
abecf3be02 | ||
|
|
3d9b48c5fd | ||
|
|
ba4a66f772 | ||
|
|
ec839183e8 | ||
|
|
b0ec0601c1 | ||
|
|
637d8334f4 | ||
|
|
6db46b50e9 | ||
|
|
f6b1cc7556 | ||
|
|
65a0a90a51 | ||
|
|
38a7872fbf | ||
|
|
5839cc5926 | ||
|
|
49bd230474 | ||
|
|
074f2135f6 | ||
|
|
ef073d59ca | ||
|
|
a3b2f94339 | ||
|
|
b17c576a30 | ||
|
|
bc4156ca3c | ||
|
|
b747b8448e | ||
|
|
aa4b9fc27e | ||
|
|
3f3c2c3d1e | ||
|
|
4b49f8fb7f | ||
|
|
550f3b28ea | ||
|
|
6d4c232345 | ||
|
|
2d085ad6f4 | ||
|
|
3ea83f5cc3 | ||
|
|
b47bd0fc7a | ||
|
|
27e90c4c26 | ||
|
|
ad1a846d8e | ||
|
|
78f75a06df | ||
|
|
262d714751 | ||
|
|
f71c3bba5c | ||
|
|
8b495064b2 | ||
|
|
57a36204db | ||
|
|
7cc1722858 | ||
|
|
af50887361 | ||
|
|
99c8c6c8d4 | ||
|
|
1d7c9fd553 | ||
|
|
274c454fa4 | ||
|
|
453fcc4675 | ||
|
|
42427bfd74 | ||
|
|
33eedd2270 | ||
|
|
edaf9e34f4 | ||
|
|
965325aa7c | ||
|
|
bdf2bbd0f1 | ||
|
|
dc4aab2cab | ||
|
|
3b1f6c69de | ||
|
|
cdf5082cff | ||
|
|
61686ed6ea | ||
|
|
f63b96fd05 | ||
|
|
228bd83b75 | ||
|
|
a21dcb852a | ||
|
|
6558c37b9a | ||
|
|
e6720cce12 | ||
|
|
22d039c550 | ||
|
|
a21fcd72ce | ||
|
|
058391cf06 | ||
|
|
24e6acc6e8 | ||
|
|
8e3508f28d | ||
|
|
e72b424968 | ||
|
|
56d299a7dc | ||
|
|
575e6bf707 | ||
|
|
e50c21440f | ||
|
|
7cff11080d | ||
|
|
72381f9844 | ||
|
|
7c649e7497 | ||
|
|
eff308cd43 | ||
|
|
bd3745118e | ||
|
|
602ebef02a | ||
|
|
9040937376 | ||
|
|
a11be5b2ff | ||
|
|
6351d0b87d | ||
|
|
fae3434393 | ||
|
|
4013ae87dd | ||
|
|
cb4ba58b08 | ||
|
|
8c94452348 | ||
|
|
ea96a00b12 | ||
|
|
be26dd58c3 | ||
|
|
eaba5edf7f | ||
|
|
12da04ef37 | ||
|
|
8b03532ddb | ||
|
|
112b54ec7d | ||
|
|
ee6a8ede0a | ||
|
|
07ce5f05a2 | ||
|
|
7b04016ca2 | ||
|
|
b6792d3fb8 | ||
|
|
2f0d658432 | ||
|
|
8c1865c3ec | ||
|
|
096d2098d6 | ||
|
|
882d64ae11 | ||
|
|
3352bcd186 | ||
|
|
9743c1624d | ||
|
|
e85d59c5a4 | ||
|
|
ac0ff8fb94 | ||
|
|
90f93ee4ec | ||
|
|
794d926e3a | ||
|
|
bd41ebef9f | ||
|
|
725370704f | ||
|
|
f03b7689ce | ||
|
|
fb31e2a5e4 | ||
|
|
d688c6da7e | ||
|
|
618784dc3b | ||
|
|
9d64d4ed1d | ||
|
|
7f02243c6c | ||
|
|
3916c68126 | ||
|
|
a6f0c66331 | ||
|
|
bdfb220126 | ||
|
|
abcf2f86be | ||
|
|
a4d8ebdfc9 | ||
|
|
b51c149c30 | ||
|
|
39095a3098 | ||
|
|
765199727e | ||
|
|
c7043af853 | ||
|
|
02a8ef7dd9 | ||
|
|
6bb8c1b2f5 | ||
|
|
3cf253fd0f | ||
|
|
0365728337 | ||
|
|
b8143a5bb4 | ||
|
|
531a19dde9 | ||
|
|
69ff7de481 | ||
|
|
ffc0555c7c | ||
|
|
84a7981dfa | ||
|
|
2573c2bf98 | ||
|
|
3b4c1501f3 | ||
|
|
e836bedecc | ||
|
|
a797b74a70 | ||
|
|
ab497403ca | ||
|
|
d4dea9a1d2 | ||
|
|
28d93ea5e0 | ||
|
|
e6a31b16ed | ||
|
|
9553192281 | ||
|
|
74069f2d24 | ||
|
|
b4b00a57c1 | ||
|
|
a516ef691d | ||
|
|
e80b7448f5 | ||
|
|
f129544f83 | ||
|
|
9fa291a32f | ||
|
|
d06e92ffc2 | ||
|
|
1b83344995 | ||
|
|
cf49393ef2 | ||
|
|
f2ecbceae9 | ||
|
|
c582eba753 | ||
|
|
de86b62cdd | ||
|
|
73150471e9 | ||
|
|
ec751f4ac2 | ||
|
|
e652166289 | ||
|
|
a671d555cb | ||
|
|
6240554f4c | ||
|
|
4ee9c9bbe3 | ||
|
|
c830439085 | ||
|
|
f2196583c8 | ||
|
|
3dbca2115c | ||
|
|
b45d8de27d | ||
|
|
3ba46bbbfa | ||
|
|
06f3f0c86c | ||
|
|
06f07e3e40 | ||
|
|
740740b8c6 | ||
|
|
b6ed39b18b | ||
|
|
958c4704f8 | ||
|
|
ef075fb0ce | ||
|
|
556520583a | ||
|
|
399c56a097 | ||
|
|
f078d95588 | ||
|
|
33911afcd6 | ||
|
|
ae8d31e83f | ||
|
|
72c4052012 | ||
|
|
f713a1fa7e | ||
|
|
62488ac4e5 | ||
|
|
ab4c6e5fca | ||
|
|
26f4a969c9 | ||
|
|
703965915d | ||
|
|
24e38a3cf9 | ||
|
|
b12cfaedf3 | ||
|
|
71807e698c | ||
|
|
1d155298c1 | ||
|
|
4dfc5ae681 | ||
|
|
26f237069c | ||
|
|
b6e1c65c4c | ||
|
|
11f94b8306 | ||
|
|
01bcedef7a | ||
|
|
e51384fcc0 | ||
|
|
83c53c8b2e | ||
|
|
1afe08caed | ||
|
|
7289833928 | ||
|
|
f4d10df0f3 | ||
|
|
652b0df054 | ||
|
|
0e9ea5027c | ||
|
|
658303d375 | ||
|
|
ccc3a4b584 | ||
|
|
ef5ac86e0a | ||
|
|
91b90b276a | ||
|
|
85c32c3c9a | ||
|
|
40838255a7 | ||
|
|
a67ccb384f | ||
|
|
cb31e5a581 | ||
|
|
3c12a55872 | ||
|
|
6da8b11674 | ||
|
|
552489611f | ||
|
|
e48d0f4f0c | ||
|
|
49b6063501 | ||
|
|
dd049feb40 | ||
|
|
76a86c452e | ||
|
|
41aec15fab | ||
|
|
245cb0e35d | ||
|
|
7a0b1e8494 | ||
|
|
70c1c9f018 | ||
|
|
97e965157b | ||
|
|
04bbd471ff | ||
|
|
650a286982 | ||
|
|
ad44a8441a | ||
|
|
b339cf2429 | ||
|
|
9cd97c2f1e | ||
|
|
a7f6b60cba | ||
|
|
0d7dc50670 | ||
|
|
4bc5b9261f | ||
|
|
fb572d5abb | ||
|
|
8fa4219b30 | ||
|
|
a52d0cd419 | ||
|
|
0080ab5132 | ||
|
|
8afa582aa5 | ||
|
|
d847c7648e | ||
|
|
c140db16d1 | ||
|
|
adbf7c6f5e | ||
|
|
5cec697be3 | ||
|
|
587bbfdd73 | ||
|
|
b3a2ceedea | ||
|
|
621f18bf40 | ||
|
|
99c1a59dd4 | ||
|
|
3a149c9edc | ||
|
|
fdaf5fb2f3 | ||
|
|
2f83e90c8b | ||
|
|
05acd4ae88 | ||
|
|
87007677ed | ||
|
|
4ee0032c2a | ||
|
|
06583a0bc1 | ||
|
|
024c9c1a7a | ||
|
|
f3855dbc6f | ||
|
|
758dac47c3 | ||
|
|
81393a76b4 | ||
|
|
9949bb654d | ||
|
|
b0b9902f40 | ||
|
|
5aa8de11f4 | ||
|
|
b18c9e495f | ||
|
|
d3590234a3 | ||
|
|
39adef8ab8 | ||
|
|
13e443880a | ||
|
|
45961144b9 | ||
|
|
34129b8d24 | ||
|
|
48bd97fe41 | ||
|
|
b1b67c497e | ||
|
|
237fb95b4b | ||
|
|
c1b7c6ba6c | ||
|
|
d8add9291f | ||
|
|
a93edf158e | ||
|
|
fdadf3ba07 | ||
|
|
3e26f1113d | ||
|
|
822652cac3 | ||
|
|
1447687ebe | ||
|
|
12150f775d | ||
|
|
5f2f179581 | ||
|
|
407134bab1 | ||
|
|
de5b895fad | ||
|
|
80e3f01562 | ||
|
|
6904dcfed0 | ||
|
|
21863e8de6 | ||
|
|
d75be372cb | ||
|
|
edaf999bf5 | ||
|
|
3e98485c8b | ||
|
|
cc292886a6 | ||
|
|
0c1b36d0d4 | ||
|
|
a06957e9fa | ||
|
|
390bc59d99 | ||
|
|
85464f0fbb | ||
|
|
42f7a68ba5 | ||
|
|
e3397a7c90 | ||
|
|
46b4a21617 | ||
|
|
fc0aba6311 | ||
|
|
0b96a79c41 | ||
|
|
a5929ebb29 | ||
|
|
ce9ec0d738 | ||
|
|
961178fd82 | ||
|
|
49c73a9590 | ||
|
|
92c80e7833 | ||
|
|
6d5bce0078 | ||
|
|
112cbb9039 | ||
|
|
812c5f4993 | ||
|
|
921f303404 | ||
|
|
e0a9f8120c | ||
|
|
8ecc241a4b | ||
|
|
30e34151ed | ||
|
|
d734578f74 | ||
|
|
37c8328eed | ||
|
|
e71f6bb528 | ||
|
|
f7ae52f86e | ||
|
|
067d1cc41c | ||
|
|
b97af7efb9 | ||
|
|
fd0ecc05b2 | ||
|
|
5b934c3f9a | ||
|
|
c7a2f499e0 | ||
|
|
713f7e7bc9 | ||
|
|
09078e4c6a | ||
|
|
1f66ec2af5 | ||
|
|
936e5b3b86 | ||
|
|
99f28b569b | ||
|
|
0c83dea8b7 | ||
|
|
30edfdbdc5 | ||
|
|
60ef98b836 | ||
|
|
73c8b53882 | ||
|
|
425d8f0a3f | ||
|
|
92a83b82a0 | ||
|
|
d1ec15febf | ||
|
|
dd345c82ea | ||
|
|
2bf3e6a13b | ||
|
|
0b04476c99 | ||
|
|
229dc93132 | ||
|
|
0952c488be | ||
|
|
c4f28b3a32 | ||
|
|
201f25e0ad | ||
|
|
0c3523c34a | ||
|
|
0d7a0ee9ea | ||
|
|
931bdb0cd7 | ||
|
|
8807a78463 | ||
|
|
d832133410 | ||
|
|
cdde59b543 | ||
|
|
463dfe9729 | ||
|
|
805c8c87ba | ||
|
|
7ba2cfc010 | ||
|
|
40794c476f | ||
|
|
c3ab871366 | ||
|
|
42a5296f93 | ||
|
|
183db4ff80 | ||
|
|
0bc9bd9281 | ||
|
|
9bed7ef156 | ||
|
|
8f68e4b9f5 | ||
|
|
6589c8fce6 | ||
|
|
38b313a25d | ||
|
|
dab0ebeb99 | ||
|
|
27bf7220b9 | ||
|
|
e68ef87c66 | ||
|
|
29b747c192 | ||
|
|
2047d6b772 | ||
|
|
71e7938b7a | ||
|
|
6bce219eb3 | ||
|
|
dfcac525bc | ||
|
|
da307aee0a | ||
|
|
edf2b5b4c2 | ||
|
|
f41d947cf7 | ||
|
|
54bc169525 | ||
|
|
05d55c4000 | ||
|
|
739f5eb421 | ||
|
|
0aab1bdc4e | ||
|
|
47f99cf6cc | ||
|
|
55c9773a02 | ||
|
|
4b66aaba5c | ||
|
|
4223408090 | ||
|
|
58e6b0b683 | ||
|
|
891438c672 | ||
|
|
910864eaaf | ||
|
|
598c0757be | ||
|
|
01e0a95e14 | ||
|
|
f459a99e7e | ||
|
|
85e18a4754 | ||
|
|
1650499a38 | ||
|
|
51f243995a | ||
|
|
aeafb244d9 | ||
|
|
142417dda1 | ||
|
|
da658185c3 | ||
|
|
ef82158368 | ||
|
|
083ccd36b7 | ||
|
|
d61c79da84 | ||
|
|
8f76c3e202 | ||
|
|
23aa7a015c | ||
|
|
674a4416cf | ||
|
|
db85915c2f | ||
|
|
dfc8e8d74e | ||
|
|
b2b424a4ed | ||
|
|
3433899577 | ||
|
|
b1f814e118 | ||
|
|
7aa6afeb30 | ||
|
|
d414496a3c | ||
|
|
d4684fd01f | ||
|
|
bb444a02fe | ||
|
|
e980a8d121 | ||
|
|
f493baaf2b | ||
|
|
28f26920dd | ||
|
|
69e994c067 | ||
|
|
656083cb6f | ||
|
|
ab9ea887d2 | ||
|
|
9ac6a50e66 | ||
|
|
acc9cb94b5 | ||
|
|
01829c82ee | ||
|
|
9c02ea8799 | ||
|
|
d202538581 | ||
|
|
a84b642ba5 | ||
|
|
74176c298f | ||
|
|
91e21441f7 | ||
|
|
896b7f2d73 | ||
|
|
66ed152358 | ||
|
|
257134cd80 | ||
|
|
a4373aee91 | ||
|
|
7442905873 | ||
|
|
d3af51f684 | ||
|
|
04419a7242 | ||
|
|
a45d6e6b44 | ||
|
|
37b1306eb3 | ||
|
|
cff6573767 | ||
|
|
a2f34e02ad | ||
|
|
796543d194 | ||
|
|
3b25fb27fe | ||
|
|
3b20f955ff | ||
|
|
c81ae9c40d | ||
|
|
7ceae7af87 | ||
|
|
5e02cfe375 | ||
|
|
6e836b5fd9 | ||
|
|
8753e3a77f | ||
|
|
6a2227efc5 | ||
|
|
1fbcea7a06 | ||
|
|
168c839cf1 | ||
|
|
162e913cc4 | ||
|
|
5aaf50d68e | ||
|
|
d2f5be1d18 | ||
|
|
36ab455a49 | ||
|
|
ee8cab8455 | ||
|
|
bd884e85d4 | ||
|
|
5ceb6fb740 | ||
|
|
0d6155e8bc | ||
|
|
a78c59c11a | ||
|
|
173420c608 | ||
|
|
10b0ec301b | ||
|
|
1706a869d9 | ||
|
|
d0393799d2 | ||
|
|
739433ba8b | ||
|
|
a15e9c29c8 | ||
|
|
d58f89aa26 | ||
|
|
b7671f70da | ||
|
|
52366b9dd4 | ||
|
|
32417e40cb | ||
|
|
4cb44be9a0 | ||
|
|
a484455b0b | ||
|
|
4b3ed2b7ba | ||
|
|
e2986a7b4c | ||
|
|
82e04800aa | ||
|
|
5d367da626 | ||
|
|
59de5a5f55 | ||
|
|
0855104068 | ||
|
|
8c6f97c4e2 | ||
|
|
2d16856582 | ||
|
|
41e903cf26 | ||
|
|
4872bd3a92 | ||
|
|
8b675f55cc | ||
|
|
acda7f02c6 | ||
|
|
184ff90b9f | ||
|
|
d8be3c28cb | ||
|
|
3d358ab046 | ||
|
|
960bdfc232 | ||
|
|
101b4daff4 | ||
|
|
13431ff8cf | ||
|
|
4cdcad29df | ||
|
|
a4c34ff7be | ||
|
|
2b7b5e9a8f | ||
|
|
58db902084 | ||
|
|
983e3c9eaa | ||
|
|
dbe35cf567 | ||
|
|
8298f9d491 | ||
|
|
16a951b938 | ||
|
|
51fcbfb3c2 | ||
|
|
e01e370d16 | ||
|
|
736ac8ba90 | ||
|
|
d07104b8d9 | ||
|
|
cad53e397a | ||
|
|
3608a6d068 | ||
|
|
92ddd2eebe | ||
|
|
bf0b58b344 | ||
|
|
ff543b151c | ||
|
|
d842025835 | ||
|
|
230e56370a | ||
|
|
a8514a9ae4 | ||
|
|
148f7a9cfe | ||
|
|
29d50cabc2 | ||
|
|
a8f8297131 | ||
|
|
cd4b632d75 | ||
|
|
843754b7e7 | ||
|
|
847cc2bc50 | ||
|
|
751bd15785 | ||
|
|
c12db7567e | ||
|
|
e8069a10ba | ||
|
|
9742bf13e4 | ||
|
|
6441707c76 | ||
|
|
23bcba4fd9 | ||
|
|
9049a205b7 | ||
|
|
8cfa0b595c | ||
|
|
4b958e8b87 | ||
|
|
bcd5d2848d | ||
|
|
b59cbeceac | ||
|
|
46f948a584 | ||
|
|
14bf3a134b | ||
|
|
1557438fdf | ||
|
|
27b680e0cd | ||
|
|
14314ef939 | ||
|
|
bf5c168d7d | ||
|
|
1e0791416d | ||
|
|
ab8d42b609 | ||
|
|
96dbdbe7c9 | ||
|
|
6f135ad6ab | ||
|
|
5b9a1e1978 | ||
|
|
4ba3522e79 | ||
|
|
d3faa22b78 | ||
|
|
1daad334a5 | ||
|
|
3dda49dab4 | ||
|
|
c6c4e5580b | ||
|
|
3f808e3813 | ||
|
|
e5107c40f9 | ||
|
|
0871ca884e | ||
|
|
62ce9311bf | ||
|
|
70b15a7ab0 | ||
|
|
708bff20f0 | ||
|
|
369628ee95 | ||
|
|
0c6f8f1136 | ||
|
|
9f9d011d46 | ||
|
|
e28b73c130 | ||
|
|
56f953ab2f | ||
|
|
3ad8be175c | ||
|
|
f5f990511c | ||
|
|
1e3ccba503 | ||
|
|
a842b5b7cd | ||
|
|
909e42b0be | ||
|
|
c8acb5de68 | ||
|
|
53b9e3ddc1 | ||
|
|
68e1c61e7f | ||
|
|
8605b35b57 | ||
|
|
36680e82aa | ||
|
|
83b7d5a5f1 | ||
|
|
fe41e9d573 | ||
|
|
d76e6647d2 | ||
|
|
6f17f70137 | ||
|
|
ef01754ad5 | ||
|
|
eab9347522 | ||
|
|
59bcd62717 | ||
|
|
3f01fad12f | ||
|
|
c7f0d14c1b | ||
|
|
2408829627 | ||
|
|
8d244c8d34 | ||
|
|
42af057316 | ||
|
|
8f68078835 | ||
|
|
0c34032fd3 | ||
|
|
20f457a3e9 | ||
|
|
39693ca1fe | ||
|
|
784908420e | ||
|
|
9685929824 | ||
|
|
fe4b2c4ae4 | ||
|
|
5f87bb13f8 | ||
|
|
a87f6c6709 | ||
|
|
da3ee6b65e | ||
|
|
c5eda37bda | ||
|
|
1966367caf | ||
|
|
eed7b6e565 | ||
|
|
0e54ed691d | ||
|
|
997289da02 | ||
|
|
c841e57db5 | ||
|
|
f5138385be | ||
|
|
63ceba199d | ||
|
|
e6ee4ceae2 | ||
|
|
19a9d815eb | ||
|
|
5b78b363f0 | ||
|
|
b078c00492 | ||
|
|
e712efd008 | ||
|
|
ab27c0ce53 | ||
|
|
d97cabbe79 | ||
|
|
c3c7ffad25 | ||
|
|
fe4329d730 | ||
|
|
c53ba7b119 | ||
|
|
025eec6c70 | ||
|
|
40e1670314 | ||
|
|
2bca260627 | ||
|
|
463d8e8950 | ||
|
|
e2eed8a728 | ||
|
|
f97effcfe0 | ||
|
|
2cf21ab3bd | ||
|
|
7daa602630 | ||
|
|
7b637d6a61 | ||
|
|
a4f979be08 | ||
|
|
8852739111 | ||
|
|
2099ea16ec | ||
|
|
a739eb6d60 | ||
|
|
529ddacafe | ||
|
|
f71c95b74a | ||
|
|
8260a0843b | ||
|
|
bfbeb7b1fb | ||
|
|
df70810aa6 | ||
|
|
aca5804f98 | ||
|
|
b7f7288a4b | ||
|
|
d54a2bde0f | ||
|
|
679bb8d357 | ||
|
|
ca515998e4 | ||
|
|
c5b6d203f5 | ||
|
|
86159c5d86 | ||
|
|
846802c003 | ||
|
|
e9ec32b3c3 | ||
|
|
4882bec118 | ||
|
|
89ff259be0 | ||
|
|
60ece7fbf7 | ||
|
|
0c110f574a | ||
|
|
dbca5b2a7e | ||
|
|
3088298e6b | ||
|
|
a9c6a12182 | ||
|
|
fa5b512629 | ||
|
|
5c2061a6e6 | ||
|
|
cf0fc956c9 | ||
|
|
a0517dfbeb | ||
|
|
39c71638e6 | ||
|
|
672b728379 | ||
|
|
750a546faf | ||
|
|
a41835573b | ||
|
|
2650cb89b5 | ||
|
|
4a122e0209 | ||
|
|
ce4bf62d75 | ||
|
|
40bbcb3250 | ||
|
|
905f51fbd0 | ||
|
|
cd4fe4362b | ||
|
|
ed7be6eb99 | ||
|
|
555007ab16 | ||
|
|
bd31b99324 | ||
|
|
60237c3c0b | ||
|
|
eb21833d94 | ||
|
|
763002ae14 | ||
|
|
ae2dc39a78 | ||
|
|
fe4ced2709 | ||
|
|
9075d68b7c | ||
|
|
759c0ea957 | ||
|
|
67b393d4a3 | ||
|
|
de71821759 | ||
|
|
0c2bcaee34 | ||
|
|
1613975e0e | ||
|
|
be82204df2 | ||
|
|
14c2ff5545 | ||
|
|
d7d0e11f2c | ||
|
|
6654f45cb8 | ||
|
|
23f92179ad | ||
|
|
7377917642 | ||
|
|
0f796859f2 | ||
|
|
6383230678 | ||
|
|
51536f8746 | ||
|
|
e3b6c061c4 | ||
|
|
4bd3fa74d1 | ||
|
|
71553988d5 | ||
|
|
761b24e614 | ||
|
|
10974902b5 | ||
|
|
474407dbc2 | ||
|
|
95d84f354d | ||
|
|
db47a9a253 | ||
|
|
709a4639b3 | ||
|
|
28b9cd02ef | ||
|
|
af9ea13933 | ||
|
|
bd2cd18916 | ||
|
|
23138dc0b4 | ||
|
|
a2f9742cfc | ||
|
|
6378e614b0 | ||
|
|
b116a57aa7 | ||
|
|
a03f32f521 | ||
|
|
b9180be685 | ||
|
|
334aee64ad | ||
|
|
16bd368a58 | ||
|
|
3266a0f85c | ||
|
|
4629f1b03f | ||
|
|
fbd0c6cbea | ||
|
|
8260051c30 | ||
|
|
c061c9c3ff | ||
|
|
8961191b2e | ||
|
|
fc0d99be41 | ||
|
|
6834e72c4a | ||
|
|
efe655f880 | ||
|
|
3d5ddce621 | ||
|
|
a3de3e15cb | ||
|
|
619999d4f8 | ||
|
|
7acf27dd38 | ||
|
|
ba517eeeb5 | ||
|
|
fdd3e24967 | ||
|
|
a570ce202a | ||
|
|
0a220bbc7a | ||
|
|
e0e511f56d | ||
|
|
d375dece0e | ||
|
|
f801f265ed | ||
|
|
1b4fc89b07 | ||
|
|
3ac2b77bf0 | ||
|
|
b2ca4ad66b | ||
|
|
25a7c7bc7f | ||
|
|
6b009a4de4 | ||
|
|
0b80a86e88 | ||
|
|
b03f24d59a | ||
|
|
78ea13d366 | ||
|
|
8c2bdfba1c | ||
|
|
3289968a93 | ||
|
|
73ae754aa7 | ||
|
|
20a6e7e210 | ||
|
|
4cf433a994 | ||
|
|
e36c4d397c | ||
|
|
26037327f9 | ||
|
|
da6aa1d697 | ||
|
|
dada6aa3d1 | ||
|
|
fa5ebb1677 | ||
|
|
f071df325d | ||
|
|
3c042c4011 | ||
|
|
7e8109caa3 | ||
|
|
d3add6d8e4 | ||
|
|
1b089749c0 | ||
|
|
791d6b7e57 | ||
|
|
233bb603cf | ||
|
|
db8a816524 | ||
|
|
eff50ca202 | ||
|
|
ceabb5ab2c | ||
|
|
122c2fd5e6 | ||
|
|
cd27a72982 | ||
|
|
19b0f0d7dc | ||
|
|
6ce2049935 | ||
|
|
53b937be63 | ||
|
|
71c8f99dab | ||
|
|
9eb3fca726 | ||
|
|
019bd4dec8 | ||
|
|
be1ce06c00 | ||
|
|
074bfe3db2 | ||
|
|
34e72b42dc | ||
|
|
97d24d76d8 | ||
|
|
4d1af867a4 | ||
|
|
fc6b4c12b2 | ||
|
|
405c6de591 | ||
|
|
47bfa5fcc0 | ||
|
|
67d91d5fc5 | ||
|
|
f832c56adb | ||
|
|
1aa9ae680e | ||
|
|
c4b30db82d | ||
|
|
abd9f3c6be | ||
|
|
3de3594282 | ||
|
|
ed5816d464 | ||
|
|
3d43bdbb49 | ||
|
|
1ab492ce5b | ||
|
|
de30c6ad79 | ||
|
|
f5a48ff98d | ||
|
|
8493ee5b83 | ||
|
|
52a6d55e5d | ||
|
|
7142295aa5 | ||
|
|
8b65be26a6 | ||
|
|
60f5deb494 | ||
|
|
0fc09e6dd3 | ||
|
|
0c4ccf4e3e | ||
|
|
a0e79bf446 | ||
|
|
aa356ad7c7 | ||
|
|
9e0e384d46 | ||
|
|
a20b99e643 | ||
|
|
fe4237b2b1 | ||
|
|
4146835f6f | ||
|
|
5201ea4516 | ||
|
|
fba7b36245 | ||
|
|
353ac0fc0c | ||
|
|
00002b1e24 | ||
|
|
aa32830671 | ||
|
|
12b8100d89 | ||
|
|
72e56d271d | ||
|
|
2b69f5eff4 | ||
|
|
f224bb98c4 | ||
|
|
ec17eb3fbc | ||
|
|
358b600713 | ||
|
|
aacb5c39ba | ||
|
|
9ae8804095 | ||
|
|
2d51bd895d | ||
|
|
18f656fed2 | ||
|
|
eea76999b2 | ||
|
|
bd495adf22 | ||
|
|
e8c7bee924 | ||
|
|
24b06c24dc | ||
|
|
6fdd764a35 | ||
|
|
2400d1f265 | ||
|
|
cdef430b0b | ||
|
|
6074e4ae2c | ||
|
|
6ada704bc3 | ||
|
|
e8f7daac6f | ||
|
|
404455928e | ||
|
|
bca3e62ced | ||
|
|
e79391907a | ||
|
|
54a23cc7fa | ||
|
|
e8ebe77923 | ||
|
|
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 | ||
|
|
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 | ||
|
|
a667c957ee | ||
|
|
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 | ||
|
|
5e781a9010 | ||
|
|
277d817429 | ||
|
|
970f202757 | ||
|
|
2c6b2b47a4 | ||
|
|
5c8be4b21f | ||
|
|
558a4a2b30 | ||
|
|
203ad7d594 | ||
|
|
04cf1aed9c | ||
|
|
8c14526f85 | ||
|
|
1ff241411b |
@@ -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
|
||||||
@@ -17,8 +19,7 @@ indocker
|
|||||||
docker-*
|
docker-*
|
||||||
phpstan.neon
|
phpstan.neon
|
||||||
php*xml*
|
php*xml*
|
||||||
infection*
|
|
||||||
**/test*
|
**/test*
|
||||||
build*
|
build*
|
||||||
**/.*
|
**/.*
|
||||||
bin/helper
|
!config/roadrunner/.rr.yml
|
||||||
|
|||||||
49
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
49
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
title: 'Help wanted'
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Shlink version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: PHP version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: How do you serve Shlink
|
||||||
|
options:
|
||||||
|
- Self-hosted Apache
|
||||||
|
- Self-hosted nginx
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Docker image
|
||||||
|
- Other (explain in summary)
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database engine
|
||||||
|
options:
|
||||||
|
- MySQL
|
||||||
|
- MariaDB
|
||||||
|
- PostgreSQL
|
||||||
|
- MicrosoftSQL
|
||||||
|
- SQLite
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
value: '<!-- Describe your issue, question or request here. -->'
|
||||||
|
|
||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
github: ['acelaya']
|
github: ['acelaya']
|
||||||
custom: ['https://acel.me/donate']
|
custom: ['https://slnk.to/donate']
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
I'm always happy to help and provide support, but some understanding will be expected.
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personally if an issue gets eventually closed.
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||||
-->
|
-->
|
||||||
|
|||||||
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Something on shlink is broken or not working as documented?
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### How Shlink is set-up
|
|
||||||
|
|
||||||
* Shlink Version: x.y.z
|
|
||||||
* PHP Version: x.y.z
|
|
||||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted 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. -->
|
|
||||||
71
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
71
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Something on Shlink is broken or not working as documented?
|
||||||
|
labels: ['bug']
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Shlink version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: PHP version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: How do you serve Shlink
|
||||||
|
options:
|
||||||
|
- Self-hosted Apache
|
||||||
|
- Self-hosted nginx
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Docker image
|
||||||
|
- Other (explain in summary)
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database engine
|
||||||
|
options:
|
||||||
|
- MySQL
|
||||||
|
- MariaDB
|
||||||
|
- PostgreSQL
|
||||||
|
- MicrosoftSQL
|
||||||
|
- SQLite
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Current behavior
|
||||||
|
value: '<!-- How is it actually behaving (and it should not)? -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
value: '<!-- How did you expect it to behave? -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Minimum steps to reproduce
|
||||||
|
value: |
|
||||||
|
<!--
|
||||||
|
Simple but detailed way to reproduce the bug:
|
||||||
|
|
||||||
|
* Avoid things like "create a kubernetes cluster", or anything related with cloud providers, as that is rarely the root cause.
|
||||||
|
* Avoid too vague steps or one-liners like "Update from v1 to v2".
|
||||||
|
* Providing the reproduction in the form of a self-contained docker-composer is desirable.
|
||||||
|
|
||||||
|
Failing in any of these will cause the issue to be closed as "not reproducible".
|
||||||
|
-->
|
||||||
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Do you find shlink is missing some important feature that would make it more useful?
|
|
||||||
labels: feature
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Describe the new feature you would like to request. -->
|
|
||||||
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Do you find Shlink is missing some important feature that would make it more useful?
|
||||||
|
labels: ['feature']
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
value: '<!-- Describe the new feature you would like to request. -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Use case
|
||||||
|
value: '<!-- Explain why do you think this feature would be useful, and what problems would it help to solve. -->'
|
||||||
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: Question - Support
|
|
||||||
about: Do you have a problem setting up or using shlink?
|
|
||||||
labels: question
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### How Shlink is set-up
|
|
||||||
|
|
||||||
* Shlink Version: x.y.z
|
|
||||||
* PHP Version: x.y.z
|
|
||||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted 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
|
||||||
48
.github/actions/ci-setup/action.yml
vendored
Normal file
48
.github/actions/ci-setup/action.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: CI setup
|
||||||
|
description: 'Sets up the environment to run CI actions for Shlink'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
install-deps:
|
||||||
|
description: 'Tells if dependencies should be installed with composer. Default value is "yes"'
|
||||||
|
required: true
|
||||||
|
default: 'yes'
|
||||||
|
php-version:
|
||||||
|
description: 'The PHP version to be setup'
|
||||||
|
required: true
|
||||||
|
php-extensions:
|
||||||
|
description: 'The PHP extensions to install'
|
||||||
|
required: false
|
||||||
|
extensions-cache-key:
|
||||||
|
description: 'The key used to cache PHP extensions. If empty value is provided, extension caching is disabled'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Setup cache environment
|
||||||
|
if: ${{ inputs.php-extensions }}
|
||||||
|
id: extcache
|
||||||
|
uses: shivammathur/cache-extensions@v1
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
key: ${{ inputs.extensions-cache-key }}
|
||||||
|
- name: Cache extensions
|
||||||
|
if: ${{ inputs.php-extensions }}
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.extcache.outputs.dir }}
|
||||||
|
key: ${{ steps.extcache.outputs.key }}
|
||||||
|
restore-keys: ${{ steps.extcache.outputs.key }}
|
||||||
|
- name: Use PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
tools: composer
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
coverage: 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.4' && '--ignore-platform-req=php' || '' }}
|
||||||
|
shell: bash
|
||||||
45
.github/workflows/ci-db-tests.yml
vendored
Normal file
45
.github/workflows/ci-db-tests.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Database tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of sqlite:ci, mysql, maria, postgres or ms
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
db-tests:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3', '8.4']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.4' }}
|
||||||
|
env:
|
||||||
|
LC_ALL: C
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install MSSQL ODBC
|
||||||
|
if: ${{ inputs.platform == 'ms' }}
|
||||||
|
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
||||||
|
- name: Start database server
|
||||||
|
if: ${{ inputs.platform != 'sqlite:ci' }}
|
||||||
|
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ inputs.platform }}
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: pdo_sqlsrv-5.12.0
|
||||||
|
extensions-cache-key: db-tests-extensions-${{ matrix.php-version }}-${{ inputs.platform }}
|
||||||
|
- name: Create test database
|
||||||
|
if: ${{ inputs.platform == 'ms' }}
|
||||||
|
run: docker compose exec -T shlink_db_ms /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||||
|
- name: Run tests
|
||||||
|
run: composer test:db:${{ inputs.platform }}
|
||||||
|
- name: Upload code coverage
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ matrix.php-version == '8.3' && inputs.platform == 'sqlite:ci' }}
|
||||||
|
with:
|
||||||
|
name: coverage-db
|
||||||
|
path: |
|
||||||
|
build/coverage-db
|
||||||
|
build/coverage-db.cov
|
||||||
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-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- run: docker build -t shlink-docker-image:temp .
|
||||||
42
.github/workflows/ci-tests.yml
vendored
Normal file
42
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3', '8.4']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.4' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # rr get-binary picks this env automatically
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Start postgres database server
|
||||||
|
if: ${{ inputs.test-group == 'api' }}
|
||||||
|
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||||
|
- name: Start maria database server
|
||||||
|
if: ${{ inputs.test-group == 'cli' }}
|
||||||
|
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- name: Download RoadRunner binary
|
||||||
|
if: ${{ inputs.test-group == 'api' }}
|
||||||
|
run: ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr
|
||||||
|
- run: composer test:${{ inputs.test-group }}:ci
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ matrix.php-version == '8.3' }}
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: |
|
||||||
|
build/coverage-${{ inputs.test-group }}
|
||||||
|
build/coverage-${{ inputs.test-group }}.cov
|
||||||
192
.github/workflows/ci.yml
vendored
192
.github/workflows/ci.yml
vendored
@@ -1,188 +1,102 @@
|
|||||||
name: Continuous integration
|
name: Continuous integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request: null
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.neon'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- develop
|
- develop
|
||||||
|
- 2.x
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.neon'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
static-analysis:
|
static-analysis:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.3']
|
||||||
command: ['cs', 'stan', 'swagger:validate']
|
command: ['cs', 'stan', 'swagger:validate']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: './.github/actions/ci-setup'
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
|
||||||
extensions: openswoole-4.8.1
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer ${{ matrix.command }}
|
- run: composer ${{ matrix.command }}
|
||||||
|
|
||||||
tests:
|
unit-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
test-group: unit
|
||||||
php-version: ['8.0', '8.1']
|
|
||||||
test-group: ['unit', 'api']
|
cli-tests:
|
||||||
steps:
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
- name: Checkout code
|
with:
|
||||||
uses: actions/checkout@v2
|
test-group: cli
|
||||||
- name: Start database server
|
|
||||||
if: ${{ matrix.test-group == 'api' }}
|
api-tests:
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
- name: Use PHP
|
with:
|
||||||
uses: shivammathur/setup-php@v2
|
test-group: api
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: openswoole-4.8.1
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:${{ matrix.test-group }}:ci
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
with:
|
|
||||||
name: coverage-${{ matrix.test-group }}
|
|
||||||
path: |
|
|
||||||
build/coverage-${{ matrix.test-group }}
|
|
||||||
build/coverage-${{ matrix.test-group }}.cov
|
|
||||||
|
|
||||||
db-tests:
|
db-tests:
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0', '8.1']
|
|
||||||
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
|
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
|
||||||
env:
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
LC_ALL: C
|
with:
|
||||||
steps:
|
platform: ${{ matrix.platform }}
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Install MSSQL ODBC
|
|
||||||
if: ${{ matrix.platform == 'ms' }}
|
|
||||||
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
|
||||||
- name: Start database server
|
|
||||||
if: ${{ matrix.platform != 'sqlite:ci' }}
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ matrix.platform }}
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: openswoole-4.8.1, pdo_sqlsrv-5.10.0beta2
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- name: Create test database
|
|
||||||
if: ${{ matrix.platform == 'ms' }}
|
|
||||||
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
|
||||||
- name: Run tests
|
|
||||||
run: composer test:db:${{ matrix.platform }}
|
|
||||||
- name: Upload code coverage
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '8.0' && matrix.platform == 'sqlite:ci' }}
|
|
||||||
with:
|
|
||||||
name: coverage-db
|
|
||||||
path: |
|
|
||||||
build/coverage-db
|
|
||||||
build/coverage-db.cov
|
|
||||||
|
|
||||||
mutation-tests:
|
|
||||||
needs:
|
|
||||||
- tests
|
|
||||||
- db-tests
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['8.0', '8.1']
|
|
||||||
test-group: ['unit', 'db', 'api']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: openswoole-4.8.1
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
path: build
|
|
||||||
- if: ${{ matrix.test-group == 'unit' }}
|
|
||||||
run: composer infect:ci:unit
|
|
||||||
env:
|
|
||||||
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
|
|
||||||
- if: ${{ matrix.test-group != 'unit' }}
|
|
||||||
run: composer infect:ci:${{ matrix.test-group }}
|
|
||||||
|
|
||||||
upload-coverage:
|
upload-coverage:
|
||||||
needs:
|
needs:
|
||||||
- tests
|
- unit-tests
|
||||||
|
- api-tests
|
||||||
|
- cli-tests
|
||||||
- db-tests
|
- db-tests
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.3']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Use PHP
|
- name: Use PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: './.github/actions/ci-setup'
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
coverage: pcov
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}
|
||||||
ini-values: pcov.directory=module
|
- uses: actions/download-artifact@v4
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
path: build
|
path: build
|
||||||
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
||||||
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
|
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
|
||||||
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
||||||
- run: wget https://phar.phpunit.de/phpcov-8.2.0.phar
|
- run: mv build/coverage-cli/coverage-cli.cov build/coverage-cli.cov
|
||||||
- run: php phpcov-8.2.0.phar merge build --clover build/clover.xml
|
- run: vendor/bin/phpcov merge build --clover build/clover.xml
|
||||||
- name: Publish coverage
|
- name: Publish coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
file: ./build/clover.xml
|
file: ./build/clover.xml
|
||||||
|
|
||||||
delete-artifacts:
|
delete-artifacts:
|
||||||
needs:
|
needs:
|
||||||
- mutation-tests
|
|
||||||
- upload-coverage
|
- upload-coverage
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: geekyeggo/delete-artifact@v1
|
- uses: geekyeggo/delete-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: |
|
name: |
|
||||||
coverage-unit
|
coverage-*
|
||||||
coverage-db
|
|
||||||
coverage-api
|
|
||||||
|
|
||||||
build-docker-image:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 100
|
|
||||||
- uses: marceloprado/has-changed-path@v1
|
|
||||||
id: changed-dockerfile
|
|
||||||
with:
|
|
||||||
paths: ./Dockerfile
|
|
||||||
- if: ${{ steps.changed-dockerfile.outputs.changed == 'true' }}
|
|
||||||
run: docker build -t shlink-docker-image:temp .
|
|
||||||
- if: ${{ steps.changed-dockerfile.outputs.changed != 'true' }}
|
|
||||||
run: echo "Dockerfile didn't change. Skipped"
|
|
||||||
|
|||||||
28
.github/workflows/docker-image-build.yml
vendored
28
.github/workflows/docker-image-build.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: Build docker image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
- name: Login to docker hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
- name: Build the image
|
|
||||||
run: bash ./docker/build
|
|
||||||
26
.github/workflows/publish-docker-image.yml
vendored
Normal file
26
.github/workflows/publish-docker-image.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Build and publish docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-image:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runtime: 'rr'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
- runtime: 'rr'
|
||||||
|
tag-suffix: 'roadrunner'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
uses: shlinkio/github-actions/.github/workflows/docker-publish-image.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
image-name: shlinkio/shlink
|
||||||
|
version-arg-name: SHLINK_VERSION
|
||||||
|
platforms: ${{ matrix.platforms }}
|
||||||
|
tags-suffix: ${{ matrix.tag-suffix }}
|
||||||
|
extra-build-args: |
|
||||||
|
SHLINK_RUNTIME=${{ matrix.runtime }}
|
||||||
41
.github/workflows/publish-release.yml
vendored
41
.github/workflows/publish-release.yml
vendored
@@ -7,36 +7,29 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0', '8.1']
|
php-version: ['8.2', '8.3'] # TODO 8.4
|
||||||
swoole: ['yes', 'no']
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: './.github/actions/ci-setup'
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
extensions: openswoole-4.8.1
|
install-deps: 'no'
|
||||||
- if: ${{ matrix.swoole == 'yes' }}
|
- run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||||
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
- uses: actions/upload-artifact@v4
|
||||||
- if: ${{ matrix.swoole == 'no' }}
|
|
||||||
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
name: dist-files-${{ matrix.php-version }}
|
||||||
path: build
|
path: build
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: ['build']
|
needs: ['build']
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: actions/download-artifact@v4
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
path: build
|
path: build
|
||||||
- name: Publish release with assets
|
- name: Publish release with assets
|
||||||
@@ -50,12 +43,8 @@ jobs:
|
|||||||
|
|
||||||
delete-artifacts:
|
delete-artifacts:
|
||||||
needs: ['publish']
|
needs: ['publish']
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: [ '8.0', '8.1' ]
|
|
||||||
swoole: [ 'yes', 'no' ]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: geekyeggo/delete-artifact@v1
|
- uses: geekyeggo/delete-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
name: dist-files-*
|
||||||
|
|||||||
21
.github/workflows/publish-swagger-spec.yml
vendored
21
.github/workflows/publish-swagger-spec.yml
vendored
@@ -7,30 +7,25 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.0']
|
php-version: ['8.2']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Determine version
|
- name: Determine version
|
||||||
id: determine_version
|
id: determine_version
|
||||||
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
|
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Use PHP
|
- uses: './.github/actions/ci-setup'
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
extensions: openswoole-4.8.1
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer swagger:inline
|
- run: composer swagger:inline
|
||||||
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
||||||
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/oas.json
|
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
|
||||||
- name: Publish spec
|
- name: Publish spec
|
||||||
uses: JamesIves/github-pages-deploy-action@4.1.7
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
||||||
repository-name: 'shlinkio/shlink-open-api-specs'
|
repository-name: 'shlinkio/shlink-open-api-specs'
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
.idea
|
.idea
|
||||||
|
bin/rr
|
||||||
|
.pid
|
||||||
build
|
build
|
||||||
!docker/build
|
!docker/build
|
||||||
composer.lock
|
composer.lock
|
||||||
@@ -7,8 +9,10 @@ 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
|
docs/swagger/swagger-inlined.json
|
||||||
|
phpcov*
|
||||||
|
|||||||
1823
CHANGELOG.md
1823
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 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.
|
||||||
|
|
||||||
## 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, 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,14 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
|
|||||||
For example, `test:db:postgres`.
|
For example, `test:db:postgres`.
|
||||||
|
|
||||||
* Run `./indocker composer test:api` to run API E2E tests. For these, the Postgres database engine is used.
|
* Run `./indocker composer test:api` to run API E2E tests. For these, the Postgres database engine is used.
|
||||||
* Run `./indocker composer infect:test` to run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
* Run `./indocker composer test:cli` to run CLI E2E tests. For these, the Maria DB database engine is used.
|
||||||
* Run `./indocker composer ci` to run all previous commands together. This command is run during the project's continuous integration.
|
* Run `./indocker composer ci` to run all previous commands together, parallelizing non-conflicting tasks as much as possible.
|
||||||
* Run `./indocker composer ci:parallel` to do the same as in previous case, but parallelizing non-conflicting tasks as much as possible.
|
|
||||||
|
|
||||||
> 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 +142,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
|
||||||
|
|
||||||
|
|||||||
79
Dockerfile
79
Dockerfile
@@ -1,58 +1,46 @@
|
|||||||
FROM php:8.1.0-alpine3.15 as base
|
FROM php:8.3-alpine3.19 as base
|
||||||
|
|
||||||
ARG SHLINK_VERSION=latest
|
ARG SHLINK_VERSION=latest
|
||||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||||
ENV OPENSWOOLE_VERSION 4.8.1
|
ARG SHLINK_RUNTIME=rr
|
||||||
ENV PDO_SQLSRV_VERSION 5.10.0beta2
|
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
|
||||||
ENV LC_ALL "C"
|
ENV USER_ID '1001'
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.12.0
|
||||||
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
ENV LC_ALL 'C'
|
||||||
|
|
||||||
WORKDIR /etc/shlink
|
WORKDIR /etc/shlink
|
||||||
|
|
||||||
# Install required PHP extensions
|
# Install required PHP extensions
|
||||||
RUN \
|
RUN \
|
||||||
# Install extensions with no extra dependencies
|
# Temp install dev dependencies needed to compile the extensions
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar sockets bcmath && \
|
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 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 [ $(uname -m) == "x86_64" ]; then \
|
||||||
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
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 openswoole
|
|
||||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
|
|
||||||
pecl install openswoole-${OPENSWOOLE_VERSION} && \
|
|
||||||
docker-php-ext-enable openswoole && \
|
|
||||||
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 && \
|
php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction && \
|
||||||
php composer.phar clear-cache && \
|
php composer.phar clear-cache && \
|
||||||
rm -r docker composer.* && \
|
rm -r docker composer.* && \
|
||||||
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
||||||
@@ -62,10 +50,13 @@ 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=${USER_ID} /etc/shlink .
|
||||||
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink
|
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink && \
|
||||||
|
if [ "$SHLINK_RUNTIME" == 'rr' ]; then \
|
||||||
|
php ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; \
|
||||||
|
fi;
|
||||||
|
|
||||||
# Expose default openswoole port
|
# Expose default port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Copy config specific for the image
|
# Copy config specific for the image
|
||||||
@@ -73,14 +64,6 @@ 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 ${USER_ID}
|
||||||
# FIXME Disabled for now, as it conflicts with ENABLE_PERIODIC_VISIT_LOCATE, which is used to configure a cron as root.
|
|
||||||
# Ref: https://github.com/shlinkio/shlink/issues/1132
|
|
||||||
#RUN chown 1001 /etc/shlink/data
|
|
||||||
#RUN chown 1001 /etc/shlink/data/locks
|
|
||||||
#RUN chown 1001 /etc/shlink/data/proxies
|
|
||||||
#RUN chown 1001 /etc/shlink/data/cache
|
|
||||||
#RUN chown 1001 /etc/shlink/data/log
|
|
||||||
#USER 1001
|
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016-2021 Alejandro Celaya
|
Copyright (c) 2016-2024 Alejandro Celaya
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -1,20 +1,23 @@
|
|||||||

|

|
||||||
|
|
||||||
[](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://fosstodon.org/@shlinkio)
|
||||||
|
[](https://bsky.app/profile/shlinkio.bsky.social)
|
||||||
|
[](https://twitter.com/shlinkio)
|
||||||
[](https://slnk.to/donate)
|
[](https://slnk.to/donate)
|
||||||
|
|
||||||
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain.
|
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
|
||||||
|
|
||||||
## 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)
|
||||||
@@ -26,21 +29,21 @@ 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 or 8.1
|
* 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 or openswoole.
|
* apcu extension is recommended if you don't plan to use RoadRunner.
|
||||||
* xml extension is required if you want to generate QR codes in svg format.
|
* xml extension is required if you want to generate QR codes in svg format.
|
||||||
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||||
* MySQL, MariaDB, PostgreSQL, Microsoft SQL Server or SQLite.
|
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
|
||||||
* The web server of your choice with PHP integration (Apache or Nginx recommended).
|
* 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`.
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
@@ -50,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/openswoole integration.
|
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink*_dist.zip` file that suits your needs. You will find one for every supported PHP version.
|
||||||
|
|
||||||
Finally, decompress the file in the location of your choice.
|
Finally, decompress the file in the location of your choice.
|
||||||
|
|
||||||
@@ -60,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
|
||||||
|
|
||||||
@@ -72,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
|
||||||
|
|
||||||
|
|||||||
100
UPGRADE.md
100
UPGRADE.md
@@ -1,5 +1,103 @@
|
|||||||
# Upgrading
|
# Upgrading
|
||||||
|
|
||||||
|
## From v3.x to v4.x
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
* Swoole and Openswoole are no longer officially supported runtimes. The recommended alternative is RoadRunner.
|
||||||
|
* Dist files for swoole/openswoole are no longer published.
|
||||||
|
* Webhooks are no longer supported. Migrate to one of the other [real-time updates](https://shlink.io/documentation/advanced/real-time-updates/) mechanisms.
|
||||||
|
* When using RoadRunner, the amount of web workers, task workers and the port number can no longer be provided via config options. Use `WEB_WORKER_NUM`, `TASK_WORKER_NUM` and `PORT` env vars instead.
|
||||||
|
|
||||||
|
### Changes in URL shortener
|
||||||
|
|
||||||
|
* The short URLs `loosely` mode is no longer supported, as it was a typo. Use `loose` mode instead.
|
||||||
|
* QR codes URLs now work by default, even for short URLs that cannot be visited due to max visits or date range limitations.
|
||||||
|
If you want to keep previous behavior, pass `QR_CODE_FOR_DISABLED_SHORT_URLS=false` or the equivalent configuration option.
|
||||||
|
* Long URL title resolution is now enabled by default. You can still disable it by passing `AUTO_RESOLVE_TITLES=false` or the equivalent configuration option.
|
||||||
|
* Shlink no longer allows to opt-in for long URL verification. Long URLs are unconditionally considered correct during short URL creation/edition.
|
||||||
|
* Device long URLs have been migrated to the new Dynamic rule-based redirects system and will continue to work as expected, but the API surface has changed.
|
||||||
|
If you use shlink-web-client and rely on this feature when creating/updating short URLs, **DO NOT UPDATE YET**. Support for dynamic rule-based redirects will be added to shlink-web-client soon, in v4.1.0
|
||||||
|
|
||||||
|
### Changes in REST API
|
||||||
|
|
||||||
|
* REST API v1/v2 now behave like v3. This only affects error codes, which are now proper URIs.
|
||||||
|
* `INVALID_ARGUMENT` -> `https://shlink.io/api/error/invalid-data`
|
||||||
|
* `INVALID_SHORT_URL_DELETION` -> `https://shlink.io/api/error/invalid-short-url-deletion`
|
||||||
|
* `DOMAIN_NOT_FOUND` -> `https://shlink.io/api/error/domain-not-found`
|
||||||
|
* `FORBIDDEN_OPERATION` -> `https://shlink.io/api/error/forbidden-tag-operation`
|
||||||
|
* `INVALID_SLUG` -> `https://shlink.io/api/error/non-unique-slug`
|
||||||
|
* `INVALID_SHORTCODE` -> `https://shlink.io/api/error/short-url-not-found`
|
||||||
|
* `TAG_CONFLICT` -> `https://shlink.io/api/error/tag-conflict`
|
||||||
|
* `TAG_NOT_FOUND` -> `https://shlink.io/api/error/tag-not-found`
|
||||||
|
* `MERCURE_NOT_CONFIGURED` -> `https://shlink.io/api/error/mercure-not-configured`
|
||||||
|
* `INVALID_AUTHORIZATION` -> `https://shlink.io/api/error/missing-authentication`
|
||||||
|
* `INVALID_API_KEY` -> `https://shlink.io/api/error/invalid-api-key`
|
||||||
|
* Endpoints previously returning props like `"visitsCount": {number}` no longer do it. There should be an alternative `"visitsSummary": {}` object with the amount nested on it.
|
||||||
|
* It is no longer possible to order the short URLs list with `orderBy=visitsCount-ASC`/`orderBy=visitsCount-DESC`. Use `orderBy=visits-ASC`/`orderBy=visits-DESC` instead.
|
||||||
|
* It is no longer possible to get tags with stats using `GET /tags?withStats=true`. Use `GET /tags/stats` endpoint instead.
|
||||||
|
* The `deviceLongUrls` are ignored when calling `POST /short-urls` or `PATCH /short-urls/{shortCode}`. These should now be configured as dynamic rule-based redirects via `POST /short-urls/{shortCode}/redirect-rules`.
|
||||||
|
|
||||||
|
### Changes in Docker image
|
||||||
|
|
||||||
|
* Since openswoole is no longer supported, there are no longer image tags suffixed with `openswoole`. You should migrate to the default or `roadrunner` ones.
|
||||||
|
* The `non-root` docker tag is no longer published, as all docker images are now running without super-user permissions.
|
||||||
|
* Due to previous point, it is no longer possible to pass `ENABLE_PERIODIC_VISIT_LOCATE=true` in order to configure a cron job that locates visits periodically.
|
||||||
|
This was not really needed in the docker image, as visits are located on the fly.
|
||||||
|
|
||||||
|
### Changes in integrations
|
||||||
|
|
||||||
|
* Credentials in redis URLs should now be URL-encoded, as they are unconditionally url-decoded before being used. Previously, it was possible to customize this behavior via `REDIS_DECODE_CREDENTIALS=true|false`.
|
||||||
|
* Providing redis URIs in the form of `tcp://password@6.6.6.6:6379` is no longer supported. If you want to provide password with no username, do `tcp://:password@6.6.6.6:6379` instead.
|
||||||
|
|
||||||
|
## From v2.x to v3.x
|
||||||
|
|
||||||
|
### 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 +126,7 @@ These routes have been removed, but have a direct replacement:
|
|||||||
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
||||||
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
||||||
|
|
||||||
When using the old ones, a 404 status will me returned now.
|
When using the old ones, a 404 status will be returned now.
|
||||||
|
|
||||||
### Removed command and route aliases
|
### Removed command and route aliases
|
||||||
|
|
||||||
|
|||||||
12
bin/doctrine
Executable file
12
bin/doctrine
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||||
|
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||||
|
|
||||||
|
/** @var EntityManager $app */
|
||||||
|
$em = require __DIR__ . '/../config/entity-manager.php';
|
||||||
|
ConsoleRunner::run(new SingleManagerProvider($em));
|
||||||
@@ -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();
|
|
||||||
36
bin/roadrunner-worker.php
Normal file
36
bin/roadrunner-worker.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Mezzio\Application;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||||
|
use Shlinkio\Shlink\EventDispatcher\RoadRunner\RoadRunnerTaskConsumerToListener;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
|
||||||
|
(static function (): void {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
$rrMode = env('RR_MODE');
|
||||||
|
|
||||||
|
if ($rrMode === 'http') {
|
||||||
|
// This was spin-up as a web worker
|
||||||
|
$app = $container->get(Application::class);
|
||||||
|
$worker = $container->get(PSR7Worker::class);
|
||||||
|
|
||||||
|
while ($req = $worker->waitRequest()) {
|
||||||
|
try {
|
||||||
|
$worker->respond($app->handle($req));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$worker->getWorker()->error((string) $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$requestIdMiddleware = $container->get(RequestIdMiddleware::class);
|
||||||
|
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks(
|
||||||
|
fn (string $requestId) => $requestIdMiddleware->setCurrentRequestId($requestId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,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,22 +1,31 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
export APP_ENV=test
|
|
||||||
export DB_DRIVER=postgres
|
|
||||||
export TEST_ENV=api
|
|
||||||
export GENERATE_COVERAGE=${GENERATE_COVERAGE:-"no"}
|
|
||||||
|
|
||||||
|
export APP_ENV=test
|
||||||
|
export TEST_ENV=api
|
||||||
|
export TEST_RUNTIME="${TEST_RUNTIME:-"rr"}" # rr is the only runtime currently supported
|
||||||
|
export DB_DRIVER="${DB_DRIVER:-"postgres"}"
|
||||||
|
export GENERATE_COVERAGE="${GENERATE_COVERAGE:-"no"}"
|
||||||
|
|
||||||
|
# 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" = 'rr' ] && bin/rr stop -f -w .
|
||||||
|
|
||||||
echo 'Starting server...'
|
echo 'Starting server...'
|
||||||
vendor/bin/laminas mezzio:swoole:start -d
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -w . -c=config/roadrunner/.rr.test.yml \
|
||||||
sleep 2
|
-o=logs.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.http.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.server.output="${PWD}/${OUTPUT_LOGS}" &
|
||||||
|
sleep 2 # Let's give the server a couple of seconds to start
|
||||||
|
|
||||||
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $*
|
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --testdox-summary $*
|
||||||
testsExitCode=$?
|
TESTS_EXIT_CODE=$?
|
||||||
|
|
||||||
vendor/bin/laminas mezzio:swoole:stop
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -w .
|
||||||
|
|
||||||
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
||||||
exit $testsExitCode
|
exit $TESTS_EXIT_CODE
|
||||||
|
|||||||
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();
|
|
||||||
22
build.sh
22
build.sh
@@ -1,17 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || ([ "$#" == 2 ] && [ "$2" != "--no-swoole" ]); then
|
if [ "$#" -lt 1 ]; then
|
||||||
echo "Usage:" >&2
|
echo "Usage:" >&2
|
||||||
echo " $0 {version} [--no-swoole]" >&2
|
echo " $0 {version}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
version=$1
|
version=$1
|
||||||
noSwoole=$2
|
|
||||||
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
||||||
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_swoole"
|
distId="shlink${version}_php${phpVersion}_dist"
|
||||||
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
|
||||||
builtContent="./build/${distId}"
|
builtContent="./build/${distId}"
|
||||||
projectdir=$(pwd)
|
projectdir=$(pwd)
|
||||||
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
||||||
@@ -24,28 +22,20 @@ rsync -av * "${builtContent}" \
|
|||||||
--exclude=*docker* \
|
--exclude=*docker* \
|
||||||
--exclude=Dockerfile \
|
--exclude=Dockerfile \
|
||||||
--include=.htaccess \
|
--include=.htaccess \
|
||||||
|
--include=config/roadrunner/.rr.yml \
|
||||||
--exclude-from=./.dockerignore
|
--exclude-from=./.dockerignore
|
||||||
cd "${builtContent}"
|
cd "${builtContent}"
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
echo "Installing dependencies with $composerBin..."
|
echo "Installing dependencies with $composerBin..."
|
||||||
composerFlags="--optimize-autoloader --no-progress --no-interaction"
|
|
||||||
${composerBin} self-update
|
${composerBin} self-update
|
||||||
${composerBin} install --no-dev --prefer-dist $composerFlags
|
${composerBin} install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction
|
||||||
|
|
||||||
if [[ $noSwoole ]]; then
|
|
||||||
# If generating a dist not for swoole, uninstall mezzio-swoole
|
|
||||||
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
|
|
||||||
else
|
|
||||||
# Copy mezzio helper script to vendor (deprecated - Remove with Shlink 3.0.0)
|
|
||||||
cp "${projectdir}/bin/helper/mezzio-swoole" "./vendor/bin"
|
|
||||||
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
|
||||||
|
|||||||
216
composer.json
216
composer.json
@@ -12,72 +12,73 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0",
|
"php": "^8.2",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"akrabat/ip-address-middleware": "^2.1",
|
"akrabat/ip-address-middleware": "^2.1",
|
||||||
"cakephp/chronos": "^2.3",
|
"cakephp/chronos": "^3.0.2",
|
||||||
"cocur/slugify": "^4.0",
|
"doctrine/dbal": "^4.1",
|
||||||
"doctrine/migrations": "^3.3",
|
"doctrine/migrations": "^3.6",
|
||||||
"doctrine/orm": "^2.10",
|
"doctrine/orm": "^3.2",
|
||||||
"endroid/qr-code": "^4.4",
|
"endroid/qr-code": "^5.0",
|
||||||
"geoip2/geoip2": "^2.12",
|
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||||
"guzzlehttp/guzzle": "^7.4",
|
"geoip2/geoip2": "^3.0",
|
||||||
"happyr/doctrine-specification": "^2.0",
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
"jaybizzle/crawler-detect": "^1.2.110",
|
"hidehalo/nanoid-php": "^1.1",
|
||||||
"laminas/laminas-config": "^3.7",
|
"jaybizzle/crawler-detect": "^1.2.116",
|
||||||
"laminas/laminas-config-aggregator": "^1.7",
|
"laminas/laminas-config": "^3.8",
|
||||||
"laminas/laminas-diactoros": "^2.8",
|
"laminas/laminas-config-aggregator": "^1.15",
|
||||||
"laminas/laminas-inputfilter": "^2.13",
|
"laminas/laminas-diactoros": "^3.3",
|
||||||
"laminas/laminas-servicemanager": "^3.10",
|
"laminas/laminas-inputfilter": "^2.27",
|
||||||
"laminas/laminas-stdlib": "^3.6",
|
"laminas/laminas-servicemanager": "^3.21",
|
||||||
"lcobucci/jwt": "^4.1",
|
"laminas/laminas-stdlib": "^3.17",
|
||||||
"league/uri": "^6.4",
|
"matomo/matomo-php-tracker": "^3.2",
|
||||||
"lstrojny/functional-php": "^1.17",
|
"mezzio/mezzio": "^3.17",
|
||||||
"mezzio/mezzio": "^3.7",
|
"mezzio/mezzio-fastroute": "^3.11",
|
||||||
"mezzio/mezzio-fastroute": "^3.3",
|
"mezzio/mezzio-problem-details": "^1.13",
|
||||||
"mezzio/mezzio-problem-details": "^1.5",
|
"mlocati/ip-lib": "^1.18",
|
||||||
"mezzio/mezzio-swoole": "^3.5",
|
"mobiledetect/mobiledetectlib": "^4.8",
|
||||||
"mlocati/ip-lib": "^1.17",
|
"pagerfanta/core": "^3.8",
|
||||||
"monolog/monolog": "^2.3",
|
"ramsey/uuid": "^4.7",
|
||||||
"nikolaposa/monolog-factory": "^3.1",
|
"shlinkio/doctrine-specification": "^2.1.1",
|
||||||
"ocramius/proxy-manager": "^2.11",
|
"shlinkio/shlink-common": "^6.3",
|
||||||
"pagerfanta/core": "^3.5",
|
"shlinkio/shlink-config": "^3.2.1",
|
||||||
"php-amqplib/php-amqplib": "^3.1",
|
"shlinkio/shlink-event-dispatcher": "^4.1",
|
||||||
"php-middleware/request-id": "^4.1",
|
"shlinkio/shlink-importer": "^5.3.2",
|
||||||
"predis/predis": "^1.1",
|
"shlinkio/shlink-installer": "^9.2",
|
||||||
"pugx/shortid-php": "^1.0",
|
"shlinkio/shlink-ip-geolocation": "^4.0",
|
||||||
"ramsey/uuid": "^4.2",
|
"shlinkio/shlink-json": "^1.1",
|
||||||
"shlinkio/shlink-common": "^4.4",
|
"spiral/roadrunner": "^2024.1",
|
||||||
"shlinkio/shlink-config": "^1.4",
|
"spiral/roadrunner-cli": "^2.6",
|
||||||
"shlinkio/shlink-event-dispatcher": "^2.3",
|
"spiral/roadrunner-http": "^3.5",
|
||||||
"shlinkio/shlink-importer": "^2.5",
|
"spiral/roadrunner-jobs": "^4.5",
|
||||||
"shlinkio/shlink-installer": "^6.3",
|
"symfony/console": "^7.0",
|
||||||
"shlinkio/shlink-ip-geolocation": "^2.2",
|
"symfony/filesystem": "^7.0",
|
||||||
"symfony/console": "^5.4",
|
"symfony/lock": "^7.0",
|
||||||
"symfony/filesystem": "^6.0 || ^5.4",
|
"symfony/process": "^7.0",
|
||||||
"symfony/lock": "^6.0 || ^5.4",
|
"symfony/string": "^7.0"
|
||||||
"symfony/mercure": "^0.6",
|
|
||||||
"symfony/process": "^6.0 || ^5.4",
|
|
||||||
"symfony/string": "^6.0 || ^5.4"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"cebe/php-openapi": "^1.5",
|
"devizzent/cebe-php-openapi": "^1.0.1",
|
||||||
"devster/ubench": "^2.1",
|
"devster/ubench": "^2.1",
|
||||||
"dms/phpunit-arraysubset-asserts": "^0.3.0",
|
"phpstan/phpstan": "^1.11",
|
||||||
"eaglewu/swoole-ide-helper": "dev-master",
|
"phpstan/phpstan-doctrine": "^1.4",
|
||||||
"infection/infection": "^0.25.4",
|
"phpstan/phpstan-phpunit": "^1.4",
|
||||||
"phpspec/prophecy-phpunit": "^2.0",
|
"phpstan/phpstan-symfony": "^1.4",
|
||||||
"phpstan/phpstan": "^1.2",
|
"phpunit/php-code-coverage": "^11.0",
|
||||||
"phpstan/phpstan-doctrine": "^1.0",
|
"phpunit/phpcov": "^10.0",
|
||||||
"phpstan/phpstan-symfony": "^1.0",
|
"phpunit/phpunit": "^11.3",
|
||||||
"phpunit/php-code-coverage": "^9.2",
|
|
||||||
"phpunit/phpunit": "^9.5",
|
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"shlinkio/php-coding-standard": "~2.2.0",
|
"shlinkio/php-coding-standard": "~2.3.0",
|
||||||
"shlinkio/shlink-test-utils": "^2.5",
|
"shlinkio/shlink-test-utils": "^4.1",
|
||||||
"symfony/var-dumper": "^6.0",
|
"symfony/var-dumper": "^7.0",
|
||||||
"veewee/composer-run-parallel": "^1.1"
|
"veewee/composer-run-parallel": "^1.3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/var-exporter": ">=6.3.9,<=6.4.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -87,18 +88,20 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/constants.php",
|
"config/constants.php",
|
||||||
|
"module/Core/functions/array-utils.php",
|
||||||
"module/Core/functions/functions.php"
|
"module/Core/functions/functions.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
||||||
|
"ShlinkioCliTest\\Shlink\\CLI\\": "module/CLI/test-cli",
|
||||||
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
||||||
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
|
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
|
||||||
"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"
|
||||||
@@ -106,70 +109,46 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ci": [
|
"ci": [
|
||||||
"@cs",
|
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:postgres test:db:mysql test:db:maria test:db:ms",
|
||||||
"@stan",
|
"@parallel test:api:ci test:cli:ci"
|
||||||
"@swagger:validate",
|
|
||||||
"@test:ci",
|
|
||||||
"@infect:ci"
|
|
||||||
],
|
],
|
||||||
"ci:parallel": [
|
"cs": "phpcs -s",
|
||||||
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
|
||||||
"@parallel infect:test:api infect:ci:unit infect:ci:db"
|
|
||||||
],
|
|
||||||
"cs": "phpcs",
|
|
||||||
"cs:fix": "phpcbf",
|
"cs:fix": "phpcbf",
|
||||||
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/config config docker/config data/migrations --level=8",
|
"stan": "APP_ENV=test php vendor/bin/phpstan analyse",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:unit",
|
"@parallel test:unit test:db",
|
||||||
"@test:db",
|
"@parallel test:api test:cli"
|
||||||
"@test:api"
|
|
||||||
],
|
],
|
||||||
"test:ci": [
|
"test:unit": "COLUMNS=120 vendor/bin/phpunit --order-by=random --testdox --testdox-summary",
|
||||||
"@test:unit:ci",
|
"test:unit:ci": "@test:unit --coverage-php=build/coverage-unit.cov",
|
||||||
"@test:db",
|
"test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
|
||||||
"@test:api:ci"
|
|
||||||
],
|
|
||||||
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
|
|
||||||
"test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
|
||||||
"test:unit:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit/coverage-html",
|
|
||||||
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||||
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --testdox --testdox-summary -c phpunit-db.xml",
|
||||||
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov",
|
||||||
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
|
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite -- $*",
|
||||||
"test:db:maria": "DB_DRIVER=maria composer test:db:sqlite",
|
"test:db:maria": "DB_DRIVER=maria composer test:db:sqlite -- $*",
|
||||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite -- $*",
|
||||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite -- $*",
|
||||||
"test:api": "bin/test/run-api-tests.sh",
|
"test:api": "bin/test/run-api-tests.sh",
|
||||||
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
|
"test:api:sqlite": "DB_DRIVER=sqlite composer test:api -- $*",
|
||||||
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --only-covering-test-cases --skip-initial-tests",
|
"test:api:mysql": "DB_DRIVER=mysql composer test:api -- $*",
|
||||||
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
"test:api:maria": "DB_DRIVER=maria composer test:api -- $*",
|
||||||
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
|
"test:api:mssql": "DB_DRIVER=mssql composer test:api -- $*",
|
||||||
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json",
|
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api && vendor/bin/phpcov merge build/coverage-api --php build/coverage-api.cov && rm build/coverage-api/*.cov",
|
||||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api",
|
"test:api:pretty": "GENERATE_COVERAGE=yes composer test:api && vendor/bin/phpcov merge build/coverage-api --html build/coverage-api/coverage-html && rm build/coverage-api/*.cov",
|
||||||
"infect:test": [
|
"test:cli": "APP_ENV=test DB_DRIVER=maria TEST_ENV=cli php vendor/bin/phpunit --order-by=random --testdox --testdox-summary -c phpunit-cli.xml",
|
||||||
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
"test:cli:ci": "GENERATE_COVERAGE=yes composer test:cli && vendor/bin/phpcov merge build/coverage-cli --php build/coverage-cli.cov && rm build/coverage-cli/*.cov",
|
||||||
"@infect:ci"
|
"test:cli:pretty": "GENERATE_COVERAGE=yes composer test:cli && vendor/bin/phpcov merge build/coverage-cli --html build/coverage-cli/coverage-html && rm build/coverage-cli/*.cov",
|
||||||
],
|
|
||||||
"infect:test:unit": [
|
|
||||||
"@test:unit:ci",
|
|
||||||
"@infect:ci:unit"
|
|
||||||
],
|
|
||||||
"infect:test:api": [
|
|
||||||
"@test:api:ci",
|
|
||||||
"@infect:ci:api"
|
|
||||||
],
|
|
||||||
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
||||||
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
||||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||||
},
|
},
|
||||||
"scripts-descriptions": {
|
"scripts-descriptions": {
|
||||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
|
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\" and \"test:ci\"</>",
|
||||||
"ci:parallel": "<fg=blue;options=bold>Same as \"ci\", but parallelizing tasks as much as possible</>",
|
|
||||||
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
||||||
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
||||||
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
|
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
|
||||||
"test": "<fg=blue;options=bold>Runs all test suites</>",
|
"test": "<fg=blue;options=bold>Runs all test suites</>",
|
||||||
"test:ci": "<fg=blue;options=bold>Runs all test suites, generating all needed reports and logs for CI envs</>",
|
|
||||||
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
|
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
|
||||||
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
|
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
|
||||||
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||||
@@ -179,13 +158,13 @@
|
|||||||
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
||||||
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
|
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
|
||||||
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
||||||
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a Miscrosoft SQL Server database</>",
|
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a Microsoft SQL Server database</>",
|
||||||
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
||||||
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage reports</>",
|
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage for CI</>",
|
||||||
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
"test:api:pretty": "<fg=blue;options=bold>Runs API test suites, and generates code coverage in HTML format</>",
|
||||||
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
"test:cli": "<fg=blue;options=bold>Runs CLI test suites</>",
|
||||||
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
"test:cli:ci": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage for CI</>",
|
||||||
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
"test:cli:pretty": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage in HTML format</>",
|
||||||
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
||||||
"swagger:inline": "<fg=blue;options=bold>Inlines swagger docs in a single file</>",
|
"swagger:inline": "<fg=blue;options=bold>Inlines swagger docs in a single file</>",
|
||||||
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
||||||
@@ -196,7 +175,6 @@
|
|||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"composer/package-versions-deprecated": true,
|
"composer/package-versions-deprecated": true,
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
"infection/extension-installer": true,
|
|
||||||
"veewee/composer-run-parallel": true
|
"veewee/composer-run-parallel": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
24
config/autoload/cache.global.php
Normal file
24
config/autoload/cache.global.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
|
||||||
|
$redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv()];
|
||||||
|
$cacheRedisBlock = $redisServers === null ? [] : [
|
||||||
|
'redis' => [
|
||||||
|
'servers' => $redisServers,
|
||||||
|
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'cache' => [
|
||||||
|
'namespace' => EnvVars::CACHE_NAMESPACE->loadFromEnv(),
|
||||||
|
...$cacheRedisBlock,
|
||||||
|
],
|
||||||
|
'redis' => $redis,
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -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 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,15 +4,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_DELETE_SHORT_URL_THRESHOLD;
|
return (static function (): array {
|
||||||
|
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD->loadFromEnv();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'delete_short_urls' => [
|
'delete_short_urls' => [
|
||||||
'check_visits_threshold' => true,
|
'check_visits_threshold' => $threshold !== null,
|
||||||
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
'visits_threshold' => (int) ($threshold ?? DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,14 +3,28 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
|
use Mezzio\Application;
|
||||||
use Mezzio\Container;
|
use Mezzio\Container;
|
||||||
use Psr\Http\Client\ClientInterface;
|
use Psr\Http\Client\ClientInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestFactoryInterface;
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use Psr\Http\Message\UploadedFileFactoryInterface;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
use Spiral\RoadRunner\WorkerInterface;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
PSR7Worker::class => ConfigAbstractFactory::class,
|
||||||
|
Filesystem::class => InvokableFactory::class,
|
||||||
|
],
|
||||||
|
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Mezzio\Application::class => [
|
Application::class => [
|
||||||
Container\ApplicationConfigInjectionDelegator::class,
|
Container\ApplicationConfigInjectionDelegator::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -26,4 +40,13 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
PSR7Worker::class => [
|
||||||
|
WorkerInterface::class,
|
||||||
|
ServerRequestFactoryInterface::class,
|
||||||
|
StreamFactoryInterface::class,
|
||||||
|
UploadedFileFactoryInterface::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,39 +2,52 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Listener\OrphanVisitsCountTracker;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Listener\ShortUrlVisitsCountTracker;
|
||||||
|
|
||||||
use function Functional\contains;
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
return (static function (): array {
|
return (static function (): array {
|
||||||
$driver = env('DB_DRIVER');
|
$driver = EnvVars::DB_DRIVER->loadFromEnv();
|
||||||
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
|
$isMysqlCompatible = contains($driver, ['maria', 'mysql']);
|
||||||
|
|
||||||
$resolveDriver = static fn () => match ($driver) {
|
$resolveDriver = static fn () => match ($driver) {
|
||||||
'postgres' => 'pdo_pgsql',
|
'postgres' => 'pdo_pgsql',
|
||||||
'mssql' => 'pdo_sqlsrv',
|
'mssql' => 'pdo_sqlsrv',
|
||||||
default => 'pdo_mysql',
|
default => 'pdo_mysql',
|
||||||
};
|
};
|
||||||
$resolveDefaultPort = static fn () => match ($driver) {
|
$readCredentialAsString = static function (EnvVars $envVar): string|null {
|
||||||
'postgres' => '5432',
|
$value = $envVar->loadFromEnv();
|
||||||
'mssql' => '1433',
|
return $value === null ? null : (string) $value;
|
||||||
default => '3306',
|
|
||||||
};
|
};
|
||||||
$resolveConnection = static fn () => match (true) {
|
$resolveCharset = static fn () => match ($driver) {
|
||||||
$driver === null || $driver === 'sqlite' => [
|
// 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',
|
'driver' => 'pdo_sqlite',
|
||||||
'path' => 'data/database.sqlite',
|
'path' => 'data/database.sqlite',
|
||||||
],
|
],
|
||||||
default => [
|
default => [
|
||||||
'driver' => $resolveDriver(),
|
'driver' => $resolveDriver(),
|
||||||
'dbname' => env('DB_NAME', 'shlink'),
|
'dbname' => EnvVars::DB_NAME->loadFromEnv(),
|
||||||
'user' => env('DB_USER'),
|
'user' => $readCredentialAsString(EnvVars::DB_USER),
|
||||||
'password' => env('DB_PASSWORD'),
|
'password' => $readCredentialAsString(EnvVars::DB_PASSWORD),
|
||||||
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
|
'host' => EnvVars::DB_HOST->loadFromEnv(),
|
||||||
'port' => env('DB_PORT', $resolveDefaultPort()),
|
'port' => EnvVars::DB_PORT->loadFromEnv(),
|
||||||
'unix_socket' => $isMysqlCompatible ? env('DB_UNIX_SOCKET') : null,
|
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
|
||||||
'charset' => 'utf8',
|
'charset' => $resolveCharset(),
|
||||||
|
'driverOptions' => $driver !== 'mssql' ? [] : [
|
||||||
|
'TrustServerCertificate' => 'true',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,6 +58,10 @@ return (static function (): array {
|
|||||||
'proxies_dir' => 'data/proxies',
|
'proxies_dir' => 'data/proxies',
|
||||||
'load_mappings_using_functional_style' => true,
|
'load_mappings_using_functional_style' => true,
|
||||||
'default_repository_classname' => EntitySpecificationRepository::class,
|
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||||
|
'listeners' => [
|
||||||
|
Events::onFlush => [ShortUrlVisitsCountTracker::class, OrphanVisitsCountTracker::class],
|
||||||
|
Events::postFlush => [ShortUrlVisitsCountTracker::class, OrphanVisitsCountTracker::class],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'connection' => $resolveConnection(),
|
'connection' => $resolveConnection(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -6,11 +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_mysql',
|
'host' => 'shlink_db_mysql',
|
||||||
'dbname' => 'shlink',
|
'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,14 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
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' => env('GEOLITE_LICENSE_KEY', 'G4Lm0C60yJsnkdPi'), // Deprecated. Remove hardcoded license on v3
|
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ return [
|
|||||||
|
|
||||||
'installer' => [
|
'installer' => [
|
||||||
'enabled_options' => [
|
'enabled_options' => [
|
||||||
|
Option\Server\RuntimeConfigOption::class,
|
||||||
|
Option\Server\MemoryLimitConfigOption::class,
|
||||||
Option\Database\DatabaseDriverConfigOption::class,
|
Option\Database\DatabaseDriverConfigOption::class,
|
||||||
Option\Database\DatabaseNameConfigOption::class,
|
Option\Database\DatabaseNameConfigOption::class,
|
||||||
Option\Database\DatabaseHostConfigOption::class,
|
Option\Database\DatabaseHostConfigOption::class,
|
||||||
@@ -18,22 +20,18 @@ 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\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\Worker\TaskWorkerNumConfigOption::class,
|
Option\TimezoneConfigOption::class,
|
||||||
Option\Worker\WebWorkerNumConfigOption::class,
|
Option\Cache\CacheNamespaceConfigOption::class,
|
||||||
Option\RedisServersConfigOption::class,
|
Option\Redis\RedisServersConfigOption::class,
|
||||||
|
Option\Redis\RedisSentinelServiceConfigOption::class,
|
||||||
|
Option\Redis\RedisPubSubConfigOption::class,
|
||||||
Option\UrlShortener\ShortCodeLengthOption::class,
|
Option\UrlShortener\ShortCodeLengthOption::class,
|
||||||
Option\Mercure\EnableMercureConfigOption::class,
|
Option\Mercure\EnableMercureConfigOption::class,
|
||||||
Option\Mercure\MercurePublicUrlConfigOption::class,
|
Option\Mercure\MercurePublicUrlConfigOption::class,
|
||||||
@@ -44,6 +42,11 @@ return [
|
|||||||
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
||||||
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
|
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
|
||||||
Option\UrlShortener\AppendExtraPathConfigOption::class,
|
Option\UrlShortener\AppendExtraPathConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableTrailingSlashConfigOption::class,
|
||||||
|
Option\UrlShortener\ShortUrlModeConfigOption::class,
|
||||||
|
Option\Robots\RobotsAllowAllShortUrlsConfigOption::class,
|
||||||
|
Option\Robots\RobotsUserAgentsConfigOption::class,
|
||||||
Option\Tracking\IpAnonymizationConfigOption::class,
|
Option\Tracking\IpAnonymizationConfigOption::class,
|
||||||
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
||||||
Option\Tracking\DisableTrackParamConfigOption::class,
|
Option\Tracking\DisableTrackParamConfigOption::class,
|
||||||
@@ -57,24 +60,45 @@ return [
|
|||||||
Option\QrCode\DefaultFormatConfigOption::class,
|
Option\QrCode\DefaultFormatConfigOption::class,
|
||||||
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
||||||
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
||||||
|
Option\QrCode\DefaultColorConfigOption::class,
|
||||||
|
Option\QrCode\DefaultBgColorConfigOption::class,
|
||||||
|
Option\QrCode\DefaultLogoUrlConfigOption::class,
|
||||||
|
Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqUseSslConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
||||||
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
||||||
|
Option\Matomo\MatomoEnabledConfigOption::class,
|
||||||
|
Option\Matomo\MatomoBaseUrlConfigOption::class,
|
||||||
|
Option\Matomo\MatomoSiteIdConfigOption::class,
|
||||||
|
Option\Matomo\MatomoApiTokenConfigOption::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'installation_commands' => [
|
'installation_commands' => [
|
||||||
InstallationCommand::DB_CREATE_SCHEMA => [
|
InstallationCommand::DB_CREATE_SCHEMA->value => [
|
||||||
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
|
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
|
||||||
],
|
],
|
||||||
InstallationCommand::DB_MIGRATE => [
|
InstallationCommand::DB_MIGRATE->value => [
|
||||||
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
|
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
|
||||||
],
|
],
|
||||||
InstallationCommand::GEOLITE_DOWNLOAD_DB => [
|
InstallationCommand::ORM_PROXIES->value => [
|
||||||
|
'command' => 'bin/doctrine orm:generate-proxies',
|
||||||
|
],
|
||||||
|
InstallationCommand::ORM_CLEAR_CACHE->value => [
|
||||||
|
'command' => 'bin/doctrine orm:clear-cache:metadata',
|
||||||
|
],
|
||||||
|
InstallationCommand::GEOLITE_DOWNLOAD_DB->value => [
|
||||||
'command' => 'bin/cli ' . Command\Visit\DownloadGeoLiteDbCommand::NAME,
|
'command' => 'bin/cli ' . Command\Visit\DownloadGeoLiteDbCommand::NAME,
|
||||||
],
|
],
|
||||||
|
InstallationCommand::API_KEY_GENERATE->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Api\GenerateKeyCommand::NAME,
|
||||||
|
],
|
||||||
|
InstallationCommand::API_KEY_CREATE->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Api\InitialApiKeyCommand::NAME,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Predis\ClientInterface as PredisClient;
|
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Lock\NamespacedStore;
|
||||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -23,11 +23,12 @@ return [
|
|||||||
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
||||||
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
||||||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||||
|
NamespacedStore::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
'lock_store' => env('REDIS_SERVERS') === null ? 'local_lock_store' : 'redis_lock_store',
|
'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
||||||
|
|
||||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
'redis_lock_store' => NamespacedStore::class,
|
||||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||||
],
|
],
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
@@ -39,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,63 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Formatter;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Monolog\Handler;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Monolog\Processor;
|
|
||||||
use MonologFactory\DiContainerLoggerFactory;
|
|
||||||
use PhpMiddleware\RequestId;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||||
|
use Shlinkio\Shlink\Core\EventDispatcher\Helper\RequestIdProvider;
|
||||||
|
use Shlinkio\Shlink\EventDispatcher\Util\RequestIdProviderInterface;
|
||||||
|
|
||||||
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' => [RequestIdMiddleware::class],
|
||||||
'psr3' => [
|
'line_format' =>
|
||||||
'name' => Processor\PsrLogMessageProcessor::class,
|
'[%datetime%] [%extra.' . RequestIdMiddleware::ATTRIBUTE . '%] %channel%.%level_name% - %message%',
|
||||||
],
|
];
|
||||||
'request_id' => RequestId\MonologProcessor::class,
|
|
||||||
];
|
|
||||||
$formatter = [
|
|
||||||
'name' => Formatter\LineFormatter::class,
|
|
||||||
'params' => [
|
|
||||||
'format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%' . PHP_EOL,
|
|
||||||
'allow_inline_line_breaks' => true,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'name' => 'Shlink',
|
'type' => LoggerType::FILE->value,
|
||||||
'handlers' => [
|
...$common,
|
||||||
'shlink_handler' => [
|
|
||||||
'name' => Handler\RotatingFileHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'filename' => 'data/log/shlink_log.log',
|
|
||||||
'max_files' => 30,
|
|
||||||
'file_permission' => 0666,
|
|
||||||
],
|
|
||||||
'formatter' => $formatter,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
'processors' => $processors,
|
'Access' => [
|
||||||
],
|
'type' => LoggerType::STREAM->value,
|
||||||
'Access' => [
|
'destination' => 'php://stderr',
|
||||||
'name' => 'Access',
|
'add_new_line' => ! runningInRoadRunner(),
|
||||||
'handlers' => [
|
...$common,
|
||||||
'access_handler' => [
|
|
||||||
'name' => Handler\StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
'formatter' => $formatter,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'processors' => $processors,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'factories' => [
|
|
||||||
'Logger_Shlink' => [DiContainerLoggerFactory::class, 'Shlink'],
|
|
||||||
'Logger_Access' => [DiContainerLoggerFactory::class, 'Access'],
|
|
||||||
],
|
|
||||||
'aliases' => [
|
|
||||||
'logger' => 'Logger_Shlink',
|
|
||||||
Logger::class => 'Logger_Shlink',
|
|
||||||
LoggerInterface::class => 'Logger_Shlink',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'swoole-http-server' => [
|
|
||||||
'logger' => [
|
|
||||||
'logger-name' => 'Logger_Access',
|
|
||||||
'format' => '%u "%r" %>s %B',
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
|
||||||
|
|
||||||
];
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
'Logger_Shlink' => [LoggerFactory::class, 'Shlink'],
|
||||||
|
'Logger_Access' => [LoggerFactory::class, 'Access'],
|
||||||
|
NullLogger::class => InvokableFactory::class,
|
||||||
|
RequestIdProvider::class => ConfigAbstractFactory::class,
|
||||||
|
],
|
||||||
|
'aliases' => [
|
||||||
|
'logger' => 'Logger_Shlink',
|
||||||
|
Logger::class => 'Logger_Shlink',
|
||||||
|
LoggerInterface::class => 'Logger_Shlink',
|
||||||
|
AccessLogMiddleware::LOGGER_SERVICE_NAME => 'Logger_Access',
|
||||||
|
RequestIdProviderInterface::class => RequestIdProvider::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
RequestIdProvider::class => [RequestIdMiddleware::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -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('openswoole');
|
|
||||||
|
|
||||||
// For swoole, send logs to standard output
|
|
||||||
$handler = $isSwoole
|
|
||||||
? [
|
|
||||||
'name' => StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'handlers' => [
|
'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(),
|
||||||
|
'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,20 +4,19 @@ 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;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
return (static function (): array {
|
return (static function (): array {
|
||||||
$publicUrl = env('MERCURE_PUBLIC_HUB_URL');
|
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => $publicUrl,
|
'public_hub_url' => $publicUrl,
|
||||||
'internal_hub_url' => env('MERCURE_INTERNAL_HUB_URL', $publicUrl),
|
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv(),
|
||||||
'jwt_secret' => env('MERCURE_JWT_SECRET'),
|
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
|
||||||
'jwt_issuer' => 'Shlink',
|
'jwt_issuer' => 'Shlink',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ declare(strict_types=1);
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => 'http://localhost:8001',
|
'public_hub_url' => 'http://localhost:8002',
|
||||||
'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',
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,15 +7,17 @@ namespace Shlinkio\Shlink;
|
|||||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
use Mezzio\Router;
|
use Mezzio\Router;
|
||||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
|
||||||
use RKA\Middleware\IpAddress;
|
use RKA\Middleware\IpAddress;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||||
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'middleware_pipeline' => [
|
'middleware_pipeline' => [
|
||||||
'error-handler' => [
|
'error-handler' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
AccessLogMiddleware::class,
|
||||||
ContentLengthMiddleware::class,
|
ContentLengthMiddleware::class,
|
||||||
RequestIdMiddleware::class,
|
RequestIdMiddleware::class,
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
|
|||||||
@@ -2,22 +2,20 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION;
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT;
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_MARGIN;
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ROUND_BLOCK_SIZE;
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'qr_codes' => [
|
'qr_codes' => [
|
||||||
'size' => (int) env('DEFAULT_QR_CODE_SIZE', DEFAULT_QR_CODE_SIZE),
|
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(),
|
||||||
'margin' => (int) env('DEFAULT_QR_CODE_MARGIN', DEFAULT_QR_CODE_MARGIN),
|
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(),
|
||||||
'format' => env('DEFAULT_QR_CODE_FORMAT', DEFAULT_QR_CODE_FORMAT),
|
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(),
|
||||||
'error_correction' => env('DEFAULT_QR_CODE_ERROR_CORRECTION', DEFAULT_QR_CODE_ERROR_CORRECTION),
|
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(),
|
||||||
'round_block_size' => (bool) env('DEFAULT_QR_CODE_ROUND_BLOCK_SIZE', DEFAULT_QR_CODE_ROUND_BLOCK_SIZE),
|
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(),
|
||||||
|
'enabled_for_disabled_short_urls' => (bool) EnvVars::QR_CODE_FOR_DISABLED_SHORT_URLS->loadFromEnv(),
|
||||||
|
'color' => EnvVars::DEFAULT_QR_CODE_COLOR->loadFromEnv(),
|
||||||
|
'bg_color' => EnvVars::DEFAULT_QR_CODE_BG_COLOR->loadFromEnv(),
|
||||||
|
'logo_url' => EnvVars::DEFAULT_QR_CODE_LOGO_URL->loadFromEnv(),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,47 +2,18 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
|
||||||
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'rabbitmq' => [
|
'rabbitmq' => [
|
||||||
'enabled' => (bool) env('RABBITMQ_ENABLED', false),
|
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(),
|
||||||
'host' => env('RABBITMQ_HOST'),
|
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
|
||||||
'port' => (int) env('RABBITMQ_PORT', '5672'),
|
'use_ssl' => (bool) EnvVars::RABBITMQ_USE_SSL->loadFromEnv(),
|
||||||
'user' => env('RABBITMQ_USER'),
|
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv(),
|
||||||
'password' => env('RABBITMQ_PASSWORD'),
|
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
|
||||||
'vhost' => env('RABBITMQ_VHOST', '/'),
|
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||||
],
|
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv(),
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'factories' => [
|
|
||||||
AMQPStreamConnection::class => ConfigAbstractFactory::class,
|
|
||||||
],
|
|
||||||
'delegators' => [
|
|
||||||
AMQPStreamConnection::class => [
|
|
||||||
LazyServiceFactory::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'lazy_services' => [
|
|
||||||
'class_map' => [
|
|
||||||
AMQPStreamConnection::class => AMQPStreamConnection::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
|
||||||
AMQPStreamConnection::class => [
|
|
||||||
'config.rabbitmq.host',
|
|
||||||
'config.rabbitmq.port',
|
|
||||||
'config.rabbitmq.user',
|
|
||||||
'config.rabbitmq.password',
|
|
||||||
'config.rabbitmq.vhost',
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ return [
|
|||||||
'rabbitmq' => [
|
'rabbitmq' => [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'host' => 'shlink_rabbitmq',
|
'host' => 'shlink_rabbitmq',
|
||||||
|
'port' => 5672,
|
||||||
'user' => 'rabbit',
|
'user' => 'rabbit',
|
||||||
'password' => 'rabbit',
|
'password' => 'rabbit',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,24 +2,19 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
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' => [
|
||||||
// Deprecated env vars
|
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT->loadFromEnv(),
|
||||||
'invalid_short_url' => env('DEFAULT_INVALID_SHORT_URL_REDIRECT', env('INVALID_SHORT_URL_REDIRECT_TO')),
|
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT->loadFromEnv(),
|
||||||
'regular_404' => env('DEFAULT_REGULAR_404_REDIRECT', env('REGULAR_404_REDIRECT_TO')),
|
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT->loadFromEnv(),
|
||||||
'base_url' => env('DEFAULT_BASE_URL_REDIRECT', env('BASE_URL_REDIRECT_TO')),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'url_shortener' => [
|
'redirects' => [
|
||||||
// TODO Move these options to their own config namespace. Maybe "redirects".
|
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(),
|
||||||
'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE),
|
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(),
|
||||||
'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
return (static function (): array {
|
|
||||||
$redisServers = env('REDIS_SERVERS');
|
|
||||||
|
|
||||||
return match (true) {
|
|
||||||
$redisServers === null => [],
|
|
||||||
default => [
|
|
||||||
'cache' => [
|
|
||||||
'redis' => [
|
|
||||||
'servers' => $redisServers,
|
|
||||||
'sentinel_service' => env('REDIS_SENTINEL_SERVICE'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
|
||||||
use PhpMiddleware\RequestId;
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'request_id' => [
|
|
||||||
'allow_override' => true,
|
|
||||||
'header_name' => 'X-Request-Id',
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'factories' => [
|
|
||||||
RequestId\Generator\RamseyUuid4StaticGenerator::class => InvokableFactory::class,
|
|
||||||
RequestId\RequestIdProviderFactory::class => ConfigAbstractFactory::class,
|
|
||||||
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
|
||||||
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
|
||||||
RequestId\RequestIdProviderFactory::class => [
|
|
||||||
RequestId\Generator\RamseyUuid4StaticGenerator::class,
|
|
||||||
'config.request_id.allow_override',
|
|
||||||
'config.request_id.header_name',
|
|
||||||
],
|
|
||||||
RequestId\RequestIdMiddleware::class => [
|
|
||||||
RequestId\RequestIdProviderFactory::class,
|
|
||||||
'config.request_id.header_name',
|
|
||||||
],
|
|
||||||
RequestId\MonologProcessor::class => [RequestId\RequestIdMiddleware::class],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
14
config/autoload/robots.global.php
Normal file
14
config/autoload/robots.global.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'robots' => [
|
||||||
|
'allow-all-short-urls' => (bool) Config\EnvVars::ROBOTS_ALLOW_ALL_SHORT_URLS->loadFromEnv(),
|
||||||
|
'user-agents' => splitByComma(Config\EnvVars::ROBOTS_USER_AGENTS->loadFromEnv()),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -3,16 +3,17 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Mezzio\Router\FastRouteRouter;
|
use Mezzio\Router\FastRouteRouter;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'router' => [
|
'router' => [
|
||||||
'base_path' => env('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 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',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
118
config/autoload/routes.config.php
Normal file
118
config/autoload/routes.config.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
|
use Fig\Http\Message\RequestMethodInterface;
|
||||||
|
use RKA\Middleware\IpAddress;
|
||||||
|
use Shlinkio\Shlink\Core\Action as CoreAction;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Middleware\TrimTrailingSlashMiddleware;
|
||||||
|
use Shlinkio\Shlink\Rest\Action;
|
||||||
|
use Shlinkio\Shlink\Rest\ConfigProvider;
|
||||||
|
use Shlinkio\Shlink\Rest\Middleware;
|
||||||
|
use Shlinkio\Shlink\Rest\Middleware\Mercure\NotConfiguredMercureErrorHandler;
|
||||||
|
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
$dropDomainMiddleware = Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class;
|
||||||
|
$overrideDomainMiddleware = Middleware\ShortUrl\OverrideDomainMiddleware::class;
|
||||||
|
|
||||||
|
// TODO This should be based on config, not the env var
|
||||||
|
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv() ? '[/]' : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// The order of the routes defined here matters. Changing it might cause path conflicts
|
||||||
|
'routes' => [
|
||||||
|
// Rest
|
||||||
|
...ConfigProvider::applyRoutesPrefix([
|
||||||
|
Action\HealthAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Visits and rules routes must go first, as they have a more specific path, otherwise, when
|
||||||
|
// multi-segment slugs are enabled, routes with a less-specific path might match first
|
||||||
|
|
||||||
|
// Visits.
|
||||||
|
Action\Visit\ShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\DeleteShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\Visit\TagVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\DomainVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\GlobalVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\OrphanVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\DeleteOrphanVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\NonOrphanVisitsAction::getRouteDef(),
|
||||||
|
|
||||||
|
//Redirect rules
|
||||||
|
Action\RedirectRule\ListRedirectRulesAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\RedirectRule\SetRedirectRulesAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
|
||||||
|
// Short URLs
|
||||||
|
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
||||||
|
$dropDomainMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\SingleStepCreateShortUrlAction::getRouteDef([
|
||||||
|
Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\EditShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\DeleteShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ResolveShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ListShortUrlsAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
Action\Tag\ListTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\TagsStatsAction::getRouteDef(),
|
||||||
|
Action\Tag\DeleteTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\UpdateTagAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Domains
|
||||||
|
Action\Domain\ListDomainsAction::getRouteDef(),
|
||||||
|
Action\Domain\DomainRedirectsAction::getRouteDef(),
|
||||||
|
|
||||||
|
Action\MercureInfoAction::getRouteDef([NotConfiguredMercureErrorHandler::class]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Non-rest
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RobotsAction::class,
|
||||||
|
'path' => '/robots.txt',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\RobotsAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\PixelAction::class,
|
||||||
|
'path' => '/{shortCode}/track',
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
CoreAction\PixelAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\QrCodeAction::class,
|
||||||
|
'path' => '/{shortCode}/qr-code',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\QrCodeAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RedirectAction::class,
|
||||||
|
'path' => sprintf('/{shortCode}%s', $shortUrlRouteSuffix),
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
TrimTrailingSlashMiddleware::class,
|
||||||
|
CoreAction\RedirectAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
|
||||||
|
|
||||||
return (static function () {
|
|
||||||
$taskWorkers = (int) env('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) env('PORT', 8080),
|
|
||||||
'process-name' => 'shlink',
|
|
||||||
|
|
||||||
'options' => [
|
|
||||||
'worker_num' => (int) env('WEB_WORKER_NUM', 16),
|
|
||||||
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
})();
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'hot-code-reload' => [
|
|
||||||
'enable' => true,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -2,35 +2,37 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Core\splitByComma;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'tracking' => [
|
'tracking' => [
|
||||||
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
||||||
// This applies only if IP address tracking is enabled
|
// This applies only if IP address tracking is enabled
|
||||||
'anonymize_remote_addr' => (bool) env('ANONYMIZE_REMOTE_ADDR', true),
|
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(),
|
||||||
|
|
||||||
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||||
'track_orphan_visits' => (bool) env('TRACK_ORPHAN_VISITS', true),
|
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(),
|
||||||
|
|
||||||
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
|
||||||
|
|
||||||
// If true, visits will not be tracked at all
|
// If true, visits will not be tracked at all
|
||||||
'disable_tracking' => (bool) env('DISABLE_TRACKING', false),
|
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(),
|
||||||
|
|
||||||
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||||
'disable_ip_tracking' => (bool) env('DISABLE_IP_TRACKING', false),
|
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(),
|
||||||
|
|
||||||
// If true, the referrer will not be tracked
|
// If true, the referrer will not be tracked
|
||||||
'disable_referrer_tracking' => (bool) env('DISABLE_REFERRER_TRACKING', false),
|
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(),
|
||||||
|
|
||||||
// If true, the user agent will not be tracked
|
// If true, the user agent will not be tracked
|
||||||
'disable_ua_tracking' => (bool) env('DISABLE_UA_TRACKING', false),
|
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(),
|
||||||
|
|
||||||
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
||||||
'disable_tracking_from' => env('DISABLE_TRACKING_FROM'),
|
'disable_tracking_from' => splitByComma(EnvVars::DISABLE_TRACKING_FROM->loadFromEnv()),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,40 +2,32 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
|
||||||
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
||||||
|
|
||||||
return (static function (): array {
|
return (static function (): array {
|
||||||
$shortCodesLength = max(
|
$shortCodesLength = max(
|
||||||
(int) env('DEFAULT_SHORT_CODES_LENGTH', DEFAULT_SHORT_CODES_LENGTH),
|
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(),
|
||||||
MIN_SHORT_CODES_LENGTH,
|
MIN_SHORT_CODES_LENGTH,
|
||||||
);
|
);
|
||||||
$resolveSchema = static function (): string {
|
$modeFromEnv = EnvVars::SHORT_URL_MODE->loadFromEnv();
|
||||||
// Deprecated. For v3, IS_HTTPS_ENABLED should be true by default, instead of null
|
$mode = ShortUrlMode::tryFrom($modeFromEnv) ?? ShortUrlMode::STRICT;
|
||||||
// return ((bool) env('IS_HTTPS_ENABLED', true)) ? 'https' : 'http';
|
|
||||||
$isHttpsEnabled = env('IS_HTTPS_ENABLED', env('USE_HTTPS'));
|
|
||||||
if ($isHttpsEnabled !== null) {
|
|
||||||
$boolIsHttpsEnabled = (bool) $isHttpsEnabled;
|
|
||||||
return $boolIsHttpsEnabled ? 'https' : 'http';
|
|
||||||
}
|
|
||||||
|
|
||||||
return env('SHORT_DOMAIN_SCHEMA', 'http');
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'domain' => [
|
'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
|
||||||
// Deprecated SHORT_DOMAIN_* env vars
|
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv()) ? 'https' : 'http',
|
||||||
'schema' => $resolveSchema(),
|
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(),
|
||||||
'hostname' => env('DEFAULT_DOMAIN', env('SHORT_DOMAIN_HOST', '')),
|
|
||||||
],
|
],
|
||||||
'validate_url' => (bool) env('VALIDATE_URLS', false), // Deprecated
|
|
||||||
'default_short_codes_length' => $shortCodesLength,
|
'default_short_codes_length' => $shortCodesLength,
|
||||||
'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false),
|
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(),
|
||||||
'append_extra_path' => (bool) env('REDIRECT_APPEND_EXTRA_PATH', false),
|
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(),
|
||||||
|
'multi_segment_slugs_enabled' => (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(),
|
||||||
|
'trailing_slash_enabled' => (bool) EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(),
|
||||||
|
'mode' => $mode,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,16 +2,20 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
$isSwoole = extension_loaded('openswoole');
|
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',
|
||||||
|
default => '8000',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
'auto_resolve_titles' => true,
|
// 'multi_segment_slugs_enabled' => true,
|
||||||
|
// 'trailing_slash_enabled' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
return (static function (): array {
|
|
||||||
$webhooks = env('VISITS_WEBHOOKS');
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'url_shortener' => [
|
|
||||||
// TODO Move these options to their own config namespace
|
|
||||||
'visits_webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
|
|
||||||
'notify_orphan_visits_to_webhooks' => (bool) env('NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS', 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,39 +8,38 @@ use Laminas\ConfigAggregator;
|
|||||||
use Laminas\Diactoros;
|
use Laminas\Diactoros;
|
||||||
use Mezzio;
|
use Mezzio;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
use Mezzio\Swoole;
|
|
||||||
|
|
||||||
use function class_exists;
|
use function Shlinkio\Shlink\Config\env;
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
use const PHP_SAPI;
|
$isTestEnv = env('APP_ENV') === 'test';
|
||||||
|
|
||||||
$isCli = PHP_SAPI === 'cli';
|
return (new ConfigAggregator\ConfigAggregator(
|
||||||
|
providers: [
|
||||||
return (new ConfigAggregator\ConfigAggregator([
|
Mezzio\ConfigProvider::class,
|
||||||
Mezzio\ConfigProvider::class,
|
Mezzio\Router\ConfigProvider::class,
|
||||||
Mezzio\Router\ConfigProvider::class,
|
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
ProblemDetails\ConfigProvider::class,
|
||||||
$isCli && class_exists(Swoole\ConfigProvider::class)
|
Diactoros\ConfigProvider::class,
|
||||||
? Swoole\ConfigProvider::class
|
Common\ConfigProvider::class,
|
||||||
: new ConfigAggregator\ArrayProvider([]),
|
Config\ConfigProvider::class,
|
||||||
ProblemDetails\ConfigProvider::class,
|
Importer\ConfigProvider::class,
|
||||||
Diactoros\ConfigProvider::class,
|
IpGeolocation\ConfigProvider::class,
|
||||||
Common\ConfigProvider::class,
|
EventDispatcher\ConfigProvider::class,
|
||||||
Config\ConfigProvider::class,
|
Core\ConfigProvider::class,
|
||||||
Importer\ConfigProvider::class,
|
CLI\ConfigProvider::class,
|
||||||
IpGeolocation\ConfigProvider::class,
|
Rest\ConfigProvider::class,
|
||||||
EventDispatcher\ConfigProvider::class,
|
new ConfigAggregator\PhpFileProvider('config/autoload/{,*.}global.php'),
|
||||||
Core\ConfigProvider::class,
|
// Local config should not be loaded during tests, whereas test config should be loaded ONLY during tests
|
||||||
CLI\ConfigProvider::class,
|
new ConfigAggregator\PhpFileProvider(
|
||||||
Rest\ConfigProvider::class,
|
$isTestEnv ? 'config/test/*.global.php' : 'config/autoload/{,*.}local.php',
|
||||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
),
|
||||||
env('APP_ENV') === 'test'
|
// Routes have to be loaded last
|
||||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
|
||||||
// Deprecated. When the SimplifiedConfigParser is removed, load only generated_config.php here
|
],
|
||||||
: new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
cachedConfigFile: 'data/cache/app_config.php',
|
||||||
], 'data/cache/app_config.php', [
|
postProcessors: [
|
||||||
Core\Config\SimplifiedConfigParser::class,
|
Core\Config\PostProcessor\BasePathPrefixer::class,
|
||||||
Core\Config\BasePathPrefixer::class,
|
Core\Config\PostProcessor\MultiSegmentSlugProcessor::class,
|
||||||
Core\Config\DeprecatedConfigParser::class,
|
Core\Config\PostProcessor\ShortUrlMethodsProcessor::class,
|
||||||
]))->getMergedConfig();
|
],
|
||||||
|
))->getMergedConfig();
|
||||||
|
|||||||
@@ -4,19 +4,20 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Fig\Http\Message\StatusCodeInterface;
|
use Shlinkio\Shlink\Core\Util\RedirectStatus;
|
||||||
|
|
||||||
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||||
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||||
const MIN_SHORT_CODES_LENGTH = 4;
|
const MIN_SHORT_CODES_LENGTH = 4;
|
||||||
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
|
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302;
|
||||||
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||||
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||||
const CUSTOM_SLUGS_REGEXP = '/[^\pL\pN._~]/u'; // Any unicode letter or number, plus ".", "_" and "~" chars
|
const LOOSE_URI_MATCHER = '/(.+)\:(.+)/i'; // Matches anything starting with a schema.
|
||||||
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside an html title tag
|
|
||||||
const DEFAULT_QR_CODE_SIZE = 300;
|
const DEFAULT_QR_CODE_SIZE = 300;
|
||||||
const DEFAULT_QR_CODE_MARGIN = 0;
|
const DEFAULT_QR_CODE_MARGIN = 0;
|
||||||
const DEFAULT_QR_CODE_FORMAT = 'png';
|
const DEFAULT_QR_CODE_FORMAT = 'png';
|
||||||
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
||||||
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
||||||
const MIN_TASK_WORKERS = 4;
|
const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = true;
|
||||||
|
const DEFAULT_QR_CODE_COLOR = '#000000'; // Black
|
||||||
|
const DEFAULT_QR_CODE_BG_COLOR = '#ffffff'; // White
|
||||||
|
|||||||
@@ -3,22 +3,32 @@
|
|||||||
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 function Shlinkio\Shlink\Config\loadEnvVarsFromConfig;
|
||||||
|
use function Shlinkio\Shlink\Core\enumValues;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
// Promote env vars from installer config
|
||||||
|
loadEnvVarsFromConfig('config/params/generated_config.php', enumValues(EnvVars::class));
|
||||||
|
|
||||||
|
// This is one of the first files loaded. Configure the timezone and memory limit here
|
||||||
|
ini_set('memory_limit', EnvVars::MEMORY_LIMIT->loadFromEnv());
|
||||||
|
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv());
|
||||||
|
|
||||||
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
||||||
// It needs to be placed here as individual config files will not be loaded once config is cached
|
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||||
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
|
||||||
49
config/roadrunner/.rr.test.yml
Normal file
49
config/roadrunner/.rr.test.yml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
############################################################################################
|
||||||
|
# Routes here need to be relative to the project root, as API tests are run with `-w .` #
|
||||||
|
# See https://github.com/orgs/roadrunner-server/discussions/1440#discussioncomment-8486186 #
|
||||||
|
############################################################################################
|
||||||
|
|
||||||
|
rpc:
|
||||||
|
listen: tcp://127.0.0.1:6001
|
||||||
|
|
||||||
|
server:
|
||||||
|
command: 'php ./bin/roadrunner-worker.php'
|
||||||
|
|
||||||
|
http:
|
||||||
|
address: '0.0.0.0:9999'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: './public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
debug: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
debug: false
|
||||||
|
timeout: 300
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
encoding: json
|
||||||
|
mode: development
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
mode: 'off' # Disable logging as Shlink handles it internally
|
||||||
|
server:
|
||||||
|
encoding: json
|
||||||
|
level: info
|
||||||
|
metrics:
|
||||||
|
level: panic
|
||||||
|
jobs:
|
||||||
|
level: panic
|
||||||
38
config/roadrunner/.rr.yml
Normal file
38
config/roadrunner/.rr.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
rpc:
|
||||||
|
listen: tcp://127.0.0.1:6001
|
||||||
|
|
||||||
|
server:
|
||||||
|
command: 'php -dopcache.enable_cli=1 -dopcache.validate_timestamps=0 ../../bin/roadrunner-worker.php'
|
||||||
|
|
||||||
|
http:
|
||||||
|
address: '0.0.0.0:${PORT:-8080}'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: '../../public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: ${WEB_WORKER_NUM:-0}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
timeout: 300 # 5 minutes
|
||||||
|
pool:
|
||||||
|
num_workers: ${TASK_WORKER_NUM:-0}
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
mode: production
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
mode: 'off' # Disable logging as Shlink handles it internally
|
||||||
|
server:
|
||||||
|
level: info
|
||||||
|
jobs:
|
||||||
|
level: debug
|
||||||
@@ -7,12 +7,6 @@ namespace Shlinkio\Shlink\TestUtils;
|
|||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
use function register_shutdown_function;
|
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = require __DIR__ . '/../container.php';
|
$container = require __DIR__ . '/../container.php';
|
||||||
$testHelper = $container->get(Helper\TestHelper::class);
|
$testHelper = $container->get(Helper\TestHelper::class);
|
||||||
@@ -20,14 +14,11 @@ $config = $container->get('config');
|
|||||||
$em = $container->get(EntityManager::class);
|
$em = $container->get(EntityManager::class);
|
||||||
$httpClient = $container->get('shlink_test_api_client');
|
$httpClient = $container->get('shlink_test_api_client');
|
||||||
|
|
||||||
// Dump code coverage when process shuts down
|
$testHelper->createTestDb(
|
||||||
register_shutdown_function(function () use ($httpClient): void {
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
$httpClient->request(
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
'GET',
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
sprintf('http://%s:%s/api-tests/stop-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
$testHelper->createTestDb(['bin/cli', 'db:create'], ['bin/cli', 'db:migrate']);
|
|
||||||
ApiTest\ApiTestCase::setApiClient($httpClient);
|
ApiTest\ApiTestCase::setApiClient($httpClient);
|
||||||
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
||||||
|
|||||||
33
config/test/bootstrap_cli_tests.php
Normal file
33
config/test/bootstrap_cli_tests.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\TestUtils;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
use function file_exists;
|
||||||
|
use function unlink;
|
||||||
|
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = require __DIR__ . '/../container.php';
|
||||||
|
$testHelper = $container->get(Helper\TestHelper::class);
|
||||||
|
$config = $container->get('config');
|
||||||
|
$em = $container->get(EntityManager::class);
|
||||||
|
|
||||||
|
// Delete old coverage in PHP, to avoid merging older executions with current one
|
||||||
|
$covFile = __DIR__ . '/../../build/coverage-cli.cov';
|
||||||
|
if (file_exists($covFile)) {
|
||||||
|
unlink($covFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$testHelper->createTestDb(
|
||||||
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
|
CliTest\CliTestCase::setSeedFixturesCallback(
|
||||||
|
static fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []),
|
||||||
|
);
|
||||||
@@ -8,5 +8,10 @@ use Psr\Container\ContainerInterface;
|
|||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = require __DIR__ . '/../container.php';
|
$container = require __DIR__ . '/../container.php';
|
||||||
$container->get(Helper\TestHelper::class)->createTestDb();
|
$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';
|
||||||
|
|||||||
@@ -6,37 +6,38 @@ namespace Shlinkio\Shlink;
|
|||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\HtmlResponse;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Mezzio\Router\FastRouteRouter;
|
||||||
use Monolog\Logger;
|
use Monolog\Level;
|
||||||
use PHPUnit\Runner\Version;
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Shlinkio\Shlink\TestUtils\ApiTest\CoverageMiddleware;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Shlinkio\Shlink\TestUtils\CliTest\CliCoverageDelegator;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Shlinkio\Shlink\TestUtils\Helper\CoverageHelper;
|
||||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
use Symfony\Component\Console\Application;
|
||||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
|
||||||
use SebastianBergmann\CodeCoverage\Filter;
|
|
||||||
use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html;
|
|
||||||
use SebastianBergmann\CodeCoverage\Report\PHP;
|
|
||||||
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
|
||||||
|
|
||||||
use function Laminas\Stratigility\middleware;
|
use function Laminas\Stratigility\middleware;
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function sleep;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function sys_get_temp_dir;
|
|
||||||
|
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||||
|
|
||||||
$isApiTest = env('TEST_ENV') === 'api';
|
$testEnv = env('TEST_ENV');
|
||||||
$generateCoverage = env('GENERATE_COVERAGE') === 'yes';
|
$isApiTest = $testEnv === 'api';
|
||||||
if ($isApiTest && $generateCoverage) {
|
$isCliTest = $testEnv === 'cli';
|
||||||
$filter = new Filter();
|
$isE2eTest = $isApiTest || $isCliTest;
|
||||||
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
|
|
||||||
$filter->includeDirectory(__DIR__ . '/../../module/Rest/src');
|
$coverageType = env('GENERATE_COVERAGE');
|
||||||
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
$generateCoverage = $coverageType === 'yes';
|
||||||
}
|
$coverage = $isE2eTest && $generateCoverage ? CoverageHelper::createCoverageForDirectories(
|
||||||
|
[
|
||||||
|
__DIR__ . '/../../module/Core/src',
|
||||||
|
__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src',
|
||||||
|
],
|
||||||
|
__DIR__ . '/../../build/coverage-' . $testEnv,
|
||||||
|
) : null;
|
||||||
|
|
||||||
$buildDbConnection = static function (): array {
|
$buildDbConnection = static function (): array {
|
||||||
$driver = env('DB_DRIVER', 'sqlite');
|
$driver = env('DB_DRIVER', 'sqlite');
|
||||||
@@ -46,15 +47,16 @@ $buildDbConnection = static function (): array {
|
|||||||
return match ($driver) {
|
return match ($driver) {
|
||||||
'sqlite' => [
|
'sqlite' => [
|
||||||
'driver' => 'pdo_sqlite',
|
'driver' => 'pdo_sqlite',
|
||||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
'memory' => true,
|
||||||
],
|
],
|
||||||
'postgres' => [
|
'postgres' => [
|
||||||
'driver' => 'pdo_pgsql',
|
'driver' => 'pdo_pgsql',
|
||||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
||||||
'port' => $isCi ? '5433' : '5432',
|
'port' => $isCi ? '5434' : '5432',
|
||||||
'user' => 'postgres',
|
'user' => 'postgres',
|
||||||
'password' => 'root',
|
'password' => 'root',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
|
'charset' => 'utf8',
|
||||||
],
|
],
|
||||||
'mssql' => [
|
'mssql' => [
|
||||||
'driver' => 'pdo_sqlsrv',
|
'driver' => 'pdo_sqlsrv',
|
||||||
@@ -62,6 +64,9 @@ $buildDbConnection = static function (): array {
|
|||||||
'user' => 'sa',
|
'user' => 'sa',
|
||||||
'password' => 'Passw0rd!',
|
'password' => 'Passw0rd!',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
|
'driverOptions' => [
|
||||||
|
'TrustServerCertificate' => 'true',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
default => [ // mysql and maria
|
default => [ // mysql and maria
|
||||||
'driver' => 'pdo_mysql',
|
'driver' => 'pdo_mysql',
|
||||||
@@ -70,87 +75,52 @@ $buildDbConnection = static function (): array {
|
|||||||
'user' => 'root',
|
'user' => 'root',
|
||||||
'password' => 'root',
|
'password' => 'root',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
|
'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 [
|
||||||
|
|
||||||
'debug' => true,
|
'debug' => true,
|
||||||
ConfigAggregator::ENABLE_CACHE => false,
|
ConfigAggregator::ENABLE_CACHE => false,
|
||||||
|
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
||||||
|
|
||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'domain' => [
|
'domain' => [
|
||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => 'doma.in',
|
'hostname' => 's.test',
|
||||||
],
|
|
||||||
'validate_url' => true,
|
|
||||||
],
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'enable_coroutine' => false,
|
|
||||||
'swoole-http-server' => [
|
|
||||||
'host' => SWOOLE_TESTING_HOST,
|
|
||||||
'port' => SWOOLE_TESTING_PORT,
|
|
||||||
'process-name' => 'shlink_test',
|
|
||||||
'options' => [
|
|
||||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
|
||||||
'enable_coroutine' => false,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'routes' => !$isApiTest ? [] : [
|
'routes' => [
|
||||||
|
// This route is used to test that title resolution is skipped if the long URL times out
|
||||||
[
|
[
|
||||||
'name' => 'dump_coverage',
|
'name' => 'long_url_with_timeout',
|
||||||
'path' => '/api-tests/stop-coverage',
|
'path' => '/api-tests/long-url-with-timeout',
|
||||||
'middleware' => middleware(static function () use (&$coverage) {
|
|
||||||
// TODO I have tried moving this block to a listener so that it's invoked automatically,
|
|
||||||
// but then the coverage is generated empty ¯\_(ツ)_/¯
|
|
||||||
if ($coverage) { // @phpstan-ignore-line
|
|
||||||
$basePath = __DIR__ . '/../../build/coverage-api';
|
|
||||||
|
|
||||||
(new PHP())->process($coverage, $basePath . '.cov');
|
|
||||||
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
|
||||||
(new Html())->process($coverage, $basePath . '/coverage-html');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EmptyResponse();
|
|
||||||
}),
|
|
||||||
'allowed_methods' => ['GET'],
|
'allowed_methods' => ['GET'],
|
||||||
|
'middleware' => middleware(static function () {
|
||||||
|
sleep(5); // Title resolution times out at 3 seconds
|
||||||
|
return new HtmlResponse('<title>The title</title>');
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'middleware_pipeline' => !$isApiTest ? [] : [
|
'middleware_pipeline' => !$isApiTest ? [] : [
|
||||||
'capture_code_coverage' => [
|
'capture_code_coverage' => [
|
||||||
'middleware' => middleware(static function (
|
'middleware' => new CoverageMiddleware($coverage),
|
||||||
ServerRequestInterface $req,
|
|
||||||
RequestHandlerInterface $handler,
|
|
||||||
) use (&$coverage): ResponseInterface {
|
|
||||||
$coverage?->start($req->getHeaderLine('x-coverage-id'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
return $handler->handle($req);
|
|
||||||
} finally {
|
|
||||||
$coverage?->stop();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
'priority' => 9999,
|
'priority' => 9999,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Disable mercure integration during E2E tests
|
||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => null,
|
'public_hub_url' => null,
|
||||||
'internal_hub_url' => null,
|
'internal_hub_url' => null,
|
||||||
@@ -160,13 +130,18 @@ 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 => [
|
||||||
|
new CliCoverageDelegator($coverage),
|
||||||
|
],
|
||||||
|
] : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
@@ -175,13 +150,14 @@ return [
|
|||||||
|
|
||||||
'data_fixtures' => [
|
'data_fixtures' => [
|
||||||
'paths' => [
|
'paths' => [
|
||||||
|
// TODO These are used for other module's tests, so maybe should be somewhere else
|
||||||
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => $buildTestLoggerConfig('shlink_handler', 'shlink.log'),
|
'Shlink' => $buildTestLoggerConfig('shlink.log'),
|
||||||
'Access' => $buildTestLoggerConfig('access_handler', 'access.log'),
|
'Access' => $buildTestLoggerConfig('access.log'),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
||||||
curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
|
curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
|
||||||
apt-get update
|
apt-get update
|
||||||
ACCEPT_EULA=Y apt-get install msodbcsql17
|
ACCEPT_EULA=Y apt-get install msodbcsql18
|
||||||
apt-get install unixodbc-dev
|
# apt-get install unixodbc-dev
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
ServerName doma.in
|
ServerName s.test
|
||||||
DocumentRoot "/path/to/shlink/public"
|
DocumentRoot "/path/to/shlink/public"
|
||||||
|
|
||||||
<Directory "/path/to/shlink/public">
|
<Directory "/path/to/shlink/public">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
server {
|
server {
|
||||||
server_name doma.in;
|
server_name s.test;
|
||||||
listen 80;
|
listen 80;
|
||||||
root /path/to/shlink/public;
|
root /path/to/shlink/public;
|
||||||
index index.php;
|
index index.php;
|
||||||
@@ -11,7 +11,7 @@ server {
|
|||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi.conf;
|
include fastcgi.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
/var/log/shlink/shlink_swoole.log {
|
|
||||||
su root root
|
|
||||||
daily
|
|
||||||
missingok
|
|
||||||
rotate 120
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 0640 root root
|
|
||||||
postrotate
|
|
||||||
/etc/init.d/shlink_swoole restart
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
### BEGIN INIT INFO
|
|
||||||
# Provides: shlink_swoole
|
|
||||||
# Required-Start: $local_fs $network $named $time $syslog
|
|
||||||
# Required-Stop: $local_fs $network $named $time $syslog
|
|
||||||
# Default-Start: 2 3 4 5
|
|
||||||
# Default-Stop: 0 1 6
|
|
||||||
# Description: Shlink non-blocking server with swoole
|
|
||||||
### END INIT INFO
|
|
||||||
|
|
||||||
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
|
|
||||||
RUNAS=root
|
|
||||||
|
|
||||||
PIDFILE=/var/run/shlink_swoole.pid
|
|
||||||
LOGDIR=/var/log/shlink
|
|
||||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
|
||||||
echo 'Shlink with swoole already running' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo 'Starting shlink with swoole' >&2
|
|
||||||
mkdir -p "$LOGDIR"
|
|
||||||
touch "$LOGFILE"
|
|
||||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
|
||||||
su -c "$CMD" $RUNAS > "$PIDFILE"
|
|
||||||
echo 'Shlink started' >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
|
||||||
echo 'Shlink with swoole not running' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo 'Stopping shlink with swoole' >&2
|
|
||||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
|
||||||
echo 'Shlink stopped' >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
stop
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {start|stop|restart}"
|
|
||||||
esac
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
FROM php:8.1.0-fpm-alpine3.15
|
FROM php:8.3-fpm-alpine3.19
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.21
|
ENV APCU_VERSION 5.1.23
|
||||||
ENV PDO_SQLSRV_VERSION 5.10.0beta2
|
ENV PDO_SQLSRV_VERSION 5.12.0
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
# 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,10 +31,9 @@ RUN docker-php-ext-install gd
|
|||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
RUN 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 sockets
|
|
||||||
RUN docker-php-ext-install bcmath
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
@@ -48,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
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
display_errors=On
|
display_errors=On
|
||||||
error_reporting=-1
|
error_reporting=-1
|
||||||
memory_limit=-1
|
|
||||||
log_errors_max_len=0
|
log_errors_max_len=0
|
||||||
zend.assertions=1
|
zend.assertions=1
|
||||||
assert.exception=1
|
assert.exception=1
|
||||||
|
|||||||
2
data/infra/redis/redis-acl.conf
Normal file
2
data/infra/redis/redis-acl.conf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
user foo allcommands allkeys on >bar
|
||||||
|
requirepass barbar
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
FROM php:8.1.0-alpine3.15
|
FROM php:8.3-alpine3.19
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.21
|
ENV APCU_VERSION 5.1.23
|
||||||
ENV INOTIFY_VERSION 3.0.0
|
ENV PDO_SQLSRV_VERSION 5.12.0
|
||||||
ENV OPENSWOOLE_VERSION 4.8.1
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
ENV PDO_SQLSRV_VERSION 5.10.0beta2
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
# 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,10 +31,9 @@ RUN docker-php-ext-install gd
|
|||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
RUN 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 sockets
|
|
||||||
RUN docker-php-ext-install bcmath
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
@@ -49,22 +46,14 @@ RUN mkdir -p /usr/src/php/ext/apcu \
|
|||||||
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
# Install inotify extension
|
# Install pcov and sqlsrv driver
|
||||||
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
RUN mkdir -p /usr/src/php/ext/inotify \
|
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1 \
|
|
||||||
&& docker-php-ext-configure inotify \
|
|
||||||
&& docker-php-ext-install inotify \
|
|
||||||
&& rm /tmp/inotify.tar.gz
|
|
||||||
|
|
||||||
# Install openswoole, pcov and mssql driver
|
|
||||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
|
||||||
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
|
||||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
docker-php-ext-enable openswoole 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
|
||||||
@@ -75,12 +64,13 @@ RUN chmod 777 /home
|
|||||||
VOLUME /home/shlink
|
VOLUME /home/shlink
|
||||||
WORKDIR /home/shlink
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
# Expose openswoole port
|
# Expose roadrunner port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD \
|
CMD \
|
||||||
# Install dependencies if the vendor dir does not exist
|
# Install dependencies if the vendor dir does not exist
|
||||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||||
# When restarting the container, openswoole might think it is already in execution
|
# Download roadrunner binary
|
||||||
|
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
|
# 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 ./bin/rr serve -c config/roadrunner/.rr.dev.yml; do sleep 1 ; done
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80 default_server;
|
|
||||||
|
|
||||||
error_log /home/shlink/www/data/infra/nginx/swoole_proxy.error.log;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_pass http://shlink_swoole:8080;
|
|
||||||
proxy_read_timeout 90s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Exception;
|
|
||||||
use Doctrine\DBAL\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
|
|
||||||
{
|
|
||||||
$this->connection->getDatabasePlatform()->getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Types\Types;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20160820191203 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Check if the tables already exist
|
|
||||||
$tables = $schema->getTables();
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
if ($table->getName() === 'tags') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->createTagsTable($schema);
|
|
||||||
$this->createShortUrlsInTagsTable($schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createTagsTable(Schema $schema): void
|
|
||||||
{
|
|
||||||
$table = $schema->createTable('tags');
|
|
||||||
$table->addColumn('id', Types::BIGINT, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'autoincrement' => true,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
$table->addColumn('name', Types::STRING, [
|
|
||||||
'length' => 255,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
$table->addUniqueIndex(['name']);
|
|
||||||
|
|
||||||
$table->setPrimaryKey(['id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createShortUrlsInTagsTable(Schema $schema): void
|
|
||||||
{
|
|
||||||
$table = $schema->createTable('short_urls_in_tags');
|
|
||||||
$table->addColumn('short_url_id', Types::BIGINT, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
$table->addColumn('tag_id', Types::BIGINT, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'notnull' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$table->addForeignKeyConstraint('tags', ['tag_id'], ['id'], [
|
|
||||||
'onDelete' => 'CASCADE',
|
|
||||||
'onUpdate' => 'RESTRICT',
|
|
||||||
]);
|
|
||||||
$table->addForeignKeyConstraint('short_urls', ['short_url_id'], ['id'], [
|
|
||||||
'onDelete' => 'CASCADE',
|
|
||||||
'onUpdate' => 'RESTRICT',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$table->setPrimaryKey(['short_url_id', 'tag_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$schema->dropTable('short_urls_in_tags');
|
|
||||||
$schema->dropTable('tags');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\DBAL\Types\Types;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20171021093246 extends AbstractMigration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if ($shortUrls->hasColumn('valid_since')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->addColumn('valid_since', Types::DATETIME_MUTABLE, [
|
|
||||||
'notnull' => false,
|
|
||||||
]);
|
|
||||||
$shortUrls->addColumn('valid_until', Types::DATETIME_MUTABLE, [
|
|
||||||
'notnull' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if (! $shortUrls->hasColumn('valid_since')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->dropColumn('valid_since');
|
|
||||||
$shortUrls->dropColumn('valid_until');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\DBAL\Types\Types;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20171022064541 extends AbstractMigration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if ($shortUrls->hasColumn('max_visits')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->addColumn('max_visits', Types::INTEGER, [
|
|
||||||
'unsigned' => true,
|
|
||||||
'notnull' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$shortUrls = $schema->getTable('short_urls');
|
|
||||||
if (! $shortUrls->hasColumn('max_visits')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shortUrls->dropColumn('max_visits');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20180801183328 extends AbstractMigration
|
|
||||||
{
|
|
||||||
private const NEW_SIZE = 255;
|
|
||||||
private const OLD_SIZE = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->setSize($schema, self::NEW_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->setSize($schema, self::OLD_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
private function setSize(Schema $schema, int $size): void
|
|
||||||
{
|
|
||||||
$schema->getTable('short_urls')->getColumn('short_code')->setLength($size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Exception;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
use PDO;
|
|
||||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
|
||||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20180913205455 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Nothing to create
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function postUp(Schema $schema): void
|
|
||||||
{
|
|
||||||
$qb = $this->connection->createQueryBuilder();
|
|
||||||
$qb->select('id', 'remote_addr')
|
|
||||||
->from('visits');
|
|
||||||
$st = $this->connection->executeQuery($qb->getSQL());
|
|
||||||
|
|
||||||
$qb = $this->connection->createQueryBuilder();
|
|
||||||
$qb->update('visits', 'v')
|
|
||||||
->set('v.remote_addr', ':obfuscatedAddr')
|
|
||||||
->where('v.id=:id');
|
|
||||||
|
|
||||||
while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
|
|
||||||
$addr = $row['remote_addr'] ?? null;
|
|
||||||
if ($addr === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$qb->setParameters([
|
|
||||||
'id' => $row['id'],
|
|
||||||
'obfuscatedAddr' => $this->determineAddress((string) $addr),
|
|
||||||
])->execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function determineAddress(string $addr): ?string
|
|
||||||
{
|
|
||||||
if ($addr === IpAddress::LOCALHOST) {
|
|
||||||
return $addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (string) IpAddress::fromString($addr)->getAnonymizedCopy();
|
|
||||||
} catch (InvalidArgumentException) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Nothing to rollback
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20180915110857 extends AbstractMigration
|
|
||||||
{
|
|
||||||
private const ON_DELETE_MAP = [
|
|
||||||
'visit_locations' => 'SET NULL',
|
|
||||||
'short_urls' => 'CASCADE',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$visits = $schema->getTable('visits');
|
|
||||||
$foreignKeys = $visits->getForeignKeys();
|
|
||||||
|
|
||||||
// Remove all existing foreign keys and add them again with CASCADE delete
|
|
||||||
foreach ($foreignKeys as $foreignKey) {
|
|
||||||
$visits->removeForeignKey($foreignKey->getName());
|
|
||||||
$foreignTable = $foreignKey->getForeignTableName();
|
|
||||||
|
|
||||||
$visits->addForeignKeyConstraint(
|
|
||||||
$foreignTable,
|
|
||||||
$foreignKey->getLocalColumns(),
|
|
||||||
$foreignKey->getForeignColumns(),
|
|
||||||
[
|
|
||||||
'onDelete' => self::ON_DELETE_MAP[$foreignTable],
|
|
||||||
'onUpdate' => 'RESTRICT',
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Nothing to run
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isTransactional(): bool
|
|
||||||
{
|
|
||||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user