mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-01 04:33:12 +08:00
Compare commits
633 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 |
@@ -19,7 +19,6 @@ indocker
|
||||
docker-*
|
||||
phpstan.neon
|
||||
php*xml*
|
||||
infection*
|
||||
**/test*
|
||||
build*
|
||||
**/.*
|
||||
|
||||
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']
|
||||
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.
|
||||
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.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
-->
|
||||
|
||||
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Something on shlink is broken or not working as documented?
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be expected.
|
||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
|
||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||
-->
|
||||
|
||||
#### How Shlink is set-up
|
||||
|
||||
* Shlink Version: x.y.z
|
||||
* PHP Version: x.y.z
|
||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
|
||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary describing the problem you are experiencing. -->
|
||||
|
||||
#### Current behavior
|
||||
|
||||
<!-- How is it actually behaving (and it shouldn't)? -->
|
||||
|
||||
#### Expected behavior
|
||||
|
||||
<!-- How did you expected to behave? -->
|
||||
|
||||
#### How to reproduce
|
||||
|
||||
<!-- Provide steps to reproduce the bug. -->
|
||||
71
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
71
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Bug report
|
||||
description: Something on Shlink is broken or not working as documented?
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: input
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Shlink version
|
||||
placeholder: x.y.z
|
||||
- type: input
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: PHP version
|
||||
placeholder: x.y.z
|
||||
- type: dropdown
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: How do you serve Shlink
|
||||
options:
|
||||
- Self-hosted Apache
|
||||
- Self-hosted nginx
|
||||
- Self-hosted RoadRunner
|
||||
- Docker image
|
||||
- Other (explain in summary)
|
||||
- type: dropdown
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Database engine
|
||||
options:
|
||||
- MySQL
|
||||
- MariaDB
|
||||
- PostgreSQL
|
||||
- MicrosoftSQL
|
||||
- SQLite
|
||||
- type: input
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Database version
|
||||
placeholder: x.y.z
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Current behavior
|
||||
value: '<!-- How is it actually behaving (and it should not)? -->'
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
value: '<!-- How did you expect it to behave? -->'
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Minimum steps to reproduce
|
||||
value: |
|
||||
<!--
|
||||
Simple but detailed way to reproduce the bug:
|
||||
|
||||
* Avoid things like "create a kubernetes cluster", or anything related with cloud providers, as that is rarely the root cause.
|
||||
* Avoid too vague steps or one-liners like "Update from v1 to v2".
|
||||
* Providing the reproduction in the form of a self-contained docker-composer is desirable.
|
||||
|
||||
Failing in any of these will cause the issue to be closed as "not reproducible".
|
||||
-->
|
||||
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Do you find shlink is missing some important feature that would make it more useful?
|
||||
labels: feature
|
||||
---
|
||||
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be expected.
|
||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
|
||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||
-->
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Describe the new feature you would like to request. -->
|
||||
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Feature request
|
||||
description: Do you find Shlink is missing some important feature that would make it more useful?
|
||||
labels: ['feature']
|
||||
body:
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Summary
|
||||
value: '<!-- Describe the new feature you would like to request. -->'
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Use case
|
||||
value: '<!-- Explain why do you think this feature would be useful, and what problems would it help to solve. -->'
|
||||
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Question - Support
|
||||
about: Do you have a problem setting up or using shlink?
|
||||
labels: question
|
||||
---
|
||||
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be expected.
|
||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||
|
||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
||||
-->
|
||||
|
||||
#### How Shlink is set-up
|
||||
|
||||
* Shlink Version: x.y.z
|
||||
* PHP Version: x.y.z
|
||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted openswoole|Docker image
|
||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Describe the issue you are facing here. -->
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Question - Support
|
||||
about: Do you need help setting up or using Shlink?
|
||||
url: https://github.com/shlinkio/shlink/discussions/new?category=help-wanted
|
||||
7
.github/actions/ci-setup/action.yml
vendored
7
.github/actions/ci-setup/action.yml
vendored
@@ -12,7 +12,6 @@ inputs:
|
||||
php-extensions:
|
||||
description: 'The PHP extensions to install'
|
||||
required: false
|
||||
default: ''
|
||||
extensions-cache-key:
|
||||
description: 'The key used to cache PHP extensions. If empty value is provided, extension caching is disabled'
|
||||
required: true
|
||||
@@ -21,6 +20,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup cache environment
|
||||
if: ${{ inputs.php-extensions }}
|
||||
id: extcache
|
||||
uses: shivammathur/cache-extensions@v1
|
||||
with:
|
||||
@@ -28,7 +28,8 @@ runs:
|
||||
extensions: ${{ inputs.php-extensions }}
|
||||
key: ${{ inputs.extensions-cache-key }}
|
||||
- name: Cache extensions
|
||||
uses: actions/cache@v2
|
||||
if: ${{ inputs.php-extensions }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.extcache.outputs.dir }}
|
||||
key: ${{ steps.extcache.outputs.key }}
|
||||
@@ -43,5 +44,5 @@ runs:
|
||||
ini-values: pcov.directory=module
|
||||
- name: Install dependencies
|
||||
if: ${{ inputs.install-deps == 'yes' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
run: composer install --no-interaction --prefer-dist ${{ inputs.php-version == '8.4' && '--ignore-platform-req=php' || '' }}
|
||||
shell: bash
|
||||
|
||||
17
.github/workflows/ci-db-tests.yml
vendored
17
.github/workflows/ci-db-tests.yml
vendored
@@ -10,33 +10,34 @@ on:
|
||||
|
||||
jobs:
|
||||
db-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
php-version: ['8.2', '8.3', '8.4']
|
||||
continue-on-error: ${{ matrix.php-version == '8.4' }}
|
||||
env:
|
||||
LC_ALL: C
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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 }}
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ inputs.platform }}
|
||||
- uses: './.github/actions/ci-setup'
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-extensions: openswoole-4.12.1, pdo_sqlsrv-5.10.1
|
||||
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-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||
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@v3
|
||||
if: ${{ matrix.php-version == '8.1' && inputs.platform == 'sqlite:ci' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.php-version == '8.3' && inputs.platform == 'sqlite:ci' }}
|
||||
with:
|
||||
name: coverage-db
|
||||
path: |
|
||||
|
||||
4
.github/workflows/ci-docker-image-build.yml
vendored
4
.github/workflows/ci-docker-image-build.yml
vendored
@@ -7,8 +7,8 @@ on:
|
||||
|
||||
jobs:
|
||||
build-docker-image:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- run: docker build -t shlink-docker-image:temp .
|
||||
|
||||
45
.github/workflows/ci-mutation-tests.yml
vendored
45
.github/workflows/ci-mutation-tests.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Mutation tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
test-group:
|
||||
type: string
|
||||
required: true
|
||||
description: One of unit, db, api or cli
|
||||
|
||||
jobs:
|
||||
mutation-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: './.github/actions/ci-setup'
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-extensions: openswoole-4.12.1
|
||||
extensions-cache-key: mutation-tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: coverage-${{ inputs.test-group }}
|
||||
path: build
|
||||
- name: Resolve infection args
|
||||
id: infection_args
|
||||
run: echo "::set-output name=args::--logger-github=false"
|
||||
# TODO Try to filter mutation tests to improve execution times. Investigate why --git-diff-lines --git-diff-base=develop does not work
|
||||
# run: |
|
||||
# BRANCH="${GITHUB_REF#refs/heads/}" |
|
||||
# if [[ $BRANCH == 'main' || $BRANCH == 'develop' ]]; then
|
||||
# echo "::set-output name=args::--logger-github=false"
|
||||
# else
|
||||
# echo "::set-output name=args::--logger-github=false --git-diff-lines --git-diff-base=develop"
|
||||
# fi;
|
||||
shell: bash
|
||||
- if: ${{ inputs.test-group == 'unit' }}
|
||||
run: composer infect:ci:unit -- ${{ steps.infection_args.outputs.args }}
|
||||
env:
|
||||
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
|
||||
- if: ${{ inputs.test-group != 'unit' }}
|
||||
run: composer infect:ci:${{ inputs.test-group }} -- ${{ steps.infection_args.outputs.args }}
|
||||
21
.github/workflows/ci-tests.yml
vendored
21
.github/workflows/ci-tests.yml
vendored
@@ -10,26 +10,31 @@ on:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
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@v3
|
||||
- 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
|
||||
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
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
|
||||
- uses: './.github/actions/ci-setup'
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-extensions: openswoole-4.12.1
|
||||
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@v3
|
||||
if: ${{ matrix.php-version == '8.1' }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.php-version == '8.3' }}
|
||||
with:
|
||||
name: coverage-${{ inputs.test-group }}
|
||||
path: |
|
||||
|
||||
115
.github/workflows/ci.yml
vendored
115
.github/workflows/ci.yml
vendored
@@ -8,7 +8,6 @@ on:
|
||||
- '*.md'
|
||||
- '*.xml'
|
||||
- '*.yml*'
|
||||
- '*.json5'
|
||||
- '*.neon'
|
||||
push:
|
||||
branches:
|
||||
@@ -21,22 +20,20 @@ on:
|
||||
- '*.md'
|
||||
- '*.xml'
|
||||
- '*.yml*'
|
||||
- '*.json5'
|
||||
- '*.neon'
|
||||
|
||||
jobs:
|
||||
static-analysis:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1']
|
||||
php-version: ['8.3']
|
||||
command: ['cs', 'stan', 'swagger:validate']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: './.github/actions/ci-setup'
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-extensions: openswoole-4.12.1
|
||||
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
|
||||
- run: composer ${{ matrix.command }}
|
||||
|
||||
@@ -50,128 +47,56 @@ jobs:
|
||||
with:
|
||||
test-group: cli
|
||||
|
||||
openswoole-api-tests:
|
||||
api-tests:
|
||||
uses: './.github/workflows/ci-tests.yml'
|
||||
with:
|
||||
test-group: api
|
||||
|
||||
roadrunner-api-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
db-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # rr get-binary picks this env automatically
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr
|
||||
- run: composer test:api:rr
|
||||
|
||||
sqlite-db-tests:
|
||||
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
|
||||
uses: './.github/workflows/ci-db-tests.yml'
|
||||
with:
|
||||
platform: 'sqlite:ci'
|
||||
|
||||
mysql-db-tests:
|
||||
uses: './.github/workflows/ci-db-tests.yml'
|
||||
with:
|
||||
platform: 'mysql'
|
||||
|
||||
maria-db-tests:
|
||||
uses: './.github/workflows/ci-db-tests.yml'
|
||||
with:
|
||||
platform: 'maria'
|
||||
|
||||
postgres-db-tests:
|
||||
uses: './.github/workflows/ci-db-tests.yml'
|
||||
with:
|
||||
platform: 'postgres'
|
||||
|
||||
ms-db-tests:
|
||||
uses: './.github/workflows/ci-db-tests.yml'
|
||||
with:
|
||||
platform: 'ms'
|
||||
|
||||
unit-mutation-tests:
|
||||
needs:
|
||||
- unit-tests
|
||||
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||
with:
|
||||
test-group: unit
|
||||
|
||||
db-mutation-tests:
|
||||
needs:
|
||||
- sqlite-db-tests
|
||||
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||
with:
|
||||
test-group: db
|
||||
|
||||
api-mutation-tests:
|
||||
needs:
|
||||
- openswoole-api-tests
|
||||
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||
with:
|
||||
test-group: api
|
||||
|
||||
cli-mutation-tests:
|
||||
needs:
|
||||
- cli-tests
|
||||
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||
with:
|
||||
test-group: cli
|
||||
platform: ${{ matrix.platform }}
|
||||
|
||||
upload-coverage:
|
||||
needs:
|
||||
- unit-tests
|
||||
- openswoole-api-tests
|
||||
- api-tests
|
||||
- cli-tests
|
||||
- sqlite-db-tests
|
||||
runs-on: ubuntu-22.04
|
||||
- db-tests
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1']
|
||||
php-version: ['8.3']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: './.github/actions/ci-setup'
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
coverage: pcov
|
||||
ini-values: pcov.directory=module
|
||||
- uses: actions/download-artifact@v3
|
||||
extensions-cache-key: tests-extensions-${{ matrix.php-version }}
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: build
|
||||
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
||||
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
|
||||
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
||||
- run: mv build/coverage-cli/coverage-cli.cov build/coverage-cli.cov
|
||||
- run: wget https://phar.phpunit.de/phpcov-8.2.1.phar
|
||||
- run: php phpcov-8.2.1.phar merge build --clover build/clover.xml
|
||||
- run: vendor/bin/phpcov merge build --clover build/clover.xml
|
||||
- name: Publish coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./build/clover.xml
|
||||
|
||||
delete-artifacts:
|
||||
needs:
|
||||
- unit-mutation-tests
|
||||
- db-mutation-tests
|
||||
- api-mutation-tests
|
||||
- cli-mutation-tests
|
||||
- upload-coverage
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: geekyeggo/delete-artifact@v1
|
||||
- uses: geekyeggo/delete-artifact@v2
|
||||
with:
|
||||
name: |
|
||||
coverage-unit
|
||||
coverage-db
|
||||
coverage-api
|
||||
coverage-cli
|
||||
coverage-*
|
||||
|
||||
35
.github/workflows/publish-docker-image.yml
vendored
35
.github/workflows/publish-docker-image.yml
vendored
@@ -2,34 +2,25 @@ name: Build and publish docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths-ignore:
|
||||
- 'LICENSE'
|
||||
- '.*'
|
||||
- '*.md'
|
||||
- '*.xml'
|
||||
- '*.yml*'
|
||||
- '*.json5'
|
||||
- '*.neon'
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-openswoole:
|
||||
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
|
||||
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
|
||||
|
||||
build-roadrunner:
|
||||
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
image-name: shlinkio/shlink
|
||||
version-arg-name: SHLINK_VERSION
|
||||
platforms: 'linux/arm64/v8,linux/amd64'
|
||||
tags-suffix: roadrunner
|
||||
platforms: ${{ matrix.platforms }}
|
||||
tags-suffix: ${{ matrix.tag-suffix }}
|
||||
extra-build-args: |
|
||||
SHLINK_RUNTIME=rr
|
||||
SHLINK_RUNTIME=${{ matrix.runtime }}
|
||||
|
||||
33
.github/workflows/publish-release.yml
vendored
33
.github/workflows/publish-release.yml
vendored
@@ -7,34 +7,29 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
swoole: ['yes', 'no']
|
||||
php-version: ['8.2', '8.3'] # TODO 8.4
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: './.github/actions/ci-setup'
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-extensions: openswoole-4.12.1
|
||||
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||
install-deps: 'no'
|
||||
- if: ${{ matrix.swoole == 'yes' }}
|
||||
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||
- if: ${{ matrix.swoole == 'no' }}
|
||||
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
||||
- uses: actions/upload-artifact@v3
|
||||
- run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||
name: dist-files-${{ matrix.php-version }}
|
||||
path: build
|
||||
|
||||
publish:
|
||||
needs: ['build']
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: build
|
||||
- name: Publish release with assets
|
||||
@@ -48,12 +43,8 @@ jobs:
|
||||
|
||||
delete-artifacts:
|
||||
needs: ['publish']
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
swoole: ['yes', 'no']
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: geekyeggo/delete-artifact@v1
|
||||
- uses: geekyeggo/delete-artifact@v2
|
||||
with:
|
||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||
name: dist-files-*
|
||||
|
||||
11
.github/workflows/publish-swagger-spec.yml
vendored
11
.github/workflows/publish-swagger-spec.yml
vendored
@@ -7,26 +7,25 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1']
|
||||
php-version: ['8.2']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: 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
|
||||
- uses: './.github/actions/ci-setup'
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-extensions: openswoole-4.12.1
|
||||
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||
- run: composer swagger:inline
|
||||
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
||||
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
|
||||
- name: Publish spec
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.7
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
||||
repository-name: 'shlinkio/shlink-open-api-specs'
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,6 @@
|
||||
.idea
|
||||
bin/.rr.*
|
||||
bin/rr
|
||||
config/roadrunner/.pid
|
||||
.pid
|
||||
build
|
||||
!docker/build
|
||||
composer.lock
|
||||
@@ -10,8 +9,10 @@ vendor/
|
||||
data/database.sqlite
|
||||
data/shlink-tests.db
|
||||
data/GeoLite2-City.*
|
||||
data/infra/matomo
|
||||
docs/swagger-ui*
|
||||
docs/mercure.html
|
||||
docker-compose.override.yml
|
||||
.phpunit.result.cache
|
||||
docs/swagger/swagger-inlined.json
|
||||
phpcov*
|
||||
|
||||
2615
CHANGELOG.md
2615
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
|
||||
|
||||
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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
* 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.
|
||||
|
||||
@@ -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 api-key:generate` to get your first API key generated.
|
||||
|
||||
Once you finish this, you will have the project exposed in ports `8000` through nginx+php-fpm and `8080` through openswoole.
|
||||
Once you finish this, you will have the project exposed in ports `8800` through RoadRunner and `8000` through nginx+php-fpm.
|
||||
|
||||
> Note: The `indocker` shell script is a helper tool used to run commands inside the main docker container.
|
||||
|
||||
@@ -46,17 +46,18 @@ This is a simplified version of the project structure:
|
||||
```
|
||||
shlink
|
||||
├── bin
|
||||
│ └── cli
|
||||
│ ├── cli
|
||||
│ └── [...]
|
||||
├── config
|
||||
│ ├── autoload
|
||||
│ ├── params
|
||||
│ ├── config.php
|
||||
│ └── container.php
|
||||
│ ├── container.php
|
||||
│ └── [...]
|
||||
├── data
|
||||
│ ├── cache
|
||||
│ ├── locks
|
||||
│ ├── log
|
||||
│ ├── migrations
|
||||
│ └── proxies
|
||||
├── docs
|
||||
│ ├── adr
|
||||
@@ -67,18 +68,19 @@ shlink
|
||||
│ ├── Core
|
||||
│ └── Rest
|
||||
├── public
|
||||
│ └── [...]
|
||||
├── composer.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
The purposes of every folder are:
|
||||
|
||||
* `bin`: It contains the CLI tools. The `cli` one is the main entry point to run shlink from the command line.
|
||||
* `bin`: It contains the CLI tools. The `cli` one is the main entry point to run Shlink from the command line.
|
||||
* `config`: Contains application-wide configurations, which are later merged with the ones provided by every module.
|
||||
* `data`: Common runtime-generated git-ignored assets, like logs, caches, etc.
|
||||
* `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.
|
||||
* `module`: Contains a sub-folder for every module in the project. Modules contain the source code, tests and configurations for every context in the project.
|
||||
* `public`: Few assets (like `favicon.ico` or `robots.txt`) and the web entry point are stored here. This web entry point is not used when serving the app with openswoole.
|
||||
* `public`: Few assets (like `favicon.ico` or `robots.txt`) and the web entry point are stored here. This web entry point is not used when serving the app with RoadRunner.
|
||||
|
||||
## Project tests
|
||||
|
||||
@@ -94,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.
|
||||
|
||||
* **API tests**: These are E2E tests that spin up an instance of the app with openswoole, and test it from the outside by interacting with the REST API.
|
||||
* **API tests**: These are E2E tests that spin up an instance of the app with RoadRunner, and test it from the outside by interacting with the REST API.
|
||||
|
||||
These are the best tests to catch regressions, and to verify everything behaves as expected.
|
||||
|
||||
@@ -122,9 +124,14 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
|
||||
|
||||
* Run `./indocker composer test:api` to run API E2E tests. For these, the Postgres database engine is used.
|
||||
* Run `./indocker composer test:cli` to run CLI E2E tests. For these, the Maria DB database engine is used.
|
||||
* Run `./indocker composer infect:test` to run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
||||
* Run `./indocker composer ci` to run all previous commands together, parallelizing non-conflicting tasks as much as possible.
|
||||
|
||||
## Testing endpoints
|
||||
|
||||
The project provides a Swagger UI container for dev envs, which can be accessed in http://localhost:8005.
|
||||
|
||||
It will automatically load the contents of `docs/swagger`, so you can make any updates and they will get reflected.
|
||||
|
||||
## Pull request process
|
||||
|
||||
**Important!**: Before starting to work on a pull request, make sure you always [open an issue](https://github.com/shlinkio/shlink/issues/new/choose) first.
|
||||
|
||||
36
Dockerfile
36
Dockerfile
@@ -1,14 +1,15 @@
|
||||
FROM php:8.2-alpine3.17 as base
|
||||
FROM php:8.3-alpine3.19 as base
|
||||
|
||||
ARG SHLINK_VERSION=latest
|
||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||
ARG SHLINK_RUNTIME=openswoole
|
||||
ARG SHLINK_RUNTIME=rr
|
||||
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
|
||||
ENV OPENSWOOLE_VERSION 4.12.1
|
||||
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||
|
||||
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"
|
||||
ENV LC_ALL 'C'
|
||||
|
||||
WORKDIR /etc/shlink
|
||||
|
||||
@@ -23,12 +24,8 @@ RUN \
|
||||
apk del .dev-deps && \
|
||||
apk add --no-cache postgresql icu libzip libpng
|
||||
|
||||
# Install openswoole and sqlsrv driver for x86_64 builds
|
||||
# Install sqlsrv driver for x86_64 builds
|
||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
||||
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||
pecl install openswoole-${OPENSWOOLE_VERSION} && \
|
||||
docker-php-ext-enable openswoole ; \
|
||||
fi; \
|
||||
if [ $(uname -m) == "x86_64" ]; then \
|
||||
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
@@ -44,11 +41,6 @@ COPY . .
|
||||
COPY --from=composer:2 /usr/bin/composer ./composer.phar
|
||||
RUN apk add --no-cache git && \
|
||||
php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction && \
|
||||
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||
php composer.phar remove spiral/roadrunner spiral/roadrunner-jobs --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interactionc ; \
|
||||
elif [ $SHLINK_RUNTIME == 'rr' ]; then \
|
||||
php composer.phar remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction ; \
|
||||
fi; \
|
||||
php composer.phar clear-cache && \
|
||||
rm -r docker composer.* && \
|
||||
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
||||
@@ -58,10 +50,10 @@ RUN apk add --no-cache git && \
|
||||
FROM base
|
||||
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 && \
|
||||
if [ "$SHLINK_RUNTIME" == 'rr' ]; then \
|
||||
php ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr ; \
|
||||
php ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; \
|
||||
fi;
|
||||
|
||||
# Expose default port
|
||||
@@ -72,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/php.ini ${PHP_INI_DIR}/conf.d/
|
||||
|
||||
# Change the ownership of /etc/shlink/data to be writable, then change the user to non-root
|
||||
# FIXME Disabled for now, as it conflicts with ENABLE_PERIODIC_VISIT_LOCATE, which is used to configure a cron as root.
|
||||
# Ref: https://github.com/shlinkio/shlink/issues/1132
|
||||
#RUN chown 1001 /etc/shlink/data
|
||||
#RUN chown 1001 /etc/shlink/data/locks
|
||||
#RUN chown 1001 /etc/shlink/data/proxies
|
||||
#RUN chown 1001 /etc/shlink/data/cache
|
||||
#RUN chown 1001 /etc/shlink/data/log
|
||||
#USER 1001
|
||||
USER ${USER_ID}
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2023 Alejandro Celaya
|
||||
Copyright (c) 2016-2024 Alejandro Celaya
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
14
README.md
14
README.md
@@ -2,12 +2,13 @@
|
||||
|
||||
[](https://github.com/shlinkio/shlink/actions/workflows/ci.yml?query=workflow%3A%22Continuous+integration%22)
|
||||
[](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://hub.docker.com/r/shlinkio/shlink/)
|
||||
[](https://github.com/shlinkio/shlink/blob/main/LICENSE)
|
||||
[](https://twitter.com/shlinkio)
|
||||
|
||||
[](https://fosstodon.org/@shlinkio)
|
||||
[](https://bsky.app/profile/shlinkio.bsky.social)
|
||||
[](https://twitter.com/shlinkio)
|
||||
[](https://slnk.to/donate)
|
||||
|
||||
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
|
||||
@@ -32,18 +33,17 @@ You can learn how to use the official docker image by reading [the docs](https:/
|
||||
|
||||
The idea is that you can just generate a container using the image and provide the custom config via env vars.
|
||||
|
||||
## Self hosted
|
||||
## Self-hosted
|
||||
|
||||
First, make sure the host where you are going to run shlink fulfills these requirements:
|
||||
|
||||
* PHP 8.1 or 8.2
|
||||
* PHP 8.2 or 8.3
|
||||
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
||||
* apcu extension is recommended if you don't plan to use openswoole.
|
||||
* apcu extension is recommended if you don't plan to use RoadRunner.
|
||||
* xml extension is required if you want to generate QR codes in svg format.
|
||||
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
|
||||
* You will also need the corresponding pdo variation for the database you are planning to use: `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv` or `pdo_sqlite`.
|
||||
* The [openswoole](https://openswoole.com/) PHP extension (if you plan to serve Shlink with openswoole) or the web server of your choice with PHP integration (like Apache or Nginx).
|
||||
|
||||
### Download
|
||||
|
||||
@@ -53,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.
|
||||
|
||||
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink*_dist.zip` file that suits your needs. You will find one for every supported PHP version and with/without openswoole integration.
|
||||
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink*_dist.zip` file that suits your needs. You will find one for every supported PHP version.
|
||||
|
||||
Finally, decompress the file in the location of your choice.
|
||||
|
||||
|
||||
50
UPGRADE.md
50
UPGRADE.md
@@ -1,5 +1,55 @@
|
||||
# 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
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
|
||||
@@ -27,6 +28,9 @@ use function Shlinkio\Shlink\Config\env;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks();
|
||||
$requestIdMiddleware = $container->get(RequestIdMiddleware::class);
|
||||
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks(
|
||||
fn (string $requestId) => $requestIdMiddleware->setCurrentRequestId($requestId),
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export APP_ENV=test
|
||||
export TEST_ENV=api
|
||||
export TEST_RUNTIME="${TEST_RUNTIME:-"openswoole"}"
|
||||
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"}"
|
||||
|
||||
@@ -13,26 +13,19 @@ mkdir data/log/api-tests
|
||||
touch $OUTPUT_LOGS
|
||||
|
||||
# Try to stop server just in case it hanged in last execution
|
||||
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f
|
||||
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f -w .
|
||||
|
||||
echo 'Starting server...'
|
||||
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:start -d
|
||||
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -c=config/roadrunner/.rr.dev.yml \
|
||||
-o=http.address=0.0.0.0:9999 \
|
||||
-o=logs.encoding=json \
|
||||
-o=logs.channels.http.encoding=json \
|
||||
-o=logs.channels.server.encoding=json \
|
||||
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -w . -c=config/roadrunner/.rr.test.yml \
|
||||
-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 $*
|
||||
testsExitCode=$?
|
||||
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --testdox-summary $*
|
||||
TESTS_EXIT_CODE=$?
|
||||
|
||||
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -c config/roadrunner/.rr.dev.yml -o=http.address=0.0.0.0:9999
|
||||
[ "$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 $testsExitCode
|
||||
exit $TESTS_EXIT_CODE
|
||||
|
||||
21
build.sh
21
build.sh
@@ -1,17 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || ([ "$#" == 2 ] && [ "$2" != "--no-swoole" ]); then
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage:" >&2
|
||||
echo " $0 {version} [--no-swoole]" >&2
|
||||
echo " $0 {version}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$1
|
||||
noSwoole=$2
|
||||
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
||||
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_openswoole"
|
||||
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
||||
distId="shlink${version}_php${phpVersion}_dist"
|
||||
builtContent="./build/${distId}"
|
||||
projectdir=$(pwd)
|
||||
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
||||
@@ -30,23 +28,14 @@ cd "${builtContent}"
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing dependencies with $composerBin..."
|
||||
composerFlags="--optimize-autoloader --no-progress --no-interaction"
|
||||
${composerBin} self-update
|
||||
${composerBin} install --no-dev --prefer-dist $composerFlags
|
||||
|
||||
if [[ $noSwoole ]]; then
|
||||
# If generating a dist not for openswoole, uninstall mezzio-swoole
|
||||
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
|
||||
else
|
||||
# If generating a dist for openswoole, uninstall RoadRunner
|
||||
${composerBin} remove spiral/roadrunner spiral/roadrunner-jobs --with-all-dependencies --update-no-dev $composerFlags
|
||||
fi
|
||||
${composerBin} install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction
|
||||
|
||||
# Delete development files
|
||||
echo 'Deleting dev files...'
|
||||
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
|
||||
|
||||
# Compressing file
|
||||
|
||||
177
composer.json
177
composer.json
@@ -12,71 +12,73 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.2",
|
||||
"ext-curl": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"akrabat/ip-address-middleware": "^2.1",
|
||||
"cakephp/chronos": "^2.3",
|
||||
"doctrine/migrations": "^3.5",
|
||||
"doctrine/orm": "^2.13.3",
|
||||
"endroid/qr-code": "^4.6",
|
||||
"geoip2/geoip2": "^2.13",
|
||||
"cakephp/chronos": "^3.0.2",
|
||||
"doctrine/dbal": "^4.1",
|
||||
"doctrine/migrations": "^3.6",
|
||||
"doctrine/orm": "^3.2",
|
||||
"endroid/qr-code": "^5.0",
|
||||
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||
"geoip2/geoip2": "^3.0",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"happyr/doctrine-specification": "^2.0",
|
||||
"jaybizzle/crawler-detect": "^1.2.112",
|
||||
"laminas/laminas-config": "^3.7",
|
||||
"laminas/laminas-config-aggregator": "^1.11",
|
||||
"laminas/laminas-diactoros": "^2.19",
|
||||
"laminas/laminas-inputfilter": "^2.22",
|
||||
"laminas/laminas-servicemanager": "^3.19",
|
||||
"laminas/laminas-stdlib": "^3.15",
|
||||
"lcobucci/jwt": "^4.2",
|
||||
"league/uri": "^6.8",
|
||||
"lstrojny/functional-php": "^1.17",
|
||||
"mezzio/mezzio": "^3.13",
|
||||
"mezzio/mezzio-fastroute": "^3.7",
|
||||
"mezzio/mezzio-problem-details": "^1.7",
|
||||
"mezzio/mezzio-swoole": "^4.5",
|
||||
"hidehalo/nanoid-php": "^1.1",
|
||||
"jaybizzle/crawler-detect": "^1.2.116",
|
||||
"laminas/laminas-config": "^3.8",
|
||||
"laminas/laminas-config-aggregator": "^1.15",
|
||||
"laminas/laminas-diactoros": "^3.3",
|
||||
"laminas/laminas-inputfilter": "^2.27",
|
||||
"laminas/laminas-servicemanager": "^3.21",
|
||||
"laminas/laminas-stdlib": "^3.17",
|
||||
"matomo/matomo-php-tracker": "^3.2",
|
||||
"mezzio/mezzio": "^3.17",
|
||||
"mezzio/mezzio-fastroute": "^3.11",
|
||||
"mezzio/mezzio-problem-details": "^1.13",
|
||||
"mlocati/ip-lib": "^1.18",
|
||||
"mobiledetect/mobiledetectlib": "^3.74",
|
||||
"ocramius/proxy-manager": "^2.14",
|
||||
"pagerfanta/core": "^3.6",
|
||||
"php-middleware/request-id": "^4.1",
|
||||
"pugx/shortid-php": "^1.1",
|
||||
"ramsey/uuid": "^4.5",
|
||||
"shlinkio/shlink-common": "^5.3",
|
||||
"shlinkio/shlink-config": "^2.4",
|
||||
"shlinkio/shlink-event-dispatcher": "^2.6",
|
||||
"shlinkio/shlink-importer": "^5.0",
|
||||
"shlinkio/shlink-installer": "^8.3",
|
||||
"shlinkio/shlink-ip-geolocation": "^3.2",
|
||||
"spiral/roadrunner": "^2.11",
|
||||
"spiral/roadrunner-jobs": "^2.5",
|
||||
"symfony/console": "^6.1",
|
||||
"symfony/filesystem": "^6.1",
|
||||
"symfony/lock": "^6.1",
|
||||
"symfony/process": "^6.1",
|
||||
"symfony/string": "^6.1"
|
||||
"mobiledetect/mobiledetectlib": "^4.8",
|
||||
"pagerfanta/core": "^3.8",
|
||||
"ramsey/uuid": "^4.7",
|
||||
"shlinkio/doctrine-specification": "^2.1.1",
|
||||
"shlinkio/shlink-common": "^6.3",
|
||||
"shlinkio/shlink-config": "^3.2.1",
|
||||
"shlinkio/shlink-event-dispatcher": "^4.1",
|
||||
"shlinkio/shlink-importer": "^5.3.2",
|
||||
"shlinkio/shlink-installer": "^9.2",
|
||||
"shlinkio/shlink-ip-geolocation": "^4.0",
|
||||
"shlinkio/shlink-json": "^1.1",
|
||||
"spiral/roadrunner": "^2024.1",
|
||||
"spiral/roadrunner-cli": "^2.6",
|
||||
"spiral/roadrunner-http": "^3.5",
|
||||
"spiral/roadrunner-jobs": "^4.5",
|
||||
"symfony/console": "^7.0",
|
||||
"symfony/filesystem": "^7.0",
|
||||
"symfony/lock": "^7.0",
|
||||
"symfony/process": "^7.0",
|
||||
"symfony/string": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"cebe/php-openapi": "^1.7",
|
||||
"devizzent/cebe-php-openapi": "^1.0.1",
|
||||
"devster/ubench": "^2.1",
|
||||
"dms/phpunit-arraysubset-asserts": "^0.4.0",
|
||||
"infection/infection": "^0.26.15",
|
||||
"openswoole/ide-helper": "~4.11.5",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpstan/phpstan-doctrine": "^1.3",
|
||||
"phpstan/phpstan-phpunit": "^1.1",
|
||||
"phpstan/phpstan-symfony": "^1.2",
|
||||
"phpunit/php-code-coverage": "^9.2",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"phpstan/phpstan-doctrine": "^1.4",
|
||||
"phpstan/phpstan-phpunit": "^1.4",
|
||||
"phpstan/phpstan-symfony": "^1.4",
|
||||
"phpunit/php-code-coverage": "^11.0",
|
||||
"phpunit/phpcov": "^10.0",
|
||||
"phpunit/phpunit": "^11.3",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"shlinkio/php-coding-standard": "~2.3.0",
|
||||
"shlinkio/shlink-test-utils": "^3.4",
|
||||
"symfony/var-dumper": "^6.1",
|
||||
"veewee/composer-run-parallel": "^1.1"
|
||||
"shlinkio/shlink-test-utils": "^4.1",
|
||||
"symfony/var-dumper": "^7.0",
|
||||
"veewee/composer-run-parallel": "^1.3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/var-exporter": ">=6.3.9,<=6.4.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -86,6 +88,7 @@
|
||||
},
|
||||
"files": [
|
||||
"config/constants.php",
|
||||
"module/Core/functions/array-utils.php",
|
||||
"module/Core/functions/functions.php"
|
||||
]
|
||||
},
|
||||
@@ -106,61 +109,42 @@
|
||||
},
|
||||
"scripts": {
|
||||
"ci": [
|
||||
"@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:test:cli infect:ci:unit infect:ci:db"
|
||||
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:postgres test:db:mysql test:db:maria test:db:ms",
|
||||
"@parallel test:api:ci test:cli:ci"
|
||||
],
|
||||
"cs": "phpcs",
|
||||
"cs": "phpcs -s",
|
||||
"cs:fix": "phpcbf",
|
||||
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test* module/*/config config docker/config data/migrations --level=8",
|
||||
"stan": "APP_ENV=test php vendor/bin/phpstan analyse",
|
||||
"test": [
|
||||
"@parallel test:unit test:db",
|
||||
"@parallel test:api test:cli"
|
||||
],
|
||||
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --testdox",
|
||||
"test:unit:ci": "@test:unit --coverage-php=build/coverage-unit.cov --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
||||
"test:unit": "COLUMNS=120 vendor/bin/phpunit --order-by=random --testdox --testdox-summary",
|
||||
"test:unit:ci": "@test:unit --coverage-php=build/coverage-unit.cov",
|
||||
"test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
|
||||
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
||||
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
||||
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
|
||||
"test:db:maria": "DB_DRIVER=maria composer test:db:sqlite",
|
||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
||||
"test: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",
|
||||
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite -- $*",
|
||||
"test:db:maria": "DB_DRIVER=maria composer test:db:sqlite -- $*",
|
||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite -- $*",
|
||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite -- $*",
|
||||
"test:api": "bin/test/run-api-tests.sh",
|
||||
"test:api:rr": "TEST_RUNTIME=rr bin/test/run-api-tests.sh",
|
||||
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
|
||||
"test:api:pretty": "GENERATE_COVERAGE=pretty composer test:api",
|
||||
"test:cli": "APP_ENV=test DB_DRIVER=maria TEST_ENV=cli php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-cli.xml --log-junit=build/coverage-cli/junit.xml",
|
||||
"test:cli:ci": "GENERATE_COVERAGE=yes composer test:cli",
|
||||
"test:cli:pretty": "GENERATE_COVERAGE=pretty composer test:cli",
|
||||
"infect:ci:base": "infection --threads=max --only-covered --only-covering-test-cases --skip-initial-tests",
|
||||
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
||||
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5",
|
||||
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json5",
|
||||
"infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=90 --configuration=infection-cli.json5",
|
||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli",
|
||||
"infect:test": [
|
||||
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
||||
"@infect:ci"
|
||||
],
|
||||
"infect:test:unit": [
|
||||
"@test:unit:ci",
|
||||
"@infect:ci:unit"
|
||||
],
|
||||
"infect:test:api": [
|
||||
"@test:api:ci",
|
||||
"@infect:ci:api"
|
||||
],
|
||||
"infect:test:cli": [
|
||||
"@test:cli:ci",
|
||||
"@infect:ci:cli"
|
||||
],
|
||||
"test:api:sqlite": "DB_DRIVER=sqlite composer test:api -- $*",
|
||||
"test:api:mysql": "DB_DRIVER=mysql composer test:api -- $*",
|
||||
"test:api:maria": "DB_DRIVER=maria composer test:api -- $*",
|
||||
"test:api:mssql": "DB_DRIVER=mssql composer test:api -- $*",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
||||
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||
},
|
||||
"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\"</>",
|
||||
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
||||
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
||||
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
|
||||
@@ -181,10 +165,6 @@
|
||||
"test:cli": "<fg=blue;options=bold>Runs CLI test suites</>",
|
||||
"test:cli:ci": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage for CI</>",
|
||||
"test:cli:pretty": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage in HTML format</>",
|
||||
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
||||
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
||||
"swagger:inline": "<fg=blue;options=bold>Inlines swagger docs in a single file</>",
|
||||
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
||||
@@ -195,7 +175,6 @@
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"infection/extension-installer": true,
|
||||
"veewee/composer-run-parallel": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return (static function (): array {
|
||||
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
|
||||
$redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv(false)];
|
||||
$redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv()];
|
||||
$cacheRedisBlock = $redisServers === null ? [] : [
|
||||
'redis' => [
|
||||
'servers' => $redisServers,
|
||||
@@ -16,7 +16,7 @@ return (static function (): array {
|
||||
|
||||
return [
|
||||
'cache' => [
|
||||
'namespace' => 'Shlink',
|
||||
'namespace' => EnvVars::CACHE_NAMESPACE->loadFromEnv(),
|
||||
...$cacheRedisBlock,
|
||||
],
|
||||
'redis' => $redis,
|
||||
|
||||
@@ -8,7 +8,7 @@ return [
|
||||
|
||||
'debug' => false,
|
||||
|
||||
// Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console
|
||||
// Disabling config cache for cli, ensures it's never used for RoadRunner, and also that console
|
||||
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use Mezzio\Application;
|
||||
use Mezzio\Container;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\ServerRequestFactoryInterface;
|
||||
@@ -11,16 +13,18 @@ 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 [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
PSR7Worker::class => ConfigAbstractFactory::class,
|
||||
Filesystem::class => InvokableFactory::class,
|
||||
],
|
||||
|
||||
'delegators' => [
|
||||
Mezzio\Application::class => [
|
||||
Application::class => [
|
||||
Container\ApplicationConfigInjectionDelegator::class,
|
||||
],
|
||||
],
|
||||
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\Events;
|
||||
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;
|
||||
|
||||
return (static function (): array {
|
||||
$driver = EnvVars::DB_DRIVER->loadFromEnv();
|
||||
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
|
||||
$isMysqlCompatible = contains($driver, ['maria', 'mysql']);
|
||||
|
||||
$resolveDriver = static fn () => match ($driver) {
|
||||
'postgres' => 'pdo_pgsql',
|
||||
'mssql' => 'pdo_sqlsrv',
|
||||
default => 'pdo_mysql',
|
||||
};
|
||||
$resolveDefaultPort = static fn () => match ($driver) {
|
||||
'postgres' => '5432',
|
||||
'mssql' => '1433',
|
||||
default => '3306',
|
||||
$readCredentialAsString = static function (EnvVars $envVar): string|null {
|
||||
$value = $envVar->loadFromEnv();
|
||||
return $value === null ? null : (string) $value;
|
||||
};
|
||||
$resolveCharset = static fn () => match ($driver) {
|
||||
// This does not determine charsets or collations in tables or columns, but the charset used in the data
|
||||
@@ -28,6 +30,7 @@ return (static function (): array {
|
||||
'postgres' => 'utf8',
|
||||
default => null,
|
||||
};
|
||||
|
||||
$resolveConnection = static fn () => match ($driver) {
|
||||
null, 'sqlite' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
@@ -35,11 +38,11 @@ return (static function (): array {
|
||||
],
|
||||
default => [
|
||||
'driver' => $resolveDriver(),
|
||||
'dbname' => EnvVars::DB_NAME->loadFromEnv('shlink'),
|
||||
'user' => EnvVars::DB_USER->loadFromEnv(),
|
||||
'password' => EnvVars::DB_PASSWORD->loadFromEnv(),
|
||||
'host' => EnvVars::DB_HOST->loadFromEnv(EnvVars::DB_UNIX_SOCKET->loadFromEnv()),
|
||||
'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
|
||||
'dbname' => EnvVars::DB_NAME->loadFromEnv(),
|
||||
'user' => $readCredentialAsString(EnvVars::DB_USER),
|
||||
'password' => $readCredentialAsString(EnvVars::DB_PASSWORD),
|
||||
'host' => EnvVars::DB_HOST->loadFromEnv(),
|
||||
'port' => EnvVars::DB_PORT->loadFromEnv(),
|
||||
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
|
||||
'charset' => $resolveCharset(),
|
||||
'driverOptions' => $driver !== 'mssql' ? [] : [
|
||||
@@ -55,6 +58,10 @@ return (static function (): array {
|
||||
'proxies_dir' => 'data/proxies',
|
||||
'load_mappings_using_functional_style' => true,
|
||||
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||
'listeners' => [
|
||||
Events::onFlush => [ShortUrlVisitsCountTracker::class, OrphanVisitsCountTracker::class],
|
||||
Events::postFlush => [ShortUrlVisitsCountTracker::class, OrphanVisitsCountTracker::class],
|
||||
],
|
||||
],
|
||||
'connection' => $resolveConnection(),
|
||||
],
|
||||
|
||||
@@ -6,12 +6,40 @@ return [
|
||||
|
||||
'entity_manager' => [
|
||||
'connection' => [
|
||||
// MySQL
|
||||
'user' => 'root',
|
||||
'password' => 'root',
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => 'shlink_db_mysql',
|
||||
'dbname' => 'shlink',
|
||||
// 'dbname' => 'shlink_foo',
|
||||
'charset' => 'utf8mb4',
|
||||
|
||||
// MariaDB
|
||||
// 'user' => 'root',
|
||||
// 'password' => 'root',
|
||||
// 'driver' => 'pdo_mysql',
|
||||
// 'host' => 'shlink_db_maria',
|
||||
// 'dbname' => 'shlink_foo',
|
||||
// 'charset' => 'utf8mb4',
|
||||
|
||||
// Postgres
|
||||
// 'user' => 'postgres',
|
||||
// 'password' => 'root',
|
||||
// 'driver' => 'pdo_pgsql',
|
||||
// 'host' => 'shlink_db_postgres',
|
||||
// 'dbname' => 'shlink_foo',
|
||||
// 'charset' => 'utf8',
|
||||
|
||||
// MSSQL
|
||||
// 'user' => 'sa',
|
||||
// 'password' => 'Passw0rd!',
|
||||
// 'driver' => 'pdo_sqlsrv',
|
||||
// 'host' => 'shlink_db_ms',
|
||||
// 'dbname' => 'shlink_foo',
|
||||
// 'driverOptions' => [
|
||||
// 'TrustServerCertificate' => 'true',
|
||||
// ],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ return [
|
||||
|
||||
'installer' => [
|
||||
'enabled_options' => [
|
||||
Option\Server\RuntimeConfigOption::class,
|
||||
Option\Server\MemoryLimitConfigOption::class,
|
||||
Option\Database\DatabaseDriverConfigOption::class,
|
||||
Option\Database\DatabaseNameConfigOption::class,
|
||||
Option\Database\DatabaseHostConfigOption::class,
|
||||
@@ -20,16 +22,13 @@ return [
|
||||
Option\Database\DatabaseUnixSocketConfigOption::class,
|
||||
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
||||
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
||||
Option\Visit\VisitsWebhooksConfigOption::class,
|
||||
Option\Visit\OrphanVisitsWebhooksConfigOption::class,
|
||||
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
||||
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
||||
Option\Redirect\Regular404RedirectConfigOption::class,
|
||||
Option\Visit\VisitsThresholdConfigOption::class,
|
||||
Option\BasePathConfigOption::class,
|
||||
Option\TimezoneConfigOption::class,
|
||||
Option\Worker\TaskWorkerNumConfigOption::class,
|
||||
Option\Worker\WebWorkerNumConfigOption::class,
|
||||
Option\Cache\CacheNamespaceConfigOption::class,
|
||||
Option\Redis\RedisServersConfigOption::class,
|
||||
Option\Redis\RedisSentinelServiceConfigOption::class,
|
||||
Option\Redis\RedisPubSubConfigOption::class,
|
||||
@@ -46,6 +45,8 @@ return [
|
||||
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\OrphanVisitsTrackingConfigOption::class,
|
||||
Option\Tracking\DisableTrackParamConfigOption::class,
|
||||
@@ -59,12 +60,21 @@ return [
|
||||
Option\QrCode\DefaultFormatConfigOption::class,
|
||||
Option\QrCode\DefaultErrorCorrectionConfigOption::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\RabbitMqHostConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqUseSslConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
||||
Option\Matomo\MatomoEnabledConfigOption::class,
|
||||
Option\Matomo\MatomoBaseUrlConfigOption::class,
|
||||
Option\Matomo\MatomoSiteIdConfigOption::class,
|
||||
Option\Matomo\MatomoApiTokenConfigOption::class,
|
||||
],
|
||||
|
||||
'installation_commands' => [
|
||||
@@ -86,6 +96,9 @@ return [
|
||||
InstallationCommand::API_KEY_GENERATE->value => [
|
||||
'command' => 'bin/cli ' . Command\Api\GenerateKeyCommand::NAME,
|
||||
],
|
||||
InstallationCommand::API_KEY_CREATE->value => [
|
||||
'command' => 'bin/cli ' . Command\Api\InitialApiKeyCommand::NAME,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
use Shlinkio\Shlink\Common\Lock\NamespacedStore;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
use Symfony\Component\Lock;
|
||||
@@ -22,11 +23,12 @@ return [
|
||||
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
||||
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
||||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||
NamespacedStore::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'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,
|
||||
],
|
||||
'delegators' => [
|
||||
@@ -39,6 +41,8 @@ return [
|
||||
ConfigAbstractFactory::class => [
|
||||
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
||||
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
||||
NamespacedStore::class => [Lock\Store\RedisStore::class, 'config.cache.namespace'],
|
||||
|
||||
Lock\LockFactory::class => ['lock_store'],
|
||||
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
||||
],
|
||||
|
||||
@@ -4,51 +4,63 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use Monolog\Level;
|
||||
use Monolog\Logger;
|
||||
use PhpMiddleware\RequestId;
|
||||
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;
|
||||
|
||||
$common = [
|
||||
'level' => Level::Info->value,
|
||||
'processors' => [RequestId\MonologProcessor::class],
|
||||
'line_format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%',
|
||||
];
|
||||
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||
|
||||
return [
|
||||
return (static function (): array {
|
||||
$common = [
|
||||
'level' => Level::Info->value,
|
||||
'processors' => [RequestIdMiddleware::class],
|
||||
'line_format' =>
|
||||
'[%datetime%] [%extra.' . RequestIdMiddleware::ATTRIBUTE . '%] %channel%.%level_name% - %message%',
|
||||
];
|
||||
|
||||
'logger' => [
|
||||
'Shlink' => [
|
||||
'type' => LoggerType::FILE->value,
|
||||
...$common,
|
||||
],
|
||||
'Access' => [
|
||||
'type' => LoggerType::STREAM->value,
|
||||
...$common,
|
||||
],
|
||||
],
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
'Logger_Shlink' => [LoggerFactory::class, 'Shlink'],
|
||||
'Logger_Access' => [LoggerFactory::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',
|
||||
'logger' => [
|
||||
'Shlink' => [
|
||||
'type' => LoggerType::FILE->value,
|
||||
...$common,
|
||||
],
|
||||
'Access' => [
|
||||
'type' => LoggerType::STREAM->value,
|
||||
'destination' => 'php://stderr',
|
||||
'add_new_line' => ! runningInRoadRunner(),
|
||||
...$common,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
'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],
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
|
||||
@@ -5,14 +5,12 @@ declare(strict_types=1);
|
||||
use Monolog\Level;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||
|
||||
$isSwoole = extension_loaded('openswoole');
|
||||
|
||||
return [
|
||||
|
||||
'logger' => [
|
||||
'Shlink' => [
|
||||
// For swoole, send logs as stream
|
||||
'type' => $isSwoole ? LoggerType::STREAM->value : LoggerType::FILE->value,
|
||||
'type' => LoggerType::STREAM->value,
|
||||
'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' => '...',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -15,7 +15,7 @@ return (static function (): array {
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => $publicUrl,
|
||||
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv($publicUrl),
|
||||
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv(),
|
||||
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
|
||||
'jwt_issuer' => 'Shlink',
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
return [
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => 'http://localhost:8001',
|
||||
'public_hub_url' => 'http://localhost:8002',
|
||||
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
||||
'jwt_secret' => 'mercure_jwt_key_long_enough_to_avoid_error',
|
||||
],
|
||||
|
||||
@@ -7,15 +7,17 @@ namespace Shlinkio\Shlink;
|
||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||
use Mezzio\ProblemDetails;
|
||||
use Mezzio\Router;
|
||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
||||
use RKA\Middleware\IpAddress;
|
||||
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
||||
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'error-handler' => [
|
||||
'middleware' => [
|
||||
AccessLogMiddleware::class,
|
||||
ContentLengthMiddleware::class,
|
||||
RequestIdMiddleware::class,
|
||||
ErrorHandler::class,
|
||||
@@ -45,7 +47,6 @@ return [
|
||||
'rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
|
||||
Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||
Rest\Middleware\BodyParserMiddleware::class,
|
||||
Rest\Middleware\AuthenticationMiddleware::class,
|
||||
|
||||
@@ -4,24 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_MARGIN;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ROUND_BLOCK_SIZE;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
|
||||
|
||||
return [
|
||||
|
||||
'qr_codes' => [
|
||||
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(DEFAULT_QR_CODE_SIZE),
|
||||
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
|
||||
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
|
||||
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(
|
||||
DEFAULT_QR_CODE_ERROR_CORRECTION,
|
||||
),
|
||||
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(
|
||||
DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
|
||||
),
|
||||
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(),
|
||||
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(),
|
||||
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(),
|
||||
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(),
|
||||
'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(),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -7,15 +7,13 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
return [
|
||||
|
||||
'rabbitmq' => [
|
||||
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(false),
|
||||
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(),
|
||||
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
|
||||
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv('5672'),
|
||||
'use_ssl' => (bool) EnvVars::RABBITMQ_USE_SSL->loadFromEnv(),
|
||||
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv(),
|
||||
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
|
||||
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
|
||||
|
||||
// Deprecated
|
||||
'legacy_visits_publishing' => (bool) EnvVars::RABBITMQ_LEGACY_VISITS_PUBLISHING->loadFromEnv(false),
|
||||
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv(),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -7,6 +7,7 @@ return [
|
||||
'rabbitmq' => [
|
||||
'enabled' => true,
|
||||
'host' => 'shlink_rabbitmq',
|
||||
'port' => 5672,
|
||||
'user' => 'rabbit',
|
||||
'password' => 'rabbit',
|
||||
],
|
||||
|
||||
@@ -4,9 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
|
||||
|
||||
return [
|
||||
|
||||
'not_found_redirects' => [
|
||||
@@ -16,10 +13,8 @@ return [
|
||||
],
|
||||
|
||||
'redirects' => [
|
||||
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE->value),
|
||||
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
|
||||
DEFAULT_REDIRECT_CACHE_LIFETIME,
|
||||
),
|
||||
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(),
|
||||
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use PhpMiddleware\RequestId;
|
||||
use Shlinkio\Shlink\Common\Logger\Processor\BackwardsCompatibleMonologProcessorDelegator;
|
||||
|
||||
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,
|
||||
],
|
||||
'delegators' => [
|
||||
RequestId\MonologProcessor::class => [
|
||||
BackwardsCompatibleMonologProcessorDelegator::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()),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -8,10 +8,12 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'base_path' => EnvVars::BASE_PATH->loadFromEnv(''),
|
||||
'base_path' => EnvVars::BASE_PATH->loadFromEnv(),
|
||||
|
||||
'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',
|
||||
],
|
||||
],
|
||||
|
||||
@@ -17,12 +17,11 @@ use Shlinkio\Shlink\Rest\Middleware\Mercure\NotConfiguredMercureErrorHandler;
|
||||
use function sprintf;
|
||||
|
||||
return (static function (): array {
|
||||
$contentNegotiationMiddleware = Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class;
|
||||
$dropDomainMiddleware = Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class;
|
||||
$overrideDomainMiddleware = Middleware\ShortUrl\OverrideDomainMiddleware::class;
|
||||
|
||||
// TODO This should be based on config, not the env var
|
||||
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false) ? '[/]' : '';
|
||||
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv() ? '[/]' : '';
|
||||
|
||||
return [
|
||||
|
||||
@@ -32,23 +31,31 @@ return (static function (): array {
|
||||
...ConfigProvider::applyRoutesPrefix([
|
||||
Action\HealthAction::getRouteDef(),
|
||||
|
||||
// Visits
|
||||
// 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([
|
||||
$contentNegotiationMiddleware,
|
||||
$dropDomainMiddleware,
|
||||
$overrideDomainMiddleware,
|
||||
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class,
|
||||
]),
|
||||
Action\ShortUrl\SingleStepCreateShortUrlAction::getRouteDef([
|
||||
$contentNegotiationMiddleware,
|
||||
Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class,
|
||||
$overrideDomainMiddleware,
|
||||
]),
|
||||
Action\ShortUrl\EditShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
use function Shlinkio\Shlink\Config\getOpenswooleConfigFromEnv;
|
||||
|
||||
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
||||
|
||||
return (static function (): array {
|
||||
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM->loadFromEnv(16);
|
||||
|
||||
return [
|
||||
|
||||
'mezzio-swoole' => [
|
||||
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
||||
'enable_coroutine' => false,
|
||||
|
||||
'swoole-http-server' => [
|
||||
'host' => '0.0.0.0',
|
||||
'port' => (int) EnvVars::PORT->loadFromEnv(8080),
|
||||
'process-name' => 'shlink',
|
||||
|
||||
'options' => [
|
||||
...getOpenswooleConfigFromEnv(),
|
||||
'worker_num' => (int) EnvVars::WEB_WORKER_NUM->loadFromEnv(16),
|
||||
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'mezzio-swoole' => [
|
||||
'hot-code-reload' => [
|
||||
'enable' => true,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -4,40 +4,35 @@ declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return (static function (): array {
|
||||
/** @var string|null $disableTrackingFrom */
|
||||
$disableTrackingFrom = EnvVars::DISABLE_TRACKING_FROM->loadFromEnv();
|
||||
use function Shlinkio\Shlink\Core\splitByComma;
|
||||
|
||||
return [
|
||||
return [
|
||||
|
||||
'tracking' => [
|
||||
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
||||
// This applies only if IP address tracking is enabled
|
||||
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(true),
|
||||
'tracking' => [
|
||||
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
||||
// This applies only if IP address tracking is enabled
|
||||
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(),
|
||||
|
||||
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
|
||||
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(),
|
||||
|
||||
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
|
||||
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
|
||||
|
||||
// If true, visits will not be tracked at all
|
||||
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
|
||||
// If true, visits will not be tracked at all
|
||||
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(),
|
||||
|
||||
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
|
||||
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(),
|
||||
|
||||
// If true, the referrer will not be tracked
|
||||
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
|
||||
// If true, the referrer will not be tracked
|
||||
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(),
|
||||
|
||||
// If true, the user agent will not be tracked
|
||||
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
|
||||
// If true, the user agent will not be tracked
|
||||
'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
|
||||
'disable_tracking_from' => $disableTrackingFrom === null
|
||||
? []
|
||||
: array_map(trim(...), explode(',', $disableTrackingFrom)),
|
||||
],
|
||||
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
||||
'disable_tracking_from' => splitByComma(EnvVars::DISABLE_TRACKING_FROM->loadFromEnv()),
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
];
|
||||
|
||||
@@ -5,29 +5,28 @@ declare(strict_types=1);
|
||||
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;
|
||||
|
||||
return (static function (): array {
|
||||
$shortCodesLength = max(
|
||||
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
|
||||
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(),
|
||||
MIN_SHORT_CODES_LENGTH,
|
||||
);
|
||||
$modeFromEnv = EnvVars::SHORT_URL_MODE->loadFromEnv(ShortUrlMode::STRICT->value);
|
||||
$modeFromEnv = EnvVars::SHORT_URL_MODE->loadFromEnv();
|
||||
$mode = ShortUrlMode::tryFrom($modeFromEnv) ?? ShortUrlMode::STRICT;
|
||||
|
||||
return [
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
|
||||
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv(true)) ? 'https' : 'http',
|
||||
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(''),
|
||||
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv()) ? 'https' : 'http',
|
||||
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(),
|
||||
],
|
||||
'default_short_codes_length' => $shortCodesLength,
|
||||
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(false),
|
||||
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(false),
|
||||
'multi_segment_slugs_enabled' => (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(false),
|
||||
'trailing_slash_enabled' => (bool) EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false),
|
||||
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(),
|
||||
'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,7 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Config\runningInOpenswoole;
|
||||
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||
|
||||
return [
|
||||
@@ -12,11 +11,9 @@ return [
|
||||
'schema' => 'http',
|
||||
'hostname' => sprintf('localhost:%s', match (true) {
|
||||
runningInRoadRunner() => '8800',
|
||||
runningInOpenswoole() => '8080',
|
||||
default => '8000',
|
||||
}),
|
||||
],
|
||||
'auto_resolve_titles' => true,
|
||||
// 'multi_segment_slugs_enabled' => true,
|
||||
// 'trailing_slash_enabled' => true,
|
||||
],
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
// Deprecated. Webhooks are no longer supported. To be removed in Shlink 4.0.0
|
||||
return (static function (): array {
|
||||
$webhooks = EnvVars::VISITS_WEBHOOKS->loadFromEnv();
|
||||
|
||||
return [
|
||||
|
||||
'visits_webhooks' => [
|
||||
'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
|
||||
'notify_orphan_visits_to_webhooks' =>
|
||||
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
@@ -2,11 +2,26 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager;
|
||||
use Doctrine\Migrations\Configuration\Migration\ConfigurationArray;
|
||||
use Doctrine\Migrations\DependencyFactory;
|
||||
|
||||
// This file is currently used by doctrine migrations only
|
||||
|
||||
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';
|
||||
return ConsoleRunner::createHelperSet($em);
|
||||
|
||||
return DependencyFactory::fromEntityManager(
|
||||
new ConfigurationArray($migrationsConfig),
|
||||
new ExistingEntityManager($em),
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -8,48 +8,38 @@ use Laminas\ConfigAggregator;
|
||||
use Laminas\Diactoros;
|
||||
use Mezzio;
|
||||
use Mezzio\ProblemDetails;
|
||||
use Mezzio\Swoole;
|
||||
use Shlinkio\Shlink\Config\ConfigAggregator\EnvVarLoaderProvider;
|
||||
|
||||
use function class_exists;
|
||||
use function Shlinkio\Shlink\Config\env;
|
||||
use function Shlinkio\Shlink\Config\openswooleIsInstalled;
|
||||
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||
use function Shlinkio\Shlink\Core\enumValues;
|
||||
|
||||
use const PHP_SAPI;
|
||||
|
||||
$isTestEnv = env('APP_ENV') === 'test';
|
||||
$enableSwoole = PHP_SAPI === 'cli' && openswooleIsInstalled() && ! runningInRoadRunner();
|
||||
|
||||
return (new ConfigAggregator\ConfigAggregator([
|
||||
! $isTestEnv
|
||||
? new EnvVarLoaderProvider('config/params/generated_config.php', enumValues(Core\Config\EnvVars::class))
|
||||
: new ConfigAggregator\ArrayProvider([]),
|
||||
Mezzio\ConfigProvider::class,
|
||||
Mezzio\Router\ConfigProvider::class,
|
||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||
$enableSwoole && class_exists(Swoole\ConfigProvider::class)
|
||||
? Swoole\ConfigProvider::class
|
||||
: new ConfigAggregator\ArrayProvider([]),
|
||||
ProblemDetails\ConfigProvider::class,
|
||||
Diactoros\ConfigProvider::class,
|
||||
Common\ConfigProvider::class,
|
||||
Config\ConfigProvider::class,
|
||||
Importer\ConfigProvider::class,
|
||||
IpGeolocation\ConfigProvider::class,
|
||||
EventDispatcher\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
CLI\ConfigProvider::class,
|
||||
Rest\ConfigProvider::class,
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||
$isTestEnv
|
||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
||||
: new ConfigAggregator\ArrayProvider([]),
|
||||
// Routes have to be loaded last
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
|
||||
], 'data/cache/app_config.php', [
|
||||
Core\Config\PostProcessor\BasePathPrefixer::class,
|
||||
Core\Config\PostProcessor\MultiSegmentSlugProcessor::class,
|
||||
Core\Config\PostProcessor\ShortUrlMethodsProcessor::class,
|
||||
]))->getMergedConfig();
|
||||
return (new ConfigAggregator\ConfigAggregator(
|
||||
providers: [
|
||||
Mezzio\ConfigProvider::class,
|
||||
Mezzio\Router\ConfigProvider::class,
|
||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||
ProblemDetails\ConfigProvider::class,
|
||||
Diactoros\ConfigProvider::class,
|
||||
Common\ConfigProvider::class,
|
||||
Config\ConfigProvider::class,
|
||||
Importer\ConfigProvider::class,
|
||||
IpGeolocation\ConfigProvider::class,
|
||||
EventDispatcher\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
CLI\ConfigProvider::class,
|
||||
Rest\ConfigProvider::class,
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/{,*.}global.php'),
|
||||
// Local config should not be loaded during tests, whereas test config should be loaded ONLY during tests
|
||||
new ConfigAggregator\PhpFileProvider(
|
||||
$isTestEnv ? 'config/test/*.global.php' : 'config/autoload/{,*.}local.php',
|
||||
),
|
||||
// Routes have to be loaded last
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
|
||||
],
|
||||
cachedConfigFile: 'data/cache/app_config.php',
|
||||
postProcessors: [
|
||||
Core\Config\PostProcessor\BasePathPrefixer::class,
|
||||
Core\Config\PostProcessor\MultiSegmentSlugProcessor::class,
|
||||
Core\Config\PostProcessor\ShortUrlMethodsProcessor::class,
|
||||
],
|
||||
))->getMergedConfig();
|
||||
|
||||
@@ -9,14 +9,15 @@ use Shlinkio\Shlink\Core\Util\RedirectStatus;
|
||||
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||
const MIN_SHORT_CODES_LENGTH = 4;
|
||||
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302; // Deprecated. Default to 307 for Shlink v4
|
||||
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302;
|
||||
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
|
||||
const LOOSE_URI_MATCHER = '/(.+)\:(.+)/i'; // Matches anything starting with a schema.
|
||||
const DEFAULT_QR_CODE_SIZE = 300;
|
||||
const DEFAULT_QR_CODE_MARGIN = 0;
|
||||
const DEFAULT_QR_CODE_FORMAT = 'png';
|
||||
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
||||
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
||||
const MIN_TASK_WORKERS = 4;
|
||||
const MIGRATIONS_TABLE = 'migrations';
|
||||
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
|
||||
|
||||
@@ -6,14 +6,21 @@ use Laminas\ServiceManager\ServiceManager;
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
use Symfony\Component\Lock;
|
||||
|
||||
use function Shlinkio\Shlink\Config\loadEnvVarsFromConfig;
|
||||
use function Shlinkio\Shlink\Core\enumValues;
|
||||
|
||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
// This is one of the first files loaded. Configure the timezone here
|
||||
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv(date_default_timezone_get()));
|
||||
// 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
|
||||
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||
@@ -21,7 +28,6 @@ if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
||||
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
||||
}
|
||||
|
||||
// Build container
|
||||
return (static function (): ServiceManager {
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$container = new ServiceManager($config['dependencies']);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '2.7'
|
||||
version: '3'
|
||||
|
||||
rpc:
|
||||
listen: tcp://127.0.0.1:6001
|
||||
@@ -14,10 +14,12 @@ http:
|
||||
forbid: ['.php', '.htaccess']
|
||||
pool:
|
||||
num_workers: 1
|
||||
debug: true
|
||||
|
||||
jobs:
|
||||
pool:
|
||||
num_workers: 1
|
||||
debug: true
|
||||
timeout: 300
|
||||
consume: ['shlink']
|
||||
pipelines:
|
||||
@@ -31,19 +33,10 @@ logs:
|
||||
mode: development
|
||||
channels:
|
||||
http:
|
||||
level: debug
|
||||
mode: 'off' # Disable logging as Shlink handles it internally
|
||||
server:
|
||||
level: debug
|
||||
level: info
|
||||
metrics:
|
||||
level: debug
|
||||
|
||||
reload:
|
||||
interval: 1s
|
||||
patterns: ['.php']
|
||||
services:
|
||||
http:
|
||||
dirs: ['../../bin', '../../config', '../../data/migrations', '../../module', '../../vendor']
|
||||
recursive: true
|
||||
jobs:
|
||||
dirs: ['../../bin', '../../config', '../../data/migrations', '../../module', '../../vendor']
|
||||
recursive: true
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '2.7'
|
||||
version: '3'
|
||||
|
||||
rpc:
|
||||
listen: tcp://127.0.0.1:6001
|
||||
@@ -7,18 +7,18 @@ server:
|
||||
command: 'php -dopcache.enable_cli=1 -dopcache.validate_timestamps=0 ../../bin/roadrunner-worker.php'
|
||||
|
||||
http:
|
||||
address: '0.0.0.0:${PORT}'
|
||||
address: '0.0.0.0:${PORT:-8080}'
|
||||
middleware: ['static']
|
||||
static:
|
||||
dir: '../../public'
|
||||
forbid: ['.php', '.htaccess']
|
||||
pool:
|
||||
num_workers: ${WEB_WORKER_NUM}
|
||||
num_workers: ${WEB_WORKER_NUM:-0}
|
||||
|
||||
jobs:
|
||||
timeout: 300 # 5 minutes
|
||||
pool:
|
||||
num_workers: ${TASK_WORKER_NUM}
|
||||
num_workers: ${TASK_WORKER_NUM:-0}
|
||||
consume: ['shlink']
|
||||
pipelines:
|
||||
shlink:
|
||||
@@ -31,6 +31,8 @@ logs:
|
||||
mode: production
|
||||
channels:
|
||||
http:
|
||||
level: info # Log all http requests, set to info to disable
|
||||
mode: 'off' # Disable logging as Shlink handles it internally
|
||||
server:
|
||||
level: debug # Everything written to worker stderr is logged
|
||||
level: info
|
||||
jobs:
|
||||
level: debug
|
||||
|
||||
@@ -7,12 +7,6 @@ namespace Shlinkio\Shlink\TestUtils;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function register_shutdown_function;
|
||||
use function sprintf;
|
||||
|
||||
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = require __DIR__ . '/../container.php';
|
||||
$testHelper = $container->get(Helper\TestHelper::class);
|
||||
@@ -20,19 +14,11 @@ $config = $container->get('config');
|
||||
$em = $container->get(EntityManager::class);
|
||||
$httpClient = $container->get('shlink_test_api_client');
|
||||
|
||||
// Dump code coverage when process shuts down
|
||||
register_shutdown_function(function () use ($httpClient): void {
|
||||
$httpClient->request(
|
||||
'GET',
|
||||
sprintf('http://%s:%s/api-tests/stop-coverage', API_TESTS_HOST, API_TESTS_PORT),
|
||||
);
|
||||
});
|
||||
|
||||
$testHelper->createTestDb(
|
||||
['bin/cli', 'db:create'],
|
||||
['bin/cli', 'db:migrate'],
|
||||
['bin/doctrine', 'orm:schema-tool:drop'],
|
||||
['bin/doctrine', 'dbal:run-sql'],
|
||||
createDbCommand: ['bin/cli', 'db:create'],
|
||||
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||
);
|
||||
ApiTest\ApiTestCase::setApiClient($httpClient);
|
||||
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
||||
|
||||
@@ -23,10 +23,10 @@ if (file_exists($covFile)) {
|
||||
}
|
||||
|
||||
$testHelper->createTestDb(
|
||||
['bin/cli', 'db:create'],
|
||||
['bin/cli', 'db:migrate'],
|
||||
['bin/doctrine', 'orm:schema-tool:drop'],
|
||||
['bin/doctrine', 'dbal:run-sql'],
|
||||
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'] ?? []),
|
||||
|
||||
@@ -9,9 +9,9 @@ use Psr\Container\ContainerInterface;
|
||||
/** @var ContainerInterface $container */
|
||||
$container = require __DIR__ . '/../container.php';
|
||||
$container->get(Helper\TestHelper::class)->createTestDb(
|
||||
['bin/cli', 'db:create'],
|
||||
['bin/cli', 'db:migrate'],
|
||||
['bin/doctrine', 'orm:schema-tool:drop'],
|
||||
['bin/doctrine', 'dbal:run-sql'],
|
||||
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'));
|
||||
|
||||
@@ -6,75 +6,38 @@ namespace Shlinkio\Shlink;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\HtmlResponse;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use League\Event\EventDispatcher;
|
||||
use Mezzio\Router\FastRouteRouter;
|
||||
use Monolog\Level;
|
||||
use PHPUnit\Runner\Version;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||
use SebastianBergmann\CodeCoverage\Filter;
|
||||
use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html;
|
||||
use SebastianBergmann\CodeCoverage\Report\PHP;
|
||||
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||
use Shlinkio\Shlink\TestUtils\ApiTest\CoverageMiddleware;
|
||||
use Shlinkio\Shlink\TestUtils\CliTest\CliCoverageDelegator;
|
||||
use Shlinkio\Shlink\TestUtils\Helper\CoverageHelper;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
use function file_exists;
|
||||
use function Functional\contains;
|
||||
use function Laminas\Stratigility\middleware;
|
||||
use function Shlinkio\Shlink\Config\env;
|
||||
use function sleep;
|
||||
use function sprintf;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||
|
||||
$isApiTest = env('TEST_ENV') === 'api';
|
||||
$isCliTest = env('TEST_ENV') === 'cli';
|
||||
$testEnv = env('TEST_ENV');
|
||||
$isApiTest = $testEnv === 'api';
|
||||
$isCliTest = $testEnv === 'cli';
|
||||
$isE2eTest = $isApiTest || $isCliTest;
|
||||
|
||||
$coverageType = env('GENERATE_COVERAGE');
|
||||
$generateCoverage = contains(['yes', 'pretty'], $coverageType);
|
||||
|
||||
$coverage = null;
|
||||
if ($isE2eTest && $generateCoverage) {
|
||||
$filter = new Filter();
|
||||
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
|
||||
$filter->includeDirectory(__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src');
|
||||
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'api'|'cli' $type
|
||||
*/
|
||||
$exportCoverage = static function (string $type = 'api') use (&$coverage, $coverageType): void {
|
||||
if ($coverage === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$basePath = __DIR__ . '/../../build/coverage-' . $type;
|
||||
$covPath = $basePath . '.cov';
|
||||
|
||||
// Every CLI test runs on its own process and dumps the coverage afterwards.
|
||||
// Try to load it and merge it, so that we end up with the whole coverage at the end.
|
||||
if ($type === 'cli' && file_exists($covPath)) {
|
||||
$coverage->merge(require $covPath);
|
||||
}
|
||||
|
||||
if ($coverageType === 'pretty') {
|
||||
(new Html())->process($coverage, $basePath . '/coverage-html');
|
||||
} else {
|
||||
(new PHP())->process($coverage, $covPath);
|
||||
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
||||
}
|
||||
};
|
||||
$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 {
|
||||
$driver = env('DB_DRIVER', 'sqlite');
|
||||
@@ -89,7 +52,7 @@ $buildDbConnection = static function (): array {
|
||||
'postgres' => [
|
||||
'driver' => 'pdo_pgsql',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
||||
'port' => $isCi ? '5433' : '5432',
|
||||
'port' => $isCi ? '5434' : '5432',
|
||||
'user' => 'postgres',
|
||||
'password' => 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
@@ -121,12 +84,14 @@ $buildTestLoggerConfig = static fn (string $filename) => [
|
||||
'level' => Level::Debug->value,
|
||||
'type' => LoggerType::STREAM->value,
|
||||
'destination' => sprintf('data/log/api-tests/%s', $filename),
|
||||
'add_new_line' => true,
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'debug' => true,
|
||||
ConfigAggregator::ENABLE_CACHE => false,
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
@@ -135,52 +100,27 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
'mezzio-swoole' => [
|
||||
'enable_coroutine' => false,
|
||||
'swoole-http-server' => [
|
||||
'host' => API_TESTS_HOST,
|
||||
'port' => API_TESTS_PORT,
|
||||
'process-name' => 'shlink_test',
|
||||
'options' => [
|
||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
||||
'log_file' => __DIR__ . '/../../data/log/api-tests/output.log',
|
||||
'enable_coroutine' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'routes' => !$isApiTest ? [] : [
|
||||
'routes' => [
|
||||
// This route is used to test that title resolution is skipped if the long URL times out
|
||||
[
|
||||
'name' => 'dump_coverage',
|
||||
'path' => '/api-tests/stop-coverage',
|
||||
'middleware' => middleware(static function () use ($exportCoverage) {
|
||||
// TODO I have tried moving this block to a listener so that it's invoked automatically,
|
||||
// but then the coverage is generated empty ¯\_(ツ)_/¯
|
||||
$exportCoverage();
|
||||
return new EmptyResponse();
|
||||
}),
|
||||
'name' => 'long_url_with_timeout',
|
||||
'path' => '/api-tests/long-url-with-timeout',
|
||||
'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 ? [] : [
|
||||
'capture_code_coverage' => [
|
||||
'middleware' => middleware(static function (
|
||||
ServerRequestInterface $req,
|
||||
RequestHandlerInterface $handler,
|
||||
) use (&$coverage): ResponseInterface {
|
||||
$coverage?->start($req->getHeaderLine('x-coverage-id'));
|
||||
|
||||
try {
|
||||
return $handler->handle($req);
|
||||
} finally {
|
||||
$coverage?->stop();
|
||||
}
|
||||
}),
|
||||
'middleware' => new CoverageMiddleware($coverage),
|
||||
'priority' => 9999,
|
||||
],
|
||||
],
|
||||
|
||||
// Disable mercure integration during E2E tests
|
||||
'mercure' => [
|
||||
'public_hub_url' => null,
|
||||
'internal_hub_url' => null,
|
||||
@@ -199,58 +139,7 @@ return [
|
||||
],
|
||||
'delegators' => $isCliTest ? [
|
||||
Application::class => [
|
||||
static function (
|
||||
ContainerInterface $c,
|
||||
string $serviceName,
|
||||
callable $callback,
|
||||
) use (
|
||||
&$coverage,
|
||||
$exportCoverage,
|
||||
) {
|
||||
/** @var Application $app */
|
||||
$app = $callback();
|
||||
$wrappedEventDispatcher = new EventDispatcher();
|
||||
|
||||
// When the command starts, start collecting coverage
|
||||
$wrappedEventDispatcher->subscribeTo(
|
||||
ConsoleCommandEvent::class,
|
||||
static function () use (&$coverage): void {
|
||||
$id = env('COVERAGE_ID');
|
||||
if ($id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$coverage?->start($id);
|
||||
},
|
||||
);
|
||||
// When the command ends, stop collecting coverage
|
||||
$wrappedEventDispatcher->subscribeTo(
|
||||
ConsoleTerminateEvent::class,
|
||||
static function () use (&$coverage, $exportCoverage): void {
|
||||
$id = env('COVERAGE_ID');
|
||||
if ($id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$coverage?->stop();
|
||||
$exportCoverage('cli');
|
||||
},
|
||||
);
|
||||
|
||||
$app->setDispatcher(new class ($wrappedEventDispatcher) implements EventDispatcherInterface {
|
||||
public function __construct(private EventDispatcher $wrappedDispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
public function dispatch(object $event, ?string $eventName = null): object
|
||||
{
|
||||
$this->wrappedDispatcher->dispatch($event);
|
||||
return $event;
|
||||
}
|
||||
});
|
||||
|
||||
return $app;
|
||||
},
|
||||
new CliCoverageDelegator($coverage),
|
||||
],
|
||||
] : [],
|
||||
],
|
||||
@@ -261,7 +150,7 @@ return [
|
||||
|
||||
'data_fixtures' => [
|
||||
'paths' => [
|
||||
// TODO These are used for CLI tests too, so maybe should be somewhere else
|
||||
// TODO These are used for other module's tests, so maybe should be somewhere else
|
||||
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
||||
],
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -ex
|
||||
|
||||
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
|
||||
ACCEPT_EULA=Y apt-get install msodbcsql17
|
||||
apt-get install unixodbc-dev
|
||||
ACCEPT_EULA=Y apt-get install msodbcsql18
|
||||
# apt-get install unixodbc-dev
|
||||
|
||||
@@ -11,7 +11,7 @@ server {
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
|
||||
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/var/log/shlink/shlink_openswoole.log {
|
||||
su root root
|
||||
daily
|
||||
missingok
|
||||
rotate 120
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 0640 root root
|
||||
postrotate
|
||||
/etc/init.d/shlink_openswoole restart
|
||||
endscript
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: shlink_openswoole
|
||||
# Required-Start: $local_fs $network $named $time $syslog
|
||||
# Required-Stop: $local_fs $network $named $time $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: Shlink non-blocking server with openswoole
|
||||
### END INIT INFO
|
||||
|
||||
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
|
||||
RUNAS=root
|
||||
|
||||
PIDFILE=/var/run/shlink_openswoole.pid
|
||||
LOGDIR=/var/log/shlink
|
||||
LOGFILE=${LOGDIR}/shlink_openswoole.log
|
||||
|
||||
start() {
|
||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with openswoole already running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Starting shlink with openswoole' >&2
|
||||
mkdir -p "$LOGDIR"
|
||||
touch "$LOGFILE"
|
||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||
su -c "$CMD" $RUNAS > "$PIDFILE"
|
||||
echo 'Shlink started' >&2
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with openswoole not running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Stopping shlink with openswoole' >&2
|
||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||
echo 'Shlink stopped' >&2
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM php:8.2-fpm-alpine3.17
|
||||
FROM php:8.3-fpm-alpine3.19
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.21
|
||||
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||
ENV APCU_VERSION 5.1.23
|
||||
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
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
display_errors=On
|
||||
error_reporting=-1
|
||||
memory_limit=-1
|
||||
log_errors_max_len=0
|
||||
zend.assertions=1
|
||||
assert.exception=1
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM php:8.2-alpine3.17
|
||||
FROM php:8.3-alpine3.19
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.21
|
||||
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||
ENV APCU_VERSION 5.1.23
|
||||
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
|
||||
|
||||
@@ -71,6 +71,6 @@ CMD \
|
||||
# Install dependencies if the vendor dir does not exist
|
||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||
# Download roadrunner binary
|
||||
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --location bin/ && chmod +x bin/rr ; fi && \
|
||||
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; fi && \
|
||||
# This forces the app to be started every second until the exit code is 0
|
||||
until ./bin/rr serve -c config/roadrunner/.rr.dev.yml; do sleep 1 ; done
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
FROM php:8.2-alpine3.17
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.21
|
||||
ENV INOTIFY_VERSION 3.0.0
|
||||
ENV OPENSWOOLE_VERSION 4.12.1
|
||||
ENV PDO_SQLSRV_VERSION 5.10.1
|
||||
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||
|
||||
RUN apk update
|
||||
|
||||
# Install common php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install calendar
|
||||
|
||||
RUN apk add --no-cache oniguruma-dev
|
||||
RUN docker-php-ext-install mbstring
|
||||
|
||||
RUN apk add --no-cache sqlite-libs
|
||||
RUN apk add --no-cache sqlite-dev
|
||||
RUN docker-php-ext-install pdo_sqlite
|
||||
|
||||
RUN apk add --no-cache icu-dev
|
||||
RUN docker-php-ext-install intl
|
||||
|
||||
RUN apk add --no-cache libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||
docker-php-ext-install sockets && \
|
||||
apk del .phpize-deps
|
||||
RUN docker-php-ext-install bcmath
|
||||
|
||||
# Install APCu extension
|
||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||
&& docker-php-ext-configure apcu \
|
||||
&& docker-php-ext-install apcu \
|
||||
&& rm /tmp/apcu.tar.gz \
|
||||
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||
|
||||
# Install inotify extension
|
||||
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/inotify \
|
||||
&& 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/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||
docker-php-ext-enable openswoole pdo_sqlsrv pcov && \
|
||||
apk del .phpize-deps && \
|
||||
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
# Make home directory writable by anyone
|
||||
RUN chmod 777 /home
|
||||
|
||||
VOLUME /home/shlink
|
||||
WORKDIR /home/shlink
|
||||
|
||||
# Expose openswoole port
|
||||
EXPOSE 8080
|
||||
|
||||
CMD \
|
||||
# Install dependencies if the vendor dir does not exist
|
||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||
# When restarting the container, openswoole might think it is already in execution
|
||||
# This forces the app to be started every second until the exit code is 0
|
||||
until php ./vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||
@@ -1,14 +0,0 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
|
||||
error_log /home/shlink/www/data/infra/nginx/swoole_proxy.error.log;
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass http://shlink_swoole:8080;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function is_subclass_of;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
class Version20160819142757 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$platformClass = $this->connection->getDatabasePlatform();
|
||||
$table = $schema->getTable('short_urls');
|
||||
$column = $table->getColumn('short_code');
|
||||
|
||||
match (true) {
|
||||
is_subclass_of($platformClass, MySQLPlatform::class) => $column
|
||||
->setPlatformOption('charset', 'utf8mb4')
|
||||
->setPlatformOption('collation', 'utf8mb4_bin'),
|
||||
is_subclass_of($platformClass, SqlitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Nothing to roll back
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
class Version20160820191203 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Check if the tables already exist
|
||||
$tables = $schema->getTables();
|
||||
foreach ($tables as $table) {
|
||||
if ($table->getName() === 'tags') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->createTagsTable($schema);
|
||||
$this->createShortUrlsInTagsTable($schema);
|
||||
}
|
||||
|
||||
private function createTagsTable(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('tags');
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('name', Types::STRING, [
|
||||
'length' => 255,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addUniqueIndex(['name']);
|
||||
|
||||
$table->setPrimaryKey(['id']);
|
||||
}
|
||||
|
||||
private function createShortUrlsInTagsTable(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('short_urls_in_tags');
|
||||
$table->addColumn('short_url_id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('tag_id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
|
||||
$table->addForeignKeyConstraint('tags', ['tag_id'], ['id'], [
|
||||
'onDelete' => 'CASCADE',
|
||||
'onUpdate' => 'RESTRICT',
|
||||
]);
|
||||
$table->addForeignKeyConstraint('short_urls', ['short_url_id'], ['id'], [
|
||||
'onDelete' => 'CASCADE',
|
||||
'onUpdate' => 'RESTRICT',
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['short_url_id', 'tag_id']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$schema->dropTable('short_urls_in_tags');
|
||||
$schema->dropTable('tags');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
class Version20171021093246 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if ($shortUrls->hasColumn('valid_since')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->addColumn('valid_since', Types::DATETIME_MUTABLE, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$shortUrls->addColumn('valid_until', Types::DATETIME_MUTABLE, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if (! $shortUrls->hasColumn('valid_since')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->dropColumn('valid_since');
|
||||
$shortUrls->dropColumn('valid_until');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
class Version20171022064541 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if ($shortUrls->hasColumn('max_visits')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->addColumn('max_visits', Types::INTEGER, [
|
||||
'unsigned' => true,
|
||||
'notnull' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if (! $shortUrls->hasColumn('max_visits')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->dropColumn('max_visits');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20180801183328 extends AbstractMigration
|
||||
{
|
||||
private const NEW_SIZE = 255;
|
||||
private const OLD_SIZE = 10;
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->setSize($schema, self::NEW_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->setSize($schema, self::OLD_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function setSize(Schema $schema, int $size): void
|
||||
{
|
||||
$schema->getTable('short_urls')->getColumn('short_code')->setLength($size);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use PDO;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20180913205455 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Nothing to create
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->select('id', 'remote_addr')
|
||||
->from('visits');
|
||||
$st = $this->connection->executeQuery($qb->getSQL());
|
||||
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visits', 'v')
|
||||
->set('v.remote_addr', ':obfuscatedAddr')
|
||||
->where('v.id=:id');
|
||||
|
||||
while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
|
||||
$addr = $row['remote_addr'] ?? null;
|
||||
if ($addr === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$qb->setParameters([
|
||||
'id' => $row['id'],
|
||||
'obfuscatedAddr' => $this->determineAddress((string) $addr),
|
||||
])->execute();
|
||||
}
|
||||
}
|
||||
|
||||
private function determineAddress(string $addr): ?string
|
||||
{
|
||||
if ($addr === IpAddress::LOCALHOST) {
|
||||
return $addr;
|
||||
}
|
||||
|
||||
try {
|
||||
return (string) IpAddress::fromString($addr)->getAnonymizedCopy();
|
||||
} catch (InvalidArgumentException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Nothing to rollback
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class 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() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20181020060559 extends AbstractMigration
|
||||
{
|
||||
private const COLUMNS = [
|
||||
'countryCode' => 'country_code',
|
||||
'countryName' => 'country_name',
|
||||
'regionName' => 'region_name',
|
||||
'cityName' => 'city_name',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->createColumns($schema->getTable('visit_locations'), self::COLUMNS);
|
||||
}
|
||||
|
||||
private function createColumns(Table $visitLocations, array $columnNames): void
|
||||
{
|
||||
foreach ($columnNames as $name) {
|
||||
if (! $visitLocations->hasColumn($name)) {
|
||||
$visitLocations->addColumn($name, Types::STRING, ['notnull' => false]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
// If the camel case columns do not exist, do nothing
|
||||
if (! $visitLocations->hasColumn('countryCode')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations');
|
||||
foreach (self::COLUMNS as $camelCaseName => $snakeCaseName) {
|
||||
$qb->set($snakeCaseName, $camelCaseName);
|
||||
}
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// No down
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20181020065148 extends AbstractMigration
|
||||
{
|
||||
private const CAMEL_CASE_COLUMNS = [
|
||||
'countryCode',
|
||||
'countryName',
|
||||
'regionName',
|
||||
'cityName',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
foreach (self::CAMEL_CASE_COLUMNS as $name) {
|
||||
if ($visitLocations->hasColumn($name)) {
|
||||
$visitLocations->dropColumn($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// No down
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20181110175521 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->getUserAgentColumn($schema)->setLength(512);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->getUserAgentColumn($schema)->setLength(256);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function getUserAgentColumn(Schema $schema): Column
|
||||
{
|
||||
return $schema->getTable('visits')->getColumn('user_agent');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20190824075137 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->getRefererColumn($schema)->setLength(1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->getRefererColumn($schema)->setLength(256);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function getRefererColumn(Schema $schema): Column
|
||||
{
|
||||
return $schema->getTable('visits')->getColumn('referer');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20190930165521 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if ($shortUrls->hasColumn('domain_id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domains = $schema->createTable('domains');
|
||||
$domains->addColumn('id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$domains->addColumn('authority', Types::STRING, [
|
||||
'length' => 512,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$domains->addUniqueIndex(['authority']);
|
||||
$domains->setPrimaryKey(['id']);
|
||||
|
||||
$shortUrls->addColumn('domain_id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => false,
|
||||
]);
|
||||
$shortUrls->addForeignKeyConstraint('domains', ['domain_id'], ['id'], [
|
||||
'onDelete' => 'RESTRICT',
|
||||
'onUpdate' => 'RESTRICT',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$schema->getTable('short_urls')->dropColumn('domain_id');
|
||||
$schema->dropTable('domains');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function array_reduce;
|
||||
|
||||
final class Version20191001201532 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
if ($shortUrls->hasIndex('unique_short_code_plus_domain')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Index|null $shortCodesIndex */
|
||||
$shortCodesIndex = array_reduce($shortUrls->getIndexes(), function (?Index $found, Index $current) {
|
||||
[$column] = $current->getColumns();
|
||||
return $column === 'short_code' ? $current : $found;
|
||||
});
|
||||
if ($shortCodesIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shortUrls->dropIndex($shortCodesIndex->getName());
|
||||
$shortUrls->addUniqueIndex(['short_code', 'domain_id'], 'unique_short_code_plus_domain');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
|
||||
$shortUrls->dropIndex('unique_short_code_plus_domain');
|
||||
$shortUrls->addUniqueIndex(['short_code']);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20191020074522 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->getOriginalUrlColumn($schema)->setLength(2048);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->getOriginalUrlColumn($schema)->setLength(1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function getOriginalUrlColumn(Schema $schema): Column
|
||||
{
|
||||
return $schema->getTable('short_urls')->getColumn('original_url');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function Functional\some;
|
||||
|
||||
final class Version20200105165647 extends AbstractMigration
|
||||
{
|
||||
private const COLUMNS = ['lat' => 'latitude', 'lon' => 'longitude'];
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function preUp(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf(some(
|
||||
self::COLUMNS,
|
||||
fn (string $v, string $newColName) => $visitLocations->hasColumn($newColName),
|
||||
), 'New columns already exist');
|
||||
|
||||
foreach (self::COLUMNS as $columnName) {
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($columnName, ':zeroValue')
|
||||
->where($qb->expr()->orX(
|
||||
$qb->expr()->eq($columnName, ':emptyString'),
|
||||
$qb->expr()->isNull($columnName),
|
||||
))
|
||||
->setParameters([
|
||||
'zeroValue' => '0',
|
||||
'emptyString' => '',
|
||||
])
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
foreach (self::COLUMNS as $newName => $oldName) {
|
||||
$visitLocations->addColumn($newName, Types::FLOAT, [
|
||||
'default' => '0.0',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$isPostgres = $this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform;
|
||||
$castType = $isPostgres ? 'DOUBLE PRECISION' : 'DECIMAL(9,2)';
|
||||
|
||||
foreach (self::COLUMNS as $newName => $oldName) {
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($newName, 'CAST(' . $oldName . ' AS ' . $castType . ')')
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
public function preDown(Schema $schema): void
|
||||
{
|
||||
foreach (self::COLUMNS as $newName => $oldName) {
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($oldName, $newName)
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
foreach (self::COLUMNS as $colName => $oldName) {
|
||||
$visitLocations->dropColumn($colName);
|
||||
}
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function Functional\none;
|
||||
|
||||
final class Version20200106215144 extends AbstractMigration
|
||||
{
|
||||
private const COLUMNS = ['latitude', 'longitude'];
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf(none(
|
||||
self::COLUMNS,
|
||||
fn (string $oldColName) => $visitLocations->hasColumn($oldColName),
|
||||
), 'Old columns do not exist');
|
||||
|
||||
foreach (self::COLUMNS as $colName) {
|
||||
$visitLocations->dropColumn($colName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
|
||||
foreach (self::COLUMNS as $colName) {
|
||||
$visitLocations->addColumn($colName, Types::STRING, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function Functional\each;
|
||||
use function Functional\partial_left;
|
||||
|
||||
final class Version20200110182849 extends AbstractMigration
|
||||
{
|
||||
private const DEFAULT_EMPTY_VALUE = '';
|
||||
private const COLUMN_DEFAULTS_MAP = [
|
||||
'visits' => [
|
||||
'referer',
|
||||
'user_agent',
|
||||
],
|
||||
'visit_locations' => [
|
||||
'timezone',
|
||||
'country_code',
|
||||
'country_name',
|
||||
'region_name',
|
||||
'city_name',
|
||||
],
|
||||
];
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
each(
|
||||
self::COLUMN_DEFAULTS_MAP,
|
||||
fn (array $columns, string $tableName) =>
|
||||
each($columns, partial_left([$this, 'setDefaultValueForColumnInTable'], $tableName)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setDefaultValueForColumnInTable(string $tableName, string $columnName): void
|
||||
{
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update($tableName)
|
||||
->set($columnName, ':emptyValue')
|
||||
->setParameter('emptyValue', self::DEFAULT_EMPTY_VALUE)
|
||||
->where($qb->expr()->isNull($columnName))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// No need (and no way) to undo this migration
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20200323190014 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf($visitLocations->hasColumn('is_empty'));
|
||||
|
||||
$visitLocations->addColumn('is_empty', Types::BOOLEAN, ['default' => false]);
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set('is_empty', ':isEmpty')
|
||||
->where($qb->expr()->eq('country_code', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('country_name', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('region_name', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('city_name', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('timezone', ':emptyString'))
|
||||
->andWhere($qb->expr()->eq('lat', 0))
|
||||
->andWhere($qb->expr()->eq('lon', 0))
|
||||
->setParameter('isEmpty', true)
|
||||
->setParameter('emptyString', '')
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visitLocations = $schema->getTable('visit_locations');
|
||||
$this->skipIf(!$visitLocations->hasColumn('is_empty'));
|
||||
|
||||
$visitLocations->dropColumn('is_empty');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20201023090929 extends AbstractMigration
|
||||
{
|
||||
private const IMPORT_SOURCE_COLUMN = 'import_source';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf($shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN));
|
||||
|
||||
$shortUrls->addColumn(self::IMPORT_SOURCE_COLUMN, Types::STRING, [
|
||||
'length' => 255,
|
||||
'notnull' => false,
|
||||
]);
|
||||
$shortUrls->addColumn('import_original_short_code', Types::STRING, [
|
||||
'length' => 255,
|
||||
'notnull' => false,
|
||||
]);
|
||||
|
||||
$shortUrls->addUniqueIndex(
|
||||
[self::IMPORT_SOURCE_COLUMN, 'import_original_short_code', 'domain_id'],
|
||||
'unique_imports',
|
||||
);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf(! $shortUrls->hasColumn(self::IMPORT_SOURCE_COLUMN));
|
||||
|
||||
$shortUrls->dropColumn(self::IMPORT_SOURCE_COLUMN);
|
||||
$shortUrls->dropColumn('import_original_short_code');
|
||||
$shortUrls->dropIndex('unique_imports');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Doctrine\DBAL\Driver\Result;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20201102113208 extends AbstractMigration
|
||||
{
|
||||
private const API_KEY_COLUMN = 'author_api_key_id';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf($shortUrls->hasColumn(self::API_KEY_COLUMN));
|
||||
|
||||
$shortUrls->addColumn(self::API_KEY_COLUMN, Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => false,
|
||||
]);
|
||||
|
||||
$shortUrls->addForeignKeyConstraint('api_keys', [self::API_KEY_COLUMN], ['id'], [
|
||||
'onDelete' => 'SET NULL',
|
||||
'onUpdate' => 'RESTRICT',
|
||||
], 'FK_' . self::API_KEY_COLUMN);
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
// If there's only one API key and it's active, link all existing URLs with it
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('api_keys')
|
||||
->where($qb->expr()->eq('enabled', ':enabled'))
|
||||
->andWhere($qb->expr()->or(
|
||||
$qb->expr()->isNull('expiration_date'),
|
||||
$qb->expr()->gt('expiration_date', ':expiration'),
|
||||
))
|
||||
->setParameters([
|
||||
'enabled' => true,
|
||||
'expiration' => Chronos::now()->toDateTimeString(),
|
||||
]);
|
||||
|
||||
/** @var Result $result */
|
||||
$result = $qb->execute();
|
||||
$id = $this->resolveOneApiKeyId($result);
|
||||
if ($id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('short_urls')
|
||||
->set(self::API_KEY_COLUMN, ':apiKeyId')
|
||||
->setParameter('apiKeyId', $id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
private function resolveOneApiKeyId(Result $result): string|int|null
|
||||
{
|
||||
$results = [];
|
||||
while ($row = $result->fetchAssociative()) {
|
||||
// As soon as we have to iterate more than once, then we cannot resolve a single API key
|
||||
if (! empty($results)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$results[] = $row['id'] ?? null;
|
||||
}
|
||||
|
||||
return $results[0] ?? null;
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf(! $shortUrls->hasColumn(self::API_KEY_COLUMN));
|
||||
|
||||
$shortUrls->removeForeignKey('FK_' . self::API_KEY_COLUMN);
|
||||
$shortUrls->dropColumn(self::API_KEY_COLUMN);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210102174433 extends AbstractMigration
|
||||
{
|
||||
private const TABLE_NAME = 'api_key_roles';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->skipIf($schema->hasTable(self::TABLE_NAME));
|
||||
|
||||
$table = $schema->createTable(self::TABLE_NAME);
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
|
||||
$table->addColumn('role_name', Types::STRING, [
|
||||
'length' => 255,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('meta', Types::JSON, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
|
||||
$table->addColumn('api_key_id', Types::BIGINT, [
|
||||
'unsigned' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addForeignKeyConstraint('api_keys', ['api_key_id'], ['id'], [
|
||||
'onDelete' => 'CASCADE',
|
||||
'onUpdate' => 'RESTRICT',
|
||||
]);
|
||||
$table->addUniqueIndex(['role_name', 'api_key_id'], 'UQ_role_plus_api_key');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->skipIf(! $schema->hasTable(self::TABLE_NAME));
|
||||
$schema->getTable(self::TABLE_NAME)->dropIndex('UQ_role_plus_api_key');
|
||||
$schema->dropTable(self::TABLE_NAME);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210118153932 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Prev migration used to set the length to 256, which made some set-ups crash
|
||||
// It has been updated to 255, and this migration ensures whoever managed to run the prev one, gets the value
|
||||
// also updated to 255
|
||||
|
||||
$rolesTable = $schema->getTable('api_key_roles');
|
||||
$nameColumn = $rolesTable->getColumn('role_name');
|
||||
$nameColumn->setLength(255);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user