mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 12:13:13 +08:00
Compare commits
985 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb684bd788 | ||
|
|
05acf4eb2a | ||
|
|
56d0383170 | ||
|
|
b31236958b | ||
|
|
3ffa46fb26 | ||
|
|
217003381a | ||
|
|
234190f493 | ||
|
|
209e3e9e14 | ||
|
|
872241f497 | ||
|
|
cb7a66c59b | ||
|
|
924383ccc8 | ||
|
|
65d1301195 | ||
|
|
57c0490d84 | ||
|
|
b927e44107 | ||
|
|
6433a67d52 | ||
|
|
1cc2cfaec7 | ||
|
|
3fa24c5d81 | ||
|
|
a5c96f41b3 | ||
|
|
9fac291df4 | ||
|
|
971b7967de | ||
|
|
b3a4adeba4 | ||
|
|
b732f1df0d | ||
|
|
4395732c5e | ||
|
|
6720d12ab8 | ||
|
|
456765e55b | ||
|
|
a6009c89d3 | ||
|
|
d767c415d1 | ||
|
|
d88f535444 | ||
|
|
0c7dd18b7c | ||
|
|
0e535123ae | ||
|
|
8ce23b80bd | ||
|
|
d96023d063 | ||
|
|
d734d1a3b3 | ||
|
|
095f075ca9 | ||
|
|
ef70e44a17 | ||
|
|
27a6f35534 | ||
|
|
47ea4218d0 | ||
|
|
1fd677df5a | ||
|
|
7c349e42fd | ||
|
|
da88ec6807 | ||
|
|
cb715c0877 | ||
|
|
97a362617d | ||
|
|
24e708b7e1 | ||
|
|
583a684b03 | ||
|
|
fe8465261f | ||
|
|
334cc231dc | ||
|
|
848d574f68 | ||
|
|
8f929c0ee3 | ||
|
|
15bd839940 | ||
|
|
0323e0d17d | ||
|
|
5fa4fa0225 | ||
|
|
986c165815 | ||
|
|
53243d1764 | ||
|
|
4aed8e6b59 | ||
|
|
16653d60ed | ||
|
|
c9be89647c | ||
|
|
406f947096 | ||
|
|
64916dafac | ||
|
|
02ca843944 | ||
|
|
3520ab6b18 | ||
|
|
30314fd532 | ||
|
|
4a3e495be7 | ||
|
|
ccfd993042 | ||
|
|
bfd2f5b7cf | ||
|
|
b7cc460844 | ||
|
|
c17c4c1319 | ||
|
|
5967dd97c5 | ||
|
|
0c26198b55 | ||
|
|
a304cca3b6 | ||
|
|
564b65c8ca | ||
|
|
9de0cf5c03 | ||
|
|
1349079f59 | ||
|
|
38016b3ba3 | ||
|
|
8db9962282 | ||
|
|
dca3fb35c7 | ||
|
|
8484449d66 | ||
|
|
6b8ca3e611 | ||
|
|
73fd348490 | ||
|
|
04389fc8b0 | ||
|
|
b0bb77ca81 | ||
|
|
22598e75e8 | ||
|
|
0f8dd1effb | ||
|
|
2c4a8543db | ||
|
|
7aa246b550 | ||
|
|
1e294fe1bc | ||
|
|
dcfb12f454 | ||
|
|
685ee51e1f | ||
|
|
8407fee96d | ||
|
|
7c881377a9 | ||
|
|
acf2961f9e | ||
|
|
f5faeb8f68 | ||
|
|
8985a6932f | ||
|
|
c04f0af56f | ||
|
|
1341d4fe57 | ||
|
|
bc3fc59b1e | ||
|
|
e04838eaa2 | ||
|
|
5d5d89afb9 | ||
|
|
749671c230 | ||
|
|
e79c41d753 | ||
|
|
a575f2eced | ||
|
|
1aba77c752 | ||
|
|
b68e262eac | ||
|
|
f78fa58cf1 | ||
|
|
3916b06e7c | ||
|
|
7fa1f1c63c | ||
|
|
7ed85e8916 | ||
|
|
94e1e6a7b6 | ||
|
|
3cba3f7a4b | ||
|
|
bfd2ce782c | ||
|
|
f99053d251 | ||
|
|
bdc93a45b5 | ||
|
|
a771743756 | ||
|
|
aff1df32f2 | ||
|
|
3562afc2bd | ||
|
|
ac08ed7cf9 | ||
|
|
9cb316bdfa | ||
|
|
6682b52159 | ||
|
|
f5878a5e7b | ||
|
|
406de16a0d | ||
|
|
a73a59f184 | ||
|
|
cca667cf46 | ||
|
|
e6a63a9b85 | ||
|
|
22630c7656 | ||
|
|
c9ec3b3b42 | ||
|
|
a6727c5382 | ||
|
|
9fe2111d62 | ||
|
|
173bfbd300 | ||
|
|
999beef349 | ||
|
|
c6fdd8a59f | ||
|
|
0ec7e8c41b | ||
|
|
89e4ed5573 | ||
|
|
4c76df91ce | ||
|
|
a1c7e7d5da | ||
|
|
f28540a53e | ||
|
|
e0e522c3f5 | ||
|
|
37e286df48 | ||
|
|
bc99ee6ebe | ||
|
|
7e8126a421 | ||
|
|
af4ee8f7ec | ||
|
|
af40e8de5c | ||
|
|
d086131630 | ||
|
|
bccc177414 | ||
|
|
0dfadcbb4a | ||
|
|
4380b62715 | ||
|
|
91698034e7 | ||
|
|
014eb2a924 | ||
|
|
96357a57d2 | ||
|
|
c7cfdffaf6 | ||
|
|
46a27a9d0a | ||
|
|
35950a6294 | ||
|
|
c104eee2b1 | ||
|
|
f0972c6220 | ||
|
|
42a5145895 | ||
|
|
8d412e7d4c | ||
|
|
f45e34cfcf | ||
|
|
320c8e2d6b | ||
|
|
988de0b96e | ||
|
|
25a785dfa7 | ||
|
|
c993bbd993 | ||
|
|
479760c0ee | ||
|
|
e186237410 | ||
|
|
4084e3f0d8 | ||
|
|
dddf64031f | ||
|
|
8f1477e893 | ||
|
|
4866fe241e | ||
|
|
6613cb5c60 | ||
|
|
0f48dd567f | ||
|
|
b24511b7b5 | ||
|
|
df40199134 | ||
|
|
935562acc9 | ||
|
|
feb67e76f0 | ||
|
|
fdbe93f0fb | ||
|
|
f27058e255 | ||
|
|
6ddbbb4ba0 | ||
|
|
ef32f2c129 | ||
|
|
760bb2db2a | ||
|
|
68f38fd9fe | ||
|
|
5c6829fb62 | ||
|
|
91c48919c6 | ||
|
|
72313800fa | ||
|
|
478d5a16fd | ||
|
|
b8909d8043 | ||
|
|
c2c659b0fe | ||
|
|
20c3bde036 | ||
|
|
e77e37076f | ||
|
|
734fdf83c1 | ||
|
|
2906d42f97 | ||
|
|
0135f205df | ||
|
|
781c6e94a0 | ||
|
|
1d64dc8a26 | ||
|
|
34ff831473 | ||
|
|
3734160cb4 | ||
|
|
21234cacfb | ||
|
|
eb4dc85006 | ||
|
|
249b8a4768 | ||
|
|
1a1868c7f4 | ||
|
|
487659d5b4 | ||
|
|
f46de4d3e1 | ||
|
|
6314315db7 | ||
|
|
a22beeed08 | ||
|
|
840e377245 | ||
|
|
6fa255386b | ||
|
|
f563e777cc | ||
|
|
a63447b12b | ||
|
|
0f81c3ab92 | ||
|
|
425f254453 | ||
|
|
a9d9ec5bf9 | ||
|
|
0c5c752ffe | ||
|
|
4b556cd79f | ||
|
|
3d32a90f8e | ||
|
|
0b4c334163 | ||
|
|
312fc0984b | ||
|
|
30bf1c2641 | ||
|
|
2d1d7357a3 | ||
|
|
c70077c525 | ||
|
|
d2fad0128f | ||
|
|
62133c994f | ||
|
|
091ea974eb | ||
|
|
955ae00036 | ||
|
|
7d4de590e5 | ||
|
|
292937b962 | ||
|
|
08bd4f131c | ||
|
|
38cc83a4ee | ||
|
|
687a1cc9c7 | ||
|
|
1bcd03b150 | ||
|
|
e2abe23895 | ||
|
|
5c5dde48de | ||
|
|
d9f11e190f | ||
|
|
1ab2d7a240 | ||
|
|
580050cb7d | ||
|
|
eab5659163 | ||
|
|
397b350cfc | ||
|
|
c0130c997a | ||
|
|
fd7f1b32dd | ||
|
|
0e286d8261 | ||
|
|
ce7d2d1fb0 | ||
|
|
2175b8a7bb | ||
|
|
6c0893cdf8 | ||
|
|
25927a296d | ||
|
|
ee4db44fe8 | ||
|
|
b8cb38ae5c | ||
|
|
899bfdce2b | ||
|
|
456960e1f0 | ||
|
|
04e03e9b6e | ||
|
|
a7283da016 | ||
|
|
672321abab | ||
|
|
2059b4050b | ||
|
|
171b43c517 | ||
|
|
ccb7c8f8d9 | ||
|
|
abbc66ac07 | ||
|
|
2d18ef5cee | ||
|
|
79c132219b | ||
|
|
04d4d4a8d7 | ||
|
|
a918113ba0 | ||
|
|
810b25ff14 | ||
|
|
c4fd8d5120 | ||
|
|
772494f46f | ||
|
|
594e7da256 | ||
|
|
49668547d7 | ||
|
|
4c46aaead8 | ||
|
|
d61f5faf59 | ||
|
|
5756609531 | ||
|
|
ea1b285d52 | ||
|
|
bc61b55b94 | ||
|
|
48f6a96da8 | ||
|
|
967f1657d2 | ||
|
|
f90a323374 | ||
|
|
d289c62532 | ||
|
|
05695e8cd6 | ||
|
|
d6a7a6ce66 | ||
|
|
05c7672de3 | ||
|
|
ce515767ce | ||
|
|
76d8fd1023 | ||
|
|
558e259b84 | ||
|
|
f467bed24c | ||
|
|
fa753ad6fb | ||
|
|
22d61fead7 | ||
|
|
c4af1471f0 | ||
|
|
87ba7a7179 | ||
|
|
e7c5cf0846 | ||
|
|
1aaedb8d90 | ||
|
|
284de28f76 | ||
|
|
687d8d91a9 | ||
|
|
771087c6c6 | ||
|
|
1fd3e6365e | ||
|
|
28989296eb | ||
|
|
fd8d73af38 | ||
|
|
144a5415da | ||
|
|
d58e24bce5 | ||
|
|
0f86123ccb | ||
|
|
3f65ef998c | ||
|
|
29d49dfbf4 | ||
|
|
701d17f6f2 | ||
|
|
642431c43e | ||
|
|
3c5b47784d | ||
|
|
64d7fe8bbf | ||
|
|
32070b1fa7 | ||
|
|
8b3324e143 | ||
|
|
f40a5a029c | ||
|
|
eac82a602c | ||
|
|
d1312e0934 | ||
|
|
58dbee10c5 | ||
|
|
f8207994dc | ||
|
|
2030401859 | ||
|
|
8966cf9910 | ||
|
|
4eb4df9ca2 | ||
|
|
32861b1c72 | ||
|
|
7248ca2e9b | ||
|
|
a6ec93f883 | ||
|
|
a28c1d17c5 | ||
|
|
fb705b44a4 | ||
|
|
a32bab9fd0 | ||
|
|
6396e7f964 | ||
|
|
c898cef277 | ||
|
|
baeba54b06 | ||
|
|
f5ee5bf7fb | ||
|
|
73605414f9 | ||
|
|
6045c371e1 | ||
|
|
97a9289d5f | ||
|
|
1983fc9b67 | ||
|
|
bb40d84212 | ||
|
|
46a35c553e | ||
|
|
080943e810 | ||
|
|
62fb3863c6 | ||
|
|
2db03a163d | ||
|
|
9e3dd82efe | ||
|
|
9f1989bfef | ||
|
|
c0bdd8fc77 | ||
|
|
8a23c90e46 | ||
|
|
9095e5b057 | ||
|
|
52c18115af | ||
|
|
737137b19f | ||
|
|
7b78bee135 | ||
|
|
accda36a7b | ||
|
|
69dd9eb067 | ||
|
|
a562bc661d | ||
|
|
258f12f684 | ||
|
|
4dc8d77a5a | ||
|
|
7c5825d1bc | ||
|
|
6ba4d8e947 | ||
|
|
3faf6e967f | ||
|
|
a7a5667301 | ||
|
|
d4924897b2 | ||
|
|
f2d39ca55a | ||
|
|
743d052f55 | ||
|
|
17dbab5ee8 | ||
|
|
8cb5e07c7b | ||
|
|
e9972783d2 | ||
|
|
84f6080a38 | ||
|
|
1b5c1e4e52 | ||
|
|
d7e89ebdae | ||
|
|
aa413dab6d | ||
|
|
b876870bd8 | ||
|
|
05e56cc845 | ||
|
|
d6c158ce98 | ||
|
|
1d4ef4e9a4 | ||
|
|
4d2684be52 | ||
|
|
6947805b5c | ||
|
|
d0e0aea0f1 | ||
|
|
b0f250ed8a | ||
|
|
45254606d4 | ||
|
|
03ee46d903 | ||
|
|
c4afc7a923 | ||
|
|
afa2a5b0f0 | ||
|
|
b40057d423 | ||
|
|
282ffef200 | ||
|
|
22b02de405 | ||
|
|
f0330e9ae3 | ||
|
|
0c26490e3f | ||
|
|
cfaecd93e4 | ||
|
|
ccbc6c7a75 | ||
|
|
2fc2ad98aa | ||
|
|
16590b2dbb | ||
|
|
f40349479e | ||
|
|
9f60c8dffe | ||
|
|
5abd9d1a40 | ||
|
|
0ae5a53d86 | ||
|
|
15a70d0157 | ||
|
|
ededb68ef1 | ||
|
|
09add5fbff | ||
|
|
e30f49a791 | ||
|
|
64737b741b | ||
|
|
d4d65bdf37 | ||
|
|
90732a4fad | ||
|
|
c5015f5828 | ||
|
|
aa77c944d8 | ||
|
|
b8faa6714a | ||
|
|
f48f98f4d7 | ||
|
|
79b2a0839f | ||
|
|
6094d17718 | ||
|
|
d2ed7d6417 | ||
|
|
a705ef21a9 | ||
|
|
67e465c479 | ||
|
|
ed3883b52c | ||
|
|
71ea0bcb5e | ||
|
|
dd2cffeee9 | ||
|
|
1ceabf3bc3 | ||
|
|
17fcd637f2 | ||
|
|
d44bc4b182 | ||
|
|
4760406221 | ||
|
|
0aae0d888c | ||
|
|
1bc01057f3 | ||
|
|
c1906606c6 | ||
|
|
1363194909 | ||
|
|
d945e0c31b | ||
|
|
0af7b75af5 | ||
|
|
36a42cb064 | ||
|
|
4db0acc0e7 | ||
|
|
8f4800aa47 | ||
|
|
4745a37549 | ||
|
|
8fc949898b | ||
|
|
d4758b0e91 | ||
|
|
a07e4b17be | ||
|
|
b9dd975bc6 | ||
|
|
9964d3e24b | ||
|
|
58e8c8e182 | ||
|
|
c7339f6cfa | ||
|
|
1aa78f766a | ||
|
|
bf56e6adaf | ||
|
|
e915b7e499 | ||
|
|
de0470d200 | ||
|
|
3d7cf6992e | ||
|
|
06db082e3f | ||
|
|
4a383cecaf | ||
|
|
9a0f9207be | ||
|
|
0e3a0a1eec | ||
|
|
fd6d180eba | ||
|
|
d152e2ef9a | ||
|
|
b530cf4461 | ||
|
|
bbe85cde31 | ||
|
|
2c3cbe7146 | ||
|
|
2358308f4d | ||
|
|
58bff4fa73 | ||
|
|
098f7afc70 | ||
|
|
4070b1e23d | ||
|
|
d9d4c8a70c | ||
|
|
05abe49d8b | ||
|
|
a71245b883 | ||
|
|
057f88a36a | ||
|
|
32fcdd9d94 | ||
|
|
313927827d | ||
|
|
358b2b661e | ||
|
|
3eddacdff8 | ||
|
|
95d4cde649 | ||
|
|
d1d947bf12 | ||
|
|
40815e5b38 | ||
|
|
8fc1d23e03 | ||
|
|
5ec8c229a1 | ||
|
|
2412ec2195 | ||
|
|
bfb96b0ae8 | ||
|
|
f64920e510 | ||
|
|
664dc333ac | ||
|
|
521f6f2b18 | ||
|
|
6986d03c53 | ||
|
|
e6e38e3ca2 | ||
|
|
951d08f914 | ||
|
|
8e1e8ba7de | ||
|
|
877b098b09 | ||
|
|
e046eddda9 | ||
|
|
084b1169d7 | ||
|
|
f7ceeff05a | ||
|
|
e0d41a2b8a | ||
|
|
6b9f9f0f44 | ||
|
|
025135b8c6 | ||
|
|
77d810b735 | ||
|
|
e1222de05b | ||
|
|
459f807e67 | ||
|
|
32df1370a6 | ||
|
|
f18f8c89ec | ||
|
|
787b791651 | ||
|
|
2eca0da852 | ||
|
|
9e49604ce2 | ||
|
|
5f85c61d6a | ||
|
|
cd58855e1f | ||
|
|
13c64b0db0 | ||
|
|
55e021ba20 | ||
|
|
26fd61a3ed | ||
|
|
46482522bb | ||
|
|
98e3e22896 | ||
|
|
15d49e97c0 | ||
|
|
d5e7ce38ac | ||
|
|
162d0560db | ||
|
|
1de05047ca | ||
|
|
2af5de1199 | ||
|
|
e66a724d2b | ||
|
|
9f4c2ac8d7 | ||
|
|
44f0011445 | ||
|
|
545094cddf | ||
|
|
99f45d8853 | ||
|
|
c25b5f9938 | ||
|
|
db1304c11a | ||
|
|
57714b373c | ||
|
|
5be7f839f3 | ||
|
|
aa441eb58b | ||
|
|
e6b6a40fa6 | ||
|
|
f6dde6f4c1 | ||
|
|
36ab475578 | ||
|
|
a74fe62da6 | ||
|
|
1e4de7fec4 | ||
|
|
47117d1fb7 | ||
|
|
cb8ef408a4 | ||
|
|
e5f21a88fa | ||
|
|
0458c4f798 | ||
|
|
75f6160432 | ||
|
|
5337eb48e7 | ||
|
|
86c30ee731 | ||
|
|
d68dc38959 | ||
|
|
0525639329 | ||
|
|
0d9c7282df | ||
|
|
3b95925217 | ||
|
|
fa595f7aa3 | ||
|
|
ff80f32f72 | ||
|
|
e55dbef2fc | ||
|
|
ebf2e459e8 | ||
|
|
1b5081ae21 | ||
|
|
d5736756f7 | ||
|
|
757cf2e193 | ||
|
|
3a75ac0486 | ||
|
|
3c3ef6fa05 | ||
|
|
3282bfd03b | ||
|
|
0813df6b29 | ||
|
|
df74a04085 | ||
|
|
8323b87076 | ||
|
|
48f01921e1 | ||
|
|
ae9d99257e | ||
|
|
0183c8a4b7 | ||
|
|
9a2ca35e6e | ||
|
|
2edb48e314 | ||
|
|
a81fd497d4 | ||
|
|
49cca5cd69 | ||
|
|
f92cff6241 | ||
|
|
1b4343ffc2 | ||
|
|
d5392a5f59 | ||
|
|
a65ce649ac | ||
|
|
d5dc6cea99 | ||
|
|
5ecfe9f0f0 | ||
|
|
0f5fb066d1 | ||
|
|
8e61639598 | ||
|
|
e88468d867 | ||
|
|
bc46e2f509 | ||
|
|
2241279bb6 | ||
|
|
25ffbed756 | ||
|
|
8784843a7a | ||
|
|
a964e2b3c9 | ||
|
|
7f7efd45ab | ||
|
|
af8f5afef8 | ||
|
|
dcfaed437c | ||
|
|
47e2322e33 | ||
|
|
00e7d57245 | ||
|
|
d53a3222d0 | ||
|
|
80fe3a73e2 | ||
|
|
7ab993b764 | ||
|
|
622edd2ed1 | ||
|
|
1f5faee356 | ||
|
|
076b0cf867 | ||
|
|
d4168bebc6 | ||
|
|
13c3629cd6 | ||
|
|
1eff9801e8 | ||
|
|
92858aebd6 | ||
|
|
1910724a98 | ||
|
|
ff8441fa95 | ||
|
|
9d8fb055b1 | ||
|
|
9651b3d692 | ||
|
|
9d10c8627a | ||
|
|
929d7670cb | ||
|
|
5714a8f884 | ||
|
|
159529937d | ||
|
|
394d9ff4d2 | ||
|
|
07165f344f | ||
|
|
4f2146dd9c | ||
|
|
5b9784cd9e | ||
|
|
9d9b61cf14 | ||
|
|
9d7db96e4b | ||
|
|
3d0bca2781 | ||
|
|
ffb54c4f7a | ||
|
|
a01031303f | ||
|
|
7808f6d182 | ||
|
|
a0c3b9412f | ||
|
|
c32e2053c3 | ||
|
|
a33151248d | ||
|
|
038ba3b006 | ||
|
|
f3c92f4110 | ||
|
|
17779dbbc6 | ||
|
|
c2dd5b8c47 | ||
|
|
fcb9121e5a | ||
|
|
0af1004860 | ||
|
|
917f668cf3 | ||
|
|
436499b7c4 | ||
|
|
66af9866f0 | ||
|
|
3703dedad9 | ||
|
|
37502151ef | ||
|
|
3816a10de3 | ||
|
|
bdda6067ab | ||
|
|
0f62af241f | ||
|
|
987919e87a | ||
|
|
0c03a4b7ff | ||
|
|
5d6d13c95f | ||
|
|
563021bdc1 | ||
|
|
2d6d35a398 | ||
|
|
30297ac5ac | ||
|
|
416c56dee2 | ||
|
|
6b968a6843 | ||
|
|
080965e166 | ||
|
|
c7239aaca2 | ||
|
|
110e8cb78d | ||
|
|
ed859767a8 | ||
|
|
d54b823c88 | ||
|
|
0ae44d3331 | ||
|
|
8063e643a3 | ||
|
|
3883ed15c4 | ||
|
|
a79c1f580e | ||
|
|
f4b569c245 | ||
|
|
899771cc2e | ||
|
|
863803b614 | ||
|
|
5be5e0bc60 | ||
|
|
0b8e305533 | ||
|
|
39d79366a3 | ||
|
|
d5b78f2a7e | ||
|
|
b2a63f734a | ||
|
|
82f41de87b | ||
|
|
af4c66d40a | ||
|
|
975260f126 | ||
|
|
bd678b41f7 | ||
|
|
66898b6ddc | ||
|
|
5eee683978 | ||
|
|
e92446df9b | ||
|
|
63a69b05a1 | ||
|
|
c79ca1d13c | ||
|
|
87c4851d7e | ||
|
|
a125c93ca3 | ||
|
|
a8465094c1 | ||
|
|
16f7359ac6 | ||
|
|
f9f4817ee2 | ||
|
|
c7e49f223f | ||
|
|
6e79b4ba7b | ||
|
|
f78a7f12a9 | ||
|
|
b3664597b0 | ||
|
|
8cfb4f61ca | ||
|
|
b0dbb2dae4 | ||
|
|
7c6da4985d | ||
|
|
386b0dfb7b | ||
|
|
1437ff48ce | ||
|
|
63294f20ee | ||
|
|
d8acc3c247 | ||
|
|
52d8ffa212 | ||
|
|
98ad2816e8 | ||
|
|
9d890f4227 | ||
|
|
0932d04907 | ||
|
|
1f78b5c524 | ||
|
|
59f10619ba | ||
|
|
334710e92c | ||
|
|
75b8175824 | ||
|
|
8a74ef2a33 | ||
|
|
d05ac5ce9d | ||
|
|
3100fffa2b | ||
|
|
6bbacb1017 | ||
|
|
4403dc5df9 | ||
|
|
fdc637c23d | ||
|
|
b99d662417 | ||
|
|
eb9a964c66 | ||
|
|
e5ef8d7f8c | ||
|
|
28650aee2b | ||
|
|
a2294704e6 | ||
|
|
e5e1aa2ff4 | ||
|
|
2f5290b9d3 | ||
|
|
ef3c4aadf2 | ||
|
|
c9ce56eea5 | ||
|
|
4fee656f96 | ||
|
|
d2a04259f5 | ||
|
|
e504daa1ba | ||
|
|
8793a67ce9 | ||
|
|
b4ded374e9 | ||
|
|
91d350b12f | ||
|
|
b3e25f28fd | ||
|
|
aca89f9abe | ||
|
|
243075dd78 | ||
|
|
7130425896 | ||
|
|
fe9ab20cbb | ||
|
|
6935b2ebe2 | ||
|
|
3dcc510da1 | ||
|
|
2f26c82fa6 | ||
|
|
9ddb60a882 | ||
|
|
210b08b61f | ||
|
|
42fe4bd5ce | ||
|
|
1b2a0820e5 | ||
|
|
6cf0155417 | ||
|
|
9b8be3e5b8 | ||
|
|
a27b01b895 | ||
|
|
16dd1838aa | ||
|
|
f788d6872f | ||
|
|
d0df007812 | ||
|
|
f60c217fae | ||
|
|
d3fc7d543a | ||
|
|
4d0fc1da07 | ||
|
|
ee2233c6dd | ||
|
|
ea6e0d7c7f | ||
|
|
d9d599eab4 | ||
|
|
d1ba44e1b3 | ||
|
|
dff2ad3740 | ||
|
|
f7e63710e4 | ||
|
|
d3b5cd5c57 | ||
|
|
86ed83d25e | ||
|
|
f96d0fe30a | ||
|
|
be406bd676 | ||
|
|
044278752b | ||
|
|
343d2ab44a | ||
|
|
66992f644e | ||
|
|
cf245524dd | ||
|
|
ad520811a3 | ||
|
|
ee1e1d5688 | ||
|
|
8ef0e7c25b | ||
|
|
c3d555ef3c | ||
|
|
bf8e14708b | ||
|
|
6ea59b1e4d | ||
|
|
cf8b778711 | ||
|
|
1e79969c3b | ||
|
|
5fd34e03fc | ||
|
|
ce9d6642d4 | ||
|
|
ecebdbbfa8 | ||
|
|
6f7ce709ca | ||
|
|
84094a51a2 | ||
|
|
7ba9eb8e2c | ||
|
|
e8a0c5484c | ||
|
|
0521227127 | ||
|
|
fac9455a1e | ||
|
|
3243ade4fd | ||
|
|
da21eb4a5c | ||
|
|
5ec6d538db | ||
|
|
08228d9d98 | ||
|
|
7856d64299 | ||
|
|
057bbae729 | ||
|
|
09b161304c | ||
|
|
a60c45ca4d | ||
|
|
89ed84ce28 | ||
|
|
a6c547c4da | ||
|
|
3e2c5abaa4 | ||
|
|
c202b3e518 | ||
|
|
e15b67b5dc | ||
|
|
7ddc180487 | ||
|
|
f3fbfc3692 | ||
|
|
b289e3bac2 | ||
|
|
4d4aafa6db | ||
|
|
2705070063 | ||
|
|
5e3770c105 | ||
|
|
0f0213aa87 | ||
|
|
0e2ad0dbca | ||
|
|
d275316acd | ||
|
|
0a681f0efa | ||
|
|
b17f96043a | ||
|
|
6f9b727673 | ||
|
|
79427d08d7 | ||
|
|
2ec807ba70 | ||
|
|
ede4525332 | ||
|
|
4dffc9f0c1 | ||
|
|
5de845c258 | ||
|
|
745ff51150 | ||
|
|
88b9f9fc56 | ||
|
|
fdbe076bf2 | ||
|
|
0760550767 | ||
|
|
1b94083188 | ||
|
|
1993d01110 | ||
|
|
37fb7e76d9 | ||
|
|
cc3362837b | ||
|
|
2012cc453c | ||
|
|
ea80b6d48a | ||
|
|
db956a1f40 | ||
|
|
4f3995ea80 | ||
|
|
e024ba5d94 | ||
|
|
af0ff0f65b | ||
|
|
a9094dc0f6 | ||
|
|
aca90ef907 | ||
|
|
ddfccea901 | ||
|
|
6c6cfb4fc3 | ||
|
|
482b792adf | ||
|
|
d3a86f4fae | ||
|
|
6de1d1057b | ||
|
|
d7e68d29de | ||
|
|
b8f24c3584 | ||
|
|
26c455616b | ||
|
|
909ecc2387 | ||
|
|
a9dff56a92 | ||
|
|
ef0dd416f9 | ||
|
|
781ca39938 | ||
|
|
ccb9d5e83a | ||
|
|
91f08c9ead | ||
|
|
433a5a923d | ||
|
|
501a933d2e | ||
|
|
633f3b728f | ||
|
|
be34600494 | ||
|
|
9577a4da4b | ||
|
|
a24688b92a | ||
|
|
91442a3379 | ||
|
|
0bd9f1e19f | ||
|
|
c233e807c2 | ||
|
|
a002c60183 | ||
|
|
c522879c64 | ||
|
|
d04abd1f75 | ||
|
|
c2feffa50c | ||
|
|
e282521040 | ||
|
|
d7b7db670f | ||
|
|
5a500a00d7 | ||
|
|
9fb07f4039 | ||
|
|
cb23d38b38 | ||
|
|
af7c11665c | ||
|
|
7f4678261e | ||
|
|
a1c8c51f70 | ||
|
|
6bbe66e8f1 | ||
|
|
5f0d281255 | ||
|
|
fd468cd4e9 | ||
|
|
1f7a94794d | ||
|
|
0232f68b91 | ||
|
|
070055a8b9 | ||
|
|
a3bbd06fe3 | ||
|
|
68b4cfbae0 | ||
|
|
97a54aef06 | ||
|
|
297c88c334 | ||
|
|
fef5390a62 | ||
|
|
08d18b1dc1 | ||
|
|
16a2349d86 | ||
|
|
18d9815e88 | ||
|
|
c8346bc5f8 | ||
|
|
2d85a207d1 | ||
|
|
0df8f17e7b | ||
|
|
29645e77cf | ||
|
|
ea76092681 | ||
|
|
c12e13dfd7 | ||
|
|
566940349f | ||
|
|
391ef5c323 | ||
|
|
70264be8e7 | ||
|
|
6208f6f0d5 | ||
|
|
c422a14c5c | ||
|
|
fbeb959317 | ||
|
|
e53ffc8d43 | ||
|
|
453ca1728e | ||
|
|
d10583c7c5 | ||
|
|
a9d213990e | ||
|
|
6321cc0f2d | ||
|
|
3e5f0b2451 | ||
|
|
4ad167eb30 | ||
|
|
01a4f9f867 | ||
|
|
b93d65ddc1 | ||
|
|
9ef9da0870 | ||
|
|
ba2053bd3a | ||
|
|
1260da85a7 | ||
|
|
960c7a0835 | ||
|
|
574f407a90 | ||
|
|
abf802093c | ||
|
|
7ca22f8629 | ||
|
|
6300982b07 | ||
|
|
54cb40f6ed | ||
|
|
8256f0c757 | ||
|
|
1009f9e7c6 | ||
|
|
9c8eef12ba | ||
|
|
9260b3ac6b | ||
|
|
ff98e1fb3d | ||
|
|
f3389d3738 | ||
|
|
a138f4153d | ||
|
|
602e11d5e7 | ||
|
|
3cd14153ca | ||
|
|
095d8e73b8 | ||
|
|
b37f303e76 | ||
|
|
c8368c9098 | ||
|
|
8d0bac9478 | ||
|
|
286c24f8c0 | ||
|
|
963d26f59b | ||
|
|
e07c464de8 | ||
|
|
575509c45b | ||
|
|
563e654b99 | ||
|
|
3e268e2012 | ||
|
|
b2d9f2fc01 | ||
|
|
6717102dd2 | ||
|
|
1ba7fc81ac | ||
|
|
caf4fa7fdd | ||
|
|
5c7962966d | ||
|
|
95ec7e0afa | ||
|
|
c37660f763 | ||
|
|
486ea10c3c | ||
|
|
e0f18f8d1f | ||
|
|
a66f116d66 | ||
|
|
dd099dc39c | ||
|
|
c05aeabdee | ||
|
|
23922f6c7b | ||
|
|
69a99949e1 | ||
|
|
d56cde72a3 | ||
|
|
99ffff11c7 | ||
|
|
bb050cc1b6 | ||
|
|
3547889ad5 | ||
|
|
479e694478 | ||
|
|
2368b634e3 | ||
|
|
dcc09975a9 | ||
|
|
102f5c4e12 | ||
|
|
cc688fa3ce | ||
|
|
e7f7cbcaac | ||
|
|
f9c56d7cb1 | ||
|
|
1fe2e6f6bd | ||
|
|
c3cc88f03e | ||
|
|
584e1f5643 | ||
|
|
04c479148a | ||
|
|
c45cb7bacb | ||
|
|
17be221920 | ||
|
|
10da57572f | ||
|
|
52478ca60a | ||
|
|
62b49dcb19 | ||
|
|
a365faef9c | ||
|
|
5d2698e8a1 | ||
|
|
ec4a413a5b | ||
|
|
596d1ee797 | ||
|
|
d117f82bcb | ||
|
|
18abe9d0f9 | ||
|
|
b53e51de33 | ||
|
|
9478c71af1 | ||
|
|
a2c4eebec8 | ||
|
|
2e5a7d76df | ||
|
|
288249d0b8 | ||
|
|
cd47aae902 | ||
|
|
9bd18ee041 | ||
|
|
22c76df8e6 | ||
|
|
6c87436a96 | ||
|
|
734dac9456 | ||
|
|
85ca366893 | ||
|
|
46db736af8 | ||
|
|
7530048fbd | ||
|
|
c3c03a3a3b | ||
|
|
d1018b6da7 | ||
|
|
fe7928ae0e | ||
|
|
f6c39285c9 | ||
|
|
0e2a289f9f | ||
|
|
f7424da16b | ||
|
|
2e10ee66b7 | ||
|
|
7e442671c3 | ||
|
|
ca646ec2b7 | ||
|
|
4df1af5fd8 | ||
|
|
b4548f3401 | ||
|
|
1819481710 | ||
|
|
e59ae654c0 | ||
|
|
a8bf699f2d | ||
|
|
de9d9d8667 | ||
|
|
869865f22a | ||
|
|
29fd313337 | ||
|
|
7781f07352 | ||
|
|
072371d459 | ||
|
|
51bf948458 | ||
|
|
48acded6ed | ||
|
|
6821f5cf97 | ||
|
|
a15b17e08b | ||
|
|
275b17e1e8 | ||
|
|
27b08ff47b | ||
|
|
bf7c760ca9 | ||
|
|
8af9b0ee02 | ||
|
|
b225c03ef1 | ||
|
|
8a12ed6b8c | ||
|
|
c3fd433446 | ||
|
|
0b9753582d | ||
|
|
9ac48bfbc5 | ||
|
|
85146e5676 | ||
|
|
18ae541c93 | ||
|
|
7a665ec26f | ||
|
|
e3cbac38ce | ||
|
|
31594d47b3 | ||
|
|
42f86a4a24 | ||
|
|
850ce152cd | ||
|
|
c22bbecdc5 | ||
|
|
230f2d155b | ||
|
|
47a2c18c7e | ||
|
|
52bb14bd66 | ||
|
|
8b9caf02d2 | ||
|
|
4580d11d32 | ||
|
|
8610a158d4 | ||
|
|
0a6030b35d | ||
|
|
543c0e62d0 | ||
|
|
4c76e17178 | ||
|
|
611a314cdf | ||
|
|
e7b4d24e5d | ||
|
|
cf60440288 | ||
|
|
15896045f3 | ||
|
|
a9f480ca99 | ||
|
|
4bd67d5f98 | ||
|
|
924ba58f73 | ||
|
|
6eef694315 | ||
|
|
fe4a4aef34 | ||
|
|
b13c95cf1a | ||
|
|
c1c325588e | ||
|
|
faad60f79e | ||
|
|
cbb5a02b95 | ||
|
|
7aa42ada54 |
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
config/autoload/*local*
|
||||
data/infra
|
||||
data/cache/*
|
||||
data/log/*
|
||||
data/locks/*
|
||||
data/proxies/*
|
||||
data/migrations_template.txt
|
||||
data/GeoLite2-City.*
|
||||
data/database.sqlite
|
||||
data/shlink-tests.db
|
||||
**/.gitignore
|
||||
CHANGELOG.md
|
||||
composer.lock
|
||||
vendor
|
||||
docs
|
||||
indocker
|
||||
docker-*
|
||||
php*
|
||||
infection.json
|
||||
phpstan.neon
|
||||
**/test*
|
||||
build*
|
||||
.github
|
||||
hooks
|
||||
27
.gitattributes
vendored
Normal file
27
.gitattributes
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/config/test export-ignore
|
||||
/data/infra export-ignore
|
||||
/docs export-ignore
|
||||
/module/CLI/test export-ignore
|
||||
/module/CLI/test-resources export-ignore
|
||||
/module/Core/test export-ignore
|
||||
/module/Core/test-db export-ignore
|
||||
/module/PreviewGenerator/test export-ignore
|
||||
/module/PreviewGenerator/test-db export-ignore
|
||||
/module/Rest/test export-ignore
|
||||
/module/Rest/test-api export-ignore
|
||||
.env.dist export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.phpstorm.meta.php export-ignore
|
||||
.scrutinizer.yml export-ignore
|
||||
.travis.yml export-ignore
|
||||
build.sh export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
docker-compose.override.yml.dist export-ignore
|
||||
docker-compose.yml export-ignore
|
||||
indocker export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
phpunit-api.xml export-ignore
|
||||
phpunit-db.xml export-ignore
|
||||
phpstan.neon
|
||||
6
.github/ISSUE_TEMPLATE.md
vendored
Normal file
6
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--
|
||||
Before opening an issue, just take into account that this is a completely free of charge open source project.
|
||||
I'm always happy to help and provide support, but some understanding will be required.
|
||||
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.
|
||||
Try to be polite, and understand it is impossible for a project to cover all use cases.
|
||||
-->
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,6 +1,14 @@
|
||||
.idea
|
||||
build
|
||||
!hooks/build
|
||||
composer.lock
|
||||
composer.phar
|
||||
vendor/
|
||||
.env
|
||||
data/database.sqlite
|
||||
data/shlink-tests.db
|
||||
data/GeoLite2-City.mmdb
|
||||
data/GeoLite2-City.mmdb.*
|
||||
docs/swagger-ui*
|
||||
docker-compose.override.yml
|
||||
.phpunit.result.cache
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
||||
|
||||
/**
|
||||
* PhpStorm Container Interop code completion
|
||||
@@ -16,4 +17,7 @@ $STATIC_METHOD_TYPES = [
|
||||
ContainerInterface::get('') => [
|
||||
'' == '@',
|
||||
],
|
||||
ServiceLocatorInterface::build('') => [
|
||||
'' == '@',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
tools:
|
||||
external_code_coverage: true
|
||||
external_code_coverage:
|
||||
timeout: 600
|
||||
checks:
|
||||
php:
|
||||
code_rating: true
|
||||
duplication: true
|
||||
php:
|
||||
code_rating: true
|
||||
duplication: true
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
extension="memcached.so"
|
||||
54
.travis.yml
54
.travis.yml
@@ -2,26 +2,58 @@ language: php
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
- /.*/
|
||||
|
||||
php:
|
||||
- 5.6
|
||||
- 7
|
||||
- 7.1
|
||||
- '7.2'
|
||||
- '7.3'
|
||||
- '7.4snapshot'
|
||||
|
||||
before_install: phpenv config-add .travis-php.ini
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: '7.4snapshot'
|
||||
|
||||
before_script:
|
||||
services:
|
||||
- mysql
|
||||
- postgresql
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
- yes | pecl install swoole
|
||||
- phpenv config-rm xdebug.ini || return 0
|
||||
|
||||
install:
|
||||
- composer self-update
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
before_script:
|
||||
- mysql -e 'CREATE DATABASE shlink_test;'
|
||||
- psql -c 'create database shlink_test;' -U postgres
|
||||
- mkdir build
|
||||
- composer check
|
||||
- export DOCKERFILE_CHANGED=$(git diff ${TRAVIS_COMMIT_RANGE:-origin/master} --name-only | grep Dockerfile)
|
||||
|
||||
after_script:
|
||||
script:
|
||||
- composer ci
|
||||
- if [[ ! -z "$DOCKERFILE_CHANGED" && "${TRAVIS_PHP_VERSION}" == "7.2" ]]; then docker build -t shlink-docker-image:temp . ; fi
|
||||
|
||||
after_success:
|
||||
- rm -f build/clover.xml
|
||||
- phpdbg -qrr vendor/bin/phpcov merge build --clover build/clover.xml
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover build/clover.xml
|
||||
|
||||
sudo: false
|
||||
# Before deploying, build dist file for current travis tag
|
||||
before_deploy:
|
||||
- rm -f ocular.phar
|
||||
- ./build.sh ${TRAVIS_TAG#?}
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key:
|
||||
secure: a9dbZchocqeuOViwUeNH54bQR5Sz7rEYXx5b9WPFtnFn9LGKKUaLbA2U91UQ9QKPrcTpsALubUYbw2CnNmvCwzaY+R8lCD3gkU4ohsEnbpnw3deOeixI74sqBHJAuCH9FSaRDGILoBMtUKx2xlzIymFxkIsgIukkGbdkWHDlRWY3oTUUuw1SQ2Xk9KDsbJQtjIc1+G/O6gHaV4qv/R9W8NPmJExKTNDrAZbC1vIUnxqp4UpVo1hst8qPd1at94CndDYM5rG+7imGbdtxTxzamt819qdTO1OfvtctKawNAm7YXZrrWft6c7gI6j6SI4hxd+ZrrPBqbaRFHkZHjnNssO/yn4SaOHFFzccmu0MzvpPCf0qWZwd3sGHVYer1MnR2mHYqU84QPlW3nrHwJjkrpq3+q0JcBY6GsJs+RskHNtkMTKV05Iz6QUI5YZGwTpuXaRm036SmavjGc4IDlMaYCk/NmbB9BKpthJxLdUpczOHpnjXXHziotWD6cfEnbjU3byfD8HY5WrxSjsNT7SKmXN3hRof7bk985ewQVjGT42O3NbnfnqjQQWr/B7/zFTpLR4f526Bkq12CdCyf5lvrbq+POkLVdJ+uFfR7ds248Ue/jBQy6kM1tWmKF9QiwisFlA84eQ4CW3I93Rp97URv+AQa9zmbD0Ve3Udp+g6nF5I=
|
||||
file: "./build/shlink_${TRAVIS_TAG#?}_dist.zip"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
php: '7.2'
|
||||
|
||||
1234
CHANGELOG.md
1234
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
56
Dockerfile
Normal file
56
Dockerfile
Normal file
@@ -0,0 +1,56 @@
|
||||
FROM php:7.3.8-cli-alpine3.10
|
||||
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
||||
|
||||
ARG SHLINK_VERSION=1.18.1
|
||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||
ENV SWOOLE_VERSION 4.3.3
|
||||
ENV COMPOSER_VERSION 1.9.0
|
||||
|
||||
WORKDIR /etc/shlink
|
||||
|
||||
RUN \
|
||||
# Install mysl and calendar
|
||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar && \
|
||||
# Install sqlite
|
||||
apk add --no-cache sqlite-libs sqlite-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||
# Install postgres
|
||||
apk add --no-cache postgresql-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_pgsql && \
|
||||
# [Deprecated] Install intl
|
||||
apk add --no-cache icu-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" intl && \
|
||||
# Install zip and gd
|
||||
apk add --no-cache libzip-dev zlib-dev libpng-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" zip gd
|
||||
|
||||
# Install swoole
|
||||
# First line fixes an error when installing pecl extensions. Found in https://github.com/docker-library/php/issues/233
|
||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
|
||||
pecl install swoole-${SWOOLE_VERSION} && \
|
||||
docker-php-ext-enable swoole && \
|
||||
apk del .phpize-deps
|
||||
|
||||
# Install shlink
|
||||
COPY . .
|
||||
RUN rm -rf ./docker && \
|
||||
wget https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar && \
|
||||
php composer.phar install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction && \
|
||||
php composer.phar clear-cache && \
|
||||
rm composer.*
|
||||
|
||||
# Add shlink to the path to ease running it after container is created
|
||||
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink
|
||||
RUN sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
||||
|
||||
# Expose swoole port
|
||||
EXPOSE 8080
|
||||
|
||||
# Expose params config dir, since the user is expected to provide custom config from there
|
||||
VOLUME /etc/shlink/config/params
|
||||
|
||||
# Copy config specific for the image
|
||||
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
|
||||
COPY docker/config/shlink_in_docker.local.php config/autoload/shlink_in_docker.local.php
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Alejandro Celaya
|
||||
Copyright (c) 2016-2019 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
|
||||
|
||||
296
README.md
296
README.md
@@ -1,9 +1,293 @@
|
||||
# Shlink
|
||||
|
||||
[](https://travis-ci.org/shlinkio/shlink)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://packagist.org/packages/shlinkio/shlink)
|
||||
[](https://packagist.org/packages/shlinkio/shlink)
|
||||
[](https://travis-ci.org/shlinkio/shlink)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://packagist.org/packages/shlinkio/shlink)
|
||||
[](https://github.com/shlinkio/shlink/blob/master/LICENSE)
|
||||
[](https://acel.me/donate)
|
||||
|
||||
A PHP-based URL shortener application with analytics and management
|
||||
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Update to new version](#update-to-new-version)
|
||||
- [Using a docker image](#using-a-docker-image)
|
||||
- [Using shlink](#using-shlink)
|
||||
- [Shlink CLI Help](#shlink-cli-help)
|
||||
|
||||
## Installation
|
||||
|
||||
First make sure the host where you are going to run shlink fulfills these requirements:
|
||||
|
||||
* PHP 7.2 or greater with JSON, APCu, intl, curl, PDO and gd extensions enabled.
|
||||
* MySQL, PostgreSQL or SQLite.
|
||||
* The web server of your choice with PHP integration (Apache or Nginx recommended).
|
||||
|
||||
Then, you will need a built version of the project. There are a few ways to get it.
|
||||
|
||||
* **Using a dist file**
|
||||
|
||||
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
|
||||
|
||||
Just go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink_X.X.X_dist.zip` file you will find there.
|
||||
|
||||
Finally, decompress the file in the location of your choice.
|
||||
|
||||
* **Building from sources**
|
||||
|
||||
If for any reason you want to build the project yourself, follow these steps:
|
||||
|
||||
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
||||
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
|
||||
* Run `./build.sh 1.0.0`, replacing the version with the version number you are going to build (the version number is only used for the generated dist file).
|
||||
|
||||
After that, you will have a `shlink_x.x.x_dist.zip` dist file inside the `build` directory.
|
||||
|
||||
This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by [travis](https://travis-ci.org/shlinkio/shlink), attaching generated dist file to it.
|
||||
|
||||
Despite how you built the project, you are going to need to install it now, by following these steps:
|
||||
|
||||
* If you are going to use MySQL or PostgreSQL, create an empty database with the name of your choice.
|
||||
* Recursively grant write permissions to the `data` directory. Shlink uses it to cache some information.
|
||||
* Setup the application by running the `bin/install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
|
||||
* Expose shlink to the web, either by using a traditional web server + fast CGI approach, or by using a [swoole](https://www.swoole.co.uk/) non-blocking server.
|
||||
|
||||
* **Using a web server:**
|
||||
|
||||
For example, assuming your domain is doma.in and shlink is in the `/path/to/shlink` folder, these would be the basic configurations for Nginx and Apache.
|
||||
|
||||
*Nginx:*
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name doma.in;
|
||||
listen 80;
|
||||
root /path/to/shlink/public;
|
||||
index index.php;
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Apache:*
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName doma.in
|
||||
DocumentRoot "/path/to/shlink/public"
|
||||
|
||||
<Directory "/path/to/shlink/public">
|
||||
Options FollowSymLinks Includes ExecCGI
|
||||
AllowOverride all
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
* **Using swoole:**
|
||||
|
||||
**Important!** Swoole support is still experimental. Use it with care, and report any found issue.
|
||||
|
||||
First you need to install the swoole PHP extension with [pecl](https://pecl.php.net/package/swoole), `pecl install swoole`.
|
||||
|
||||
Once installed, it's actually pretty easy to get shlink up and running with swoole. Just run `./vendor/bin/zend-expressive-swoole start -d` and you will get shlink running on port 8080.
|
||||
|
||||
However, by doing it this way, you are loosing all the access logs, and the service won't be automatically run if the server has to be restarted.
|
||||
|
||||
For that reason, you should create a daemon script, in `/etc/init.d/shlink_swoole`, like this one, replacing `/path/to/shlink` by the path to your shlink installation:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: shlink_swoole
|
||||
# Required-Start: $local_fs $network $named $time $syslog
|
||||
# Required-Stop: $local_fs $network $named $time $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: Shlink non-blocking server with swoole
|
||||
### END INIT INFO
|
||||
|
||||
SCRIPT=/path/to/shlink/vendor/bin/zend-expressive-swoole\ start
|
||||
RUNAS=root
|
||||
|
||||
PIDFILE=/var/run/shlink_swoole.pid
|
||||
LOGDIR=/var/log/shlink
|
||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
||||
|
||||
start() {
|
||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole already running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Starting shlink with swoole' >&2
|
||||
mkdir -p "$LOGDIR"
|
||||
touch "$LOGFILE"
|
||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||
su -c "$CMD" $RUNAS > "$PIDFILE"
|
||||
echo 'Shlink started' >&2
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole not running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Stopping shlink with swoole' >&2
|
||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||
echo 'Shlink stopped' >&2
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
```
|
||||
|
||||
Then run these commands to enable the service and start it:
|
||||
|
||||
* `sudo chmod +x /etc/init.d/shlink_swoole`
|
||||
* `sudo update-rc.d shlink_swoole defaults`
|
||||
* `sudo update-rc.d shlink_swoole enable`
|
||||
* `/etc/init.d/shlink_swoole start`
|
||||
|
||||
Now again, you can access shlink on port 8080, but this time the service will be automatically run at system start-up, and all access logs will be written in `/var/log/shlink/shlink_swoole.log` (you will probably want to [rotate those logs](https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-16-04). You can find an example logrotate config file [here](data/infra/examples/shlink-daemon-logrotate.conf)).
|
||||
|
||||
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with shlink's API.
|
||||
* Finally access to [https://app.shlink.io](https://app.shlink.io) and configure your server to start creating short URLs.
|
||||
|
||||
**Bonus**
|
||||
|
||||
There are a couple of time-consuming tasks that shlink expects you to do manually, or at least it is recommended, since it will improve runtime performance.
|
||||
|
||||
Those tasks can be performed using shlink's CLI, so it should be easy to schedule them to be run in the background (for example, using cron jobs):
|
||||
|
||||
* **For shlink older than 1.18.0 or not using swoole as the web server**: Resolve IP address locations: `/path/to/shlink/bin/cli visit:locate`
|
||||
|
||||
If you don't run this command regularly, the stats will say all visits come from *unknown* locations.
|
||||
|
||||
* Generate website previews: `/path/to/shlink/bin/cli short-url:process-previews`
|
||||
|
||||
Running this will improve the performance of the `doma.in/abc123/preview` URLs, which return a preview of the site.
|
||||
|
||||
> **Important!** Generating previews is considered deprecated and the feature will be removed in Shlink v2.
|
||||
|
||||
* **For shlink older than v1.17.0**: Update IP geolocation database: `/path/to/shlink/bin/cli visit:update-db`
|
||||
|
||||
When shlink is installed it downloads a fresh [GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) db file. Running this command will update this file.
|
||||
|
||||
The file is updated the first Tuesday of every month, so it should be enough running this command the first Wednesday.
|
||||
|
||||
*Any of these commands accept the `-q` flag, which makes it not display any output. This is recommended when configuring the commands as cron jobs.*
|
||||
|
||||
> In future versions, it is planed that, when using **swoole** to serve shlink, some of these tasks are automatically run without blocking the request and also, without having to configure cron jobs. Probably resolving IP locations and generating previews.
|
||||
|
||||
## Update to new version
|
||||
|
||||
When a new Shlink version is available, you don't need to repeat the entire process yourself. Instead, follow these steps:
|
||||
|
||||
1. Rename your existing Shlink directory to something else (ie. `shlink` ---> `shlink-old`).
|
||||
2. Download and extract the new version of Shlink, and set the directories name to that of the old version. (ie. `shlink`).
|
||||
3. Run the `bin/update` script in the new version's directory to migrate your configuration over.
|
||||
4. If you are using shlink with swoole, restart the service by running `/etc/init.d/shlink_swoole restart`.
|
||||
|
||||
The `bin/update` script will ask you for the location from previous shlink version, and use it in order to import the configuration. It will then update the database and generate some assets shlink needs to work.
|
||||
|
||||
Right now, it does not import cached info (like website previews), but it will. For now you will need to regenerate them again.
|
||||
|
||||
**Important!** It is recommended that you don't skip any version when using this process. The update gets better on every version, but older versions might make assumptions.
|
||||
|
||||
## Using a docker image
|
||||
|
||||
Starting with version 1.15.0, an official docker image is provided. You can learn how to use it by reading [the docs](docker/README.md).
|
||||
|
||||
The idea is that you can just generate a container using the image and provide custom config via env vars.
|
||||
|
||||
## Using shlink
|
||||
|
||||
Once shlink is installed, there are two main ways to interact with it:
|
||||
|
||||
* **The command line**. Try running `bin/cli` and see all the [available commands](#shlink-cli-help).
|
||||
|
||||
All of those commands can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
|
||||
|
||||
It is probably a good idea to symlink the CLI entry point (`bin/cli`) to somewhere in your path, so that you can run shlink from any directory.
|
||||
|
||||
* **The REST API**. The complete docs on how to use the API can be found [here](https://shlink.io/api-docs), and a sandbox which also documents every endpoint can be found [here](https://shlink.io/swagger-ui/index.html).
|
||||
|
||||
However, you probably don't want to consume the raw API yourself. That's why a nice [web client](https://github.com/shlinkio/shlink-web-client) is provided that can be directly used from [https://app.shlink.io](https://app.shlink.io), or you can host it yourself too.
|
||||
|
||||
Both the API and CLI allow you to do the same operations, except for API key management, which can be done from the command line interface only.
|
||||
|
||||
### Shlink CLI Help
|
||||
|
||||
```
|
||||
Usage:
|
||||
command [options] [arguments]
|
||||
|
||||
Options:
|
||||
-h, --help Display this help message
|
||||
-q, --quiet Do not output any message
|
||||
-V, --version Display this application version
|
||||
--ansi Force ANSI output
|
||||
--no-ansi Disable ANSI output
|
||||
-n, --no-interaction Do not ask any interactive question
|
||||
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
|
||||
|
||||
Available commands:
|
||||
help Displays help for a command
|
||||
list Lists commands
|
||||
api-key
|
||||
api-key:disable Disables an API key.
|
||||
api-key:generate Generates a new valid API key.
|
||||
api-key:list Lists all the available API keys.
|
||||
config
|
||||
config:generate-charset [DEPRECATED] Generates a character set sample just by shuffling the default one, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". Then it can be set in the SHORTCODE_CHARS environment variable
|
||||
config:generate-secret [DEPRECATED] Generates a random secret string that can be used for JWT token encryption
|
||||
db
|
||||
db:create Creates the database needed for shlink to work. It will do nothing if the database already exists
|
||||
db:migrate Runs database migrations, which will ensure the shlink database is up to date.
|
||||
short-url
|
||||
short-url:delete [short-code:delete] Deletes a short URL
|
||||
short-url:generate [shortcode:generate|short-code:generate] Generates a short URL for provided long URL and returns it
|
||||
short-url:list [shortcode:list|short-code:list] List all short URLs
|
||||
short-url:parse [shortcode:parse|short-code:parse] Returns the long URL behind a short code
|
||||
short-url:process-previews [shortcode:process-previews|short-code:process-previews] [DEPRECATED] Processes and generates the previews for every URL, improving performance for later web requests.
|
||||
short-url:visits [shortcode:visits|short-code:visits] Returns the detailed visits information for provided short code
|
||||
tag
|
||||
tag:create Creates one or more tags.
|
||||
tag:delete Deletes one or more tags.
|
||||
tag:list Lists existing tags.
|
||||
tag:rename Renames one existing tag.
|
||||
visit
|
||||
visit:locate [visit:process] Resolves visits origin locations.
|
||||
visit:update-db [DEPRECATED] Updates the GeoLite2 database file used to geolocate IP addresses
|
||||
```
|
||||
|
||||
> This product includes GeoLite2 data created by MaxMind, available from [https://www.maxmind.com](https://www.maxmind.com)
|
||||
|
||||
9
bin/cli
9
bin/cli
@@ -1,11 +1,10 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
use Interop\Container\ContainerInterface;
|
||||
declare(strict_types=1);
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/../config/container.php';
|
||||
|
||||
/** @var CliApp $app */
|
||||
$app = $container->get(CliApp::class);
|
||||
$app->run();
|
||||
$container->get(CliApp::class)->run();
|
||||
|
||||
18
bin/install
18
bin/install
@@ -1,14 +1,12 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Zend\Config\Writer\PhpArray;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use function chdir;
|
||||
use function dirname;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$app = new Application();
|
||||
$app->add(new InstallCommand(new PhpArray()));
|
||||
$app->setDefaultCommand('shlink:install');
|
||||
$app->run();
|
||||
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$run(false);
|
||||
|
||||
14
bin/test/run-api-tests.sh
Executable file
14
bin/test/run-api-tests.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
export APP_ENV=test
|
||||
|
||||
# Try to stop server just in case it hanged in last execution
|
||||
vendor/bin/zend-expressive-swoole stop
|
||||
|
||||
echo 'Starting server...'
|
||||
vendor/bin/zend-expressive-swoole start -d
|
||||
sleep 2
|
||||
|
||||
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always
|
||||
vendor/bin/zend-expressive-swoole stop
|
||||
18
bin/update
18
bin/update
@@ -1,14 +1,12 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
use Shlinkio\Shlink\CLI\Command\Install\UpdateCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Zend\Config\Writer\PhpArray;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use function chdir;
|
||||
use function dirname;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$app = new Application();
|
||||
$app->add(new UpdateCommand(new PhpArray()));
|
||||
$app->setDefaultCommand('shlink:install');
|
||||
$app->run();
|
||||
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$run(true);
|
||||
|
||||
Binary file not shown.
41
build.sh
41
build.sh
@@ -1,44 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
if [[ "$#" -ne 1 ]]; then
|
||||
echo "Usage:" >&2
|
||||
echo " $0 {version}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$1
|
||||
builtcontent=$(readlink -f '../shlink_build_tmp')
|
||||
builtcontent="./build/shlink_${version}_dist"
|
||||
projectdir=$(pwd)
|
||||
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
||||
|
||||
# Copy project content to temp dir
|
||||
echo 'Copying project files...'
|
||||
rm -rf "${builtcontent}"
|
||||
mkdir "${builtcontent}"
|
||||
cp -R "${projectdir}"/* "${builtcontent}"
|
||||
mkdir -p "${builtcontent}"
|
||||
rsync -av * "${builtcontent}" \
|
||||
--exclude=*docker* \
|
||||
--exclude=Dockerfile \
|
||||
--exclude-from=./.dockerignore
|
||||
cd "${builtcontent}"
|
||||
|
||||
# Install dependencies
|
||||
rm -r vendor
|
||||
rm composer.lock
|
||||
composer self-update
|
||||
composer install --no-dev --optimize-autoloader
|
||||
echo "Installing dependencies with $composerBin..."
|
||||
${composerBin} self-update
|
||||
${composerBin} install --no-dev --optimize-autoloader --no-progress --no-interaction
|
||||
|
||||
# Delete development files
|
||||
echo 'Deleting dev files...'
|
||||
rm build.sh
|
||||
rm CHANGELOG.md
|
||||
rm composer.*
|
||||
rm LICENSE
|
||||
rm php*
|
||||
rm README.md
|
||||
rm -r build
|
||||
rm -f data/database.sqlite
|
||||
rm -rf data/{cache,log,proxies}/{*,.gitignore}
|
||||
rm -rf config/params/{*,.gitignore}
|
||||
rm -rf config/autoload/{{,*.}local.php{,.dist},.gitignore}
|
||||
|
||||
# Update shlink version in config
|
||||
sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
||||
|
||||
# Compressing file
|
||||
rm -f "${projectdir}"/build/shlink_${version}_dist.zip
|
||||
zip -r "${projectdir}"/build/shlink_${version}_dist.zip .
|
||||
echo 'Compressing files...'
|
||||
cd "${projectdir}"/build
|
||||
rm -f ./shlink_${version}_dist.zip
|
||||
zip -ry ./shlink_${version}_dist.zip ./shlink_${version}_dist
|
||||
cd "${projectdir}"
|
||||
rm -rf "${builtcontent}"
|
||||
|
||||
echo 'Done!'
|
||||
|
||||
171
composer.json
171
composer.json
@@ -1,77 +1,168 @@
|
||||
{
|
||||
"name": "shlinkio/shlink",
|
||||
"type": "project",
|
||||
"homepage": "http://shlink.io",
|
||||
"homepage": "https://shlink.io",
|
||||
"description": "A self-hosted and PHP-based URL shortener application with CLI and REST interfaces",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alejandro Celaya Alastrué",
|
||||
"homepage": "http://www.alejandrocelaya.com",
|
||||
"homepage": "https://www.alejandrocelaya.com",
|
||||
"email": "alejandro@alejandrocelaya.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0",
|
||||
"zendframework/zend-expressive": "^1.0",
|
||||
"zendframework/zend-expressive-fastroute": "^1.1",
|
||||
"zendframework/zend-expressive-twigrenderer": "^1.0",
|
||||
"zendframework/zend-stdlib": "^2.7",
|
||||
"zendframework/zend-servicemanager": "^3.0",
|
||||
"zendframework/zend-paginator": "^2.6",
|
||||
"zendframework/zend-config": "^2.6",
|
||||
"zendframework/zend-i18n": "^2.7",
|
||||
"mtymek/expressive-config-manager": "^0.4",
|
||||
"acelaya/zsm-annotated-services": "^0.2.0",
|
||||
"acelaya/ze-content-based-error-handler": "^1.0",
|
||||
"php": "^7.2",
|
||||
"ext-json": "*",
|
||||
"ext-pdo": "*",
|
||||
"acelaya/ze-content-based-error-handler": "^3.0",
|
||||
"akrabat/ip-address-middleware": "^1.0",
|
||||
"cakephp/chronos": "^1.2",
|
||||
"cocur/slugify": "^3.0",
|
||||
"doctrine/cache": "^1.6",
|
||||
"doctrine/dbal": "^2.9",
|
||||
"doctrine/migrations": "^2.0",
|
||||
"doctrine/orm": "^2.5",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"symfony/console": "^3.0",
|
||||
"symfony/process": "^3.0",
|
||||
"symfony/filesystem": "^3.0",
|
||||
"endroid/qr-code": "^1.7",
|
||||
"firebase/php-jwt": "^4.0",
|
||||
"monolog/monolog": "^1.21",
|
||||
"theorchard/monolog-cascade": "^0.4",
|
||||
"endroid/qrcode": "^1.7",
|
||||
"geoip2/geoip2": "^2.9",
|
||||
"guzzlehttp/guzzle": "^6.3",
|
||||
"lstrojny/functional-php": "^1.9",
|
||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||
"doctrine/migrations": "^1.4"
|
||||
"monolog/monolog": "^1.24",
|
||||
"ocramius/proxy-manager": "~2.2.2",
|
||||
"phly/phly-event-dispatcher": "^1.0",
|
||||
"predis/predis": "^1.1",
|
||||
"shlinkio/shlink-common": "^1.0",
|
||||
"shlinkio/shlink-event-dispatcher": "^1.0",
|
||||
"shlinkio/shlink-installer": "^1.2.1",
|
||||
"shlinkio/shlink-ip-geolocation": "^1.0",
|
||||
"symfony/console": "^4.3",
|
||||
"symfony/filesystem": "^4.3",
|
||||
"symfony/lock": "^4.3",
|
||||
"symfony/process": "^4.3",
|
||||
"theorchard/monolog-cascade": "^0.5",
|
||||
"zendframework/zend-config": "^3.3",
|
||||
"zendframework/zend-config-aggregator": "^1.1",
|
||||
"zendframework/zend-diactoros": "^2.1.3",
|
||||
"zendframework/zend-expressive": "^3.2",
|
||||
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||
"zendframework/zend-expressive-helpers": "^5.3",
|
||||
"zendframework/zend-expressive-platesrenderer": "^2.1",
|
||||
"zendframework/zend-expressive-swoole": "^2.4",
|
||||
"zendframework/zend-i18n": "^2.9",
|
||||
"zendframework/zend-inputfilter": "^2.10",
|
||||
"zendframework/zend-paginator": "^2.8",
|
||||
"zendframework/zend-servicemanager": "^3.4",
|
||||
"zendframework/zend-stdlib": "^3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0",
|
||||
"squizlabs/php_codesniffer": "^2.3",
|
||||
"devster/ubench": "^2.0",
|
||||
"eaglewu/swoole-ide-helper": "dev-master",
|
||||
"filp/whoops": "^2.4",
|
||||
"infection/infection": "^0.13.4",
|
||||
"phpstan/phpstan": "^0.11.2",
|
||||
"phpunit/phpcov": "^6.0",
|
||||
"phpunit/phpunit": "^8.3",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"filp/whoops": "^2.0",
|
||||
"symfony/var-dumper": "^3.0",
|
||||
"vlucas/phpdotenv": "^2.2"
|
||||
"shlinkio/php-coding-standard": "~1.2.2",
|
||||
"shlinkio/shlink-test-utils": "^1.0",
|
||||
"symfony/dotenv": "^4.3",
|
||||
"symfony/var-dumper": "^4.3",
|
||||
"zendframework/zend-component-installer": "^2.1",
|
||||
"zendframework/zend-expressive-tooling": "^1.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src",
|
||||
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
|
||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
|
||||
"Shlinkio\\Shlink\\Common\\": "module/Common/src"
|
||||
},
|
||||
"files": [
|
||||
"module/Common/functions/functions.php"
|
||||
]
|
||||
"Shlinkio\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
||||
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
||||
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
||||
"ShlinkioTest\\Shlink\\Common\\": "module/Common/test"
|
||||
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
|
||||
"ShlinkioTest\\Shlink\\Core\\": [
|
||||
"module/Core/test",
|
||||
"module/Core/test-db"
|
||||
],
|
||||
"ShlinkioTest\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/test"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": [
|
||||
"ci": [
|
||||
"@cs",
|
||||
"@test"
|
||||
"@stan",
|
||||
"@test:ci",
|
||||
"@infect:ci"
|
||||
],
|
||||
|
||||
"cs": "phpcs",
|
||||
"cs-fix": "phpcbf",
|
||||
"serve": "php -S 0.0.0.0:8000 -t public/",
|
||||
"test": "phpunit --coverage-clover build/clover.xml",
|
||||
"pretty-test": "phpunit --coverage-html build/coverage"
|
||||
"cs:fix": "phpcbf",
|
||||
"stan": "phpstan analyse module/*/src/ module/*/config config docker/config --level=5 -c phpstan.neon",
|
||||
|
||||
"test": [
|
||||
"@test:unit",
|
||||
"@test:db",
|
||||
"@test:api"
|
||||
],
|
||||
"test:ci": [
|
||||
"@test:unit:ci",
|
||||
"@test:db",
|
||||
"@test:api"
|
||||
],
|
||||
"test:unit": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
|
||||
"test:unit:ci": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --coverage-clover=build/clover.xml --coverage-xml=build/coverage-xml --log-junit=build/phpunit.junit.xml --testdox",
|
||||
"test:db": [
|
||||
"@test:db:sqlite",
|
||||
"@test:db:mysql",
|
||||
"@test:db:postgres"
|
||||
],
|
||||
"test:db:sqlite": "APP_ENV=test phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always -c phpunit-db.xml --coverage-php build/coverage-db.cov --testdox",
|
||||
"test:db:mysql": "DB_DRIVER=mysql composer test:db:sqlite",
|
||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
||||
"test:api": "bin/test/run-api-tests.sh",
|
||||
|
||||
"test:pretty": [
|
||||
"@test",
|
||||
"phpdbg -qrr vendor/bin/phpcov merge build --html build/html"
|
||||
],
|
||||
"test:unit:pretty": "phpdbg -qrr vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage",
|
||||
|
||||
"infect": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered",
|
||||
"infect:ci": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --coverage=build",
|
||||
"infect:show": "infection --threads=4 --min-msi=75 --log-verbosity=default --only-covered --show-mutations",
|
||||
"infect:test": [
|
||||
"@test:unit:ci",
|
||||
"@infect:ci"
|
||||
]
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"check": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test\" and \"infect\"</>",
|
||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect: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</>",
|
||||
"test": "<fg=blue;options=bold>Runs all test suites</>",
|
||||
"test:ci": "<fg=blue;options=bold>Runs all test suites, generating all needed reports and logs for CI envs</>",
|
||||
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
|
||||
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
|
||||
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL and PostgreSQL</>",
|
||||
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
||||
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
||||
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
||||
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
||||
"test:pretty": "<fg=blue;options=bold>Runs all test suites and generates an HTML code coverage report</>",
|
||||
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||
"infect": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>",
|
||||
"infect:ci": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:show": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing and shows applied mutators</>",
|
||||
"infect:test": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing</>"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
'app_options' => [
|
||||
'name' => 'Shlink',
|
||||
'version' => '1.2.0',
|
||||
'secret_key' => env('SECRET_KEY'),
|
||||
'version' => '%SHLINK_VERSION%',
|
||||
'secret_key' => env('SECRET_KEY', ''),
|
||||
'disable_track_param' => null,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
11
config/autoload/common.global.php
Normal file
11
config/autoload/common.global.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\ConfigAggregator\ConfigAggregator;
|
||||
|
||||
return [
|
||||
|
||||
'debug' => false,
|
||||
ConfigAggregator::ENABLE_CACHE => true,
|
||||
|
||||
];
|
||||
11
config/autoload/common.local.php.dist
Normal file
11
config/autoload/common.local.php.dist
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\ConfigAggregator\ConfigAggregator;
|
||||
|
||||
return [
|
||||
|
||||
'debug' => true,
|
||||
ConfigAggregator::ENABLE_CACHE => false,
|
||||
|
||||
];
|
||||
13
config/autoload/delete_short_urls.global.php
Normal file
13
config/autoload/delete_short_urls.global.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
return [
|
||||
|
||||
'delete_short_urls' => [
|
||||
'visits_threshold' => 15,
|
||||
'check_visits_threshold' => true,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,21 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\Expressive;
|
||||
use Zend\Expressive\Container;
|
||||
use Zend\Expressive\Router;
|
||||
use Zend\Expressive\Template;
|
||||
use Zend\Expressive\Twig;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Expressive\Application::class => Container\ApplicationFactory::class,
|
||||
Router\FastRouteRouter::class => InvokableFactory::class,
|
||||
Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class,
|
||||
'delegators' => [
|
||||
Expressive\Application::class => [
|
||||
Container\ApplicationConfigInjectionDelegator::class,
|
||||
],
|
||||
],
|
||||
'aliases' => [
|
||||
Router\RouterInterface::class => Router\FastRouteRouter::class,
|
||||
|
||||
'lazy_services' => [
|
||||
'proxies_target_dir' => 'data/proxies',
|
||||
'proxies_namespace' => 'ShlinkProxy',
|
||||
'write_proxy_files' => true,
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
23
config/autoload/dependencies.local.php.dist
Normal file
23
config/autoload/dependencies.local.php.dist
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'lazy_services' => [
|
||||
'write_proxy_files' => false,
|
||||
],
|
||||
|
||||
'initializers' => [
|
||||
function (ContainerInterface $container, $instance) {
|
||||
if ($instance instanceof Log\LoggerAwareInterface) {
|
||||
$instance->setLogger($container->get(Log\LoggerInterface::class));
|
||||
}
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,4 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
@@ -6,14 +10,10 @@ return [
|
||||
'proxies_dir' => 'data/proxies',
|
||||
],
|
||||
'connection' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
14
config/autoload/entity-manager.local.php.dist
Normal file
14
config/autoload/entity-manager.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
'connection' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => 'shlink_db',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
use Acelaya\ExpressiveErrorHandler\ErrorHandler\ContentBasedErrorHandler;
|
||||
use Zend\Expressive\Container\WhoopsErrorHandlerFactory;
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\Expressive\Container\WhoopsErrorResponseGeneratorFactory;
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
@@ -21,7 +22,7 @@ return [
|
||||
'error_handler' => [
|
||||
'plugins' => [
|
||||
'factories' => [
|
||||
ContentBasedErrorHandler::DEFAULT_CONTENT => WhoopsErrorHandlerFactory::class,
|
||||
'text/html' => WhoopsErrorResponseGeneratorFactory::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
12
config/autoload/geolite2.global.php
Normal file
12
config/autoload/geolite2.global.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'geolite2' => [
|
||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||
'temp_dir' => sys_get_temp_dir(),
|
||||
'download_from' => 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz',
|
||||
],
|
||||
|
||||
];
|
||||
48
config/autoload/installer.global.php
Normal file
48
config/autoload/installer.global.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
|
||||
return [
|
||||
|
||||
'installer_plugins_expected_config' => [
|
||||
Plugin\LanguageConfigCustomizer::class => [
|
||||
Plugin\LanguageConfigCustomizer::DEFAULT_LANG,
|
||||
],
|
||||
|
||||
Plugin\UrlShortenerConfigCustomizer::class => [
|
||||
Plugin\UrlShortenerConfigCustomizer::SCHEMA,
|
||||
Plugin\UrlShortenerConfigCustomizer::HOSTNAME,
|
||||
Plugin\UrlShortenerConfigCustomizer::CHARS,
|
||||
Plugin\UrlShortenerConfigCustomizer::VALIDATE_URL,
|
||||
Plugin\UrlShortenerConfigCustomizer::ENABLE_NOT_FOUND_REDIRECTION,
|
||||
Plugin\UrlShortenerConfigCustomizer::NOT_FOUND_REDIRECT_TO,
|
||||
],
|
||||
|
||||
Plugin\ApplicationConfigCustomizer::class => [
|
||||
Plugin\ApplicationConfigCustomizer::SECRET,
|
||||
Plugin\ApplicationConfigCustomizer::DISABLE_TRACK_PARAM,
|
||||
Plugin\ApplicationConfigCustomizer::CHECK_VISITS_THRESHOLD,
|
||||
Plugin\ApplicationConfigCustomizer::VISITS_THRESHOLD,
|
||||
],
|
||||
|
||||
Plugin\DatabaseConfigCustomizer::class => [
|
||||
Plugin\DatabaseConfigCustomizer::DRIVER,
|
||||
Plugin\DatabaseConfigCustomizer::NAME,
|
||||
Plugin\DatabaseConfigCustomizer::USER,
|
||||
Plugin\DatabaseConfigCustomizer::PASSWORD,
|
||||
Plugin\DatabaseConfigCustomizer::HOST,
|
||||
Plugin\DatabaseConfigCustomizer::PORT,
|
||||
],
|
||||
],
|
||||
|
||||
'installation_commands' => [
|
||||
'db_create_schema' => [
|
||||
'command' => 'bin/cli db:create',
|
||||
],
|
||||
'db_migrate' => [
|
||||
'command' => 'bin/cli db:migrate',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
19
config/autoload/ip-address.global.php
Normal file
19
config/autoload/ip-address.global.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'ip_address_resolution' => [
|
||||
'headers_to_inspect' => [
|
||||
'CF-Connecting-IP',
|
||||
'True-Client-IP',
|
||||
'X-Real-IP',
|
||||
'Forwarded',
|
||||
'X-Forwarded-For',
|
||||
'X-Forwarded',
|
||||
'X-Cluster-Client-Ip',
|
||||
'Client-Ip',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'debug' => true,
|
||||
'config_cache_enabled' => false,
|
||||
|
||||
];
|
||||
43
config/autoload/locks.global.php
Normal file
43
config/autoload/locks.global.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
use Shlinkio\Shlink\Common\Lock\RetryLockStoreDelegatorFactory;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||
use Symfony\Component\Lock;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
|
||||
return [
|
||||
|
||||
'locks' => [
|
||||
'locks_dir' => __DIR__ . '/../../data/locks',
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Lock\Store\FlockStore::class => ConfigAbstractFactory::class,
|
||||
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
||||
Lock\Factory::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||
'lock_store' => Lock\Store\FlockStore::class,
|
||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
||||
],
|
||||
'delegators' => [
|
||||
Lock\Store\RedisStore::class => [
|
||||
RetryLockStoreDelegatorFactory::class,
|
||||
],
|
||||
Lock\Factory::class => [
|
||||
LoggerAwareDelegatorFactory::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
||||
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
||||
Lock\Factory::class => ['lock_store'],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,30 +1,72 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Processor;
|
||||
|
||||
use const PHP_EOL;
|
||||
|
||||
return [
|
||||
|
||||
'logger' => [
|
||||
'formatters' => [
|
||||
'dashed' => [
|
||||
'format' => '[%datetime%] %channel%.%level_name% - %message% %context%' . PHP_EOL,
|
||||
'format' => '[%datetime%] %channel%.%level_name% - %message%' . PHP_EOL,
|
||||
'include_stacktraces' => true,
|
||||
],
|
||||
],
|
||||
|
||||
'handlers' => [
|
||||
'rotating_file_handler' => [
|
||||
'shlink_rotating_handler' => [
|
||||
'class' => RotatingFileHandler::class,
|
||||
'level' => Logger::INFO,
|
||||
'filename' => 'data/log/shlink_log.log',
|
||||
'max_files' => 30,
|
||||
'formatter' => 'dashed',
|
||||
],
|
||||
'access_handler' => [
|
||||
'class' => StreamHandler::class,
|
||||
'level' => Logger::INFO,
|
||||
'stream' => 'php://stdout',
|
||||
],
|
||||
],
|
||||
|
||||
'processors' => [
|
||||
'exception_with_new_line' => [
|
||||
'class' => Common\Logger\Processor\ExceptionWithNewLineProcessor::class,
|
||||
],
|
||||
'psr3' => [
|
||||
'class' => Processor\PsrLogMessageProcessor::class,
|
||||
],
|
||||
],
|
||||
|
||||
'loggers' => [
|
||||
'Shlink' => [
|
||||
'handlers' => ['rotating_file_handler'],
|
||||
'handlers' => ['shlink_rotating_handler'],
|
||||
'processors' => ['exception_with_new_line', 'psr3'],
|
||||
],
|
||||
'Access' => [
|
||||
'handlers' => ['access_handler'],
|
||||
'processors' => ['exception_with_new_line', 'psr3'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
'Logger_Shlink' => Common\Logger\LoggerFactory::class,
|
||||
'Logger_Access' => Common\Logger\LoggerFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'swoole-http-server' => [
|
||||
'logger' => [
|
||||
'logger-name' => 'Logger_Access',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
return [
|
||||
$isSwoole = extension_loaded('swoole');
|
||||
|
||||
'logger' => [
|
||||
'handlers' => [
|
||||
'rotating_file_handler' => [
|
||||
'level' => Logger::DEBUG,
|
||||
],
|
||||
// For swoole, send logs to standard output
|
||||
$logger = $isSwoole ? [
|
||||
'handlers' => [
|
||||
'shlink_rotating_handler' => [
|
||||
'level' => Logger::EMERGENCY, // This basically disables regular file logs
|
||||
],
|
||||
'shlink_stdout_handler' => [
|
||||
'class' => StreamHandler::class,
|
||||
'level' => Logger::DEBUG,
|
||||
'stream' => 'php://stdout',
|
||||
'formatter' => 'dashed',
|
||||
],
|
||||
],
|
||||
|
||||
'loggers' => [
|
||||
'Shlink' => [
|
||||
'handlers' => ['shlink_stdout_handler'],
|
||||
],
|
||||
],
|
||||
] : [
|
||||
'handlers' => [
|
||||
'shlink_rotating_handler' => [
|
||||
'level' => Logger::DEBUG,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'logger' => $logger,
|
||||
|
||||
];
|
||||
|
||||
@@ -1,19 +1,56 @@
|
||||
<?php
|
||||
use Zend\Expressive\Container\ApplicationFactory;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Zend\Expressive;
|
||||
use Zend\Stratigility\Middleware\ErrorHandler;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'pre-routing' => [
|
||||
'middleware' => [
|
||||
ErrorHandler::class,
|
||||
Expressive\Helper\ContentLengthMiddleware::class,
|
||||
Common\Middleware\CloseDbConnectionMiddleware::class,
|
||||
],
|
||||
'priority' => 12,
|
||||
],
|
||||
'pre-routing-rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Rest\Middleware\PathVersionMiddleware::class,
|
||||
Rest\Middleware\ShortUrl\ShortCodePathMiddleware::class,
|
||||
],
|
||||
'priority' => 11,
|
||||
],
|
||||
|
||||
'routing' => [
|
||||
'middleware' => [
|
||||
ApplicationFactory::ROUTING_MIDDLEWARE,
|
||||
Expressive\Router\Middleware\RouteMiddleware::class,
|
||||
],
|
||||
'priority' => 10,
|
||||
],
|
||||
|
||||
'rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Rest\Middleware\CrossDomainMiddleware::class,
|
||||
Expressive\Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||
Rest\Middleware\BodyParserMiddleware::class,
|
||||
Rest\Middleware\AuthenticationMiddleware::class,
|
||||
],
|
||||
'priority' => 5,
|
||||
],
|
||||
|
||||
'post-routing' => [
|
||||
'middleware' => [
|
||||
ApplicationFactory::DISPATCH_MIDDLEWARE,
|
||||
Expressive\Router\Middleware\DispatchMiddleware::class,
|
||||
|
||||
// Only if a not found error is triggered, set-up the locale to be used
|
||||
Common\Middleware\LocaleMiddleware::class,
|
||||
Core\Response\NotFoundHandler::class,
|
||||
],
|
||||
'priority' => 1,
|
||||
],
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'phpwkhtmltopdf' => [
|
||||
'images' => [
|
||||
'binary' => 'bin/wkhtmltoimage',
|
||||
'type' => 'jpg',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @deprecated */
|
||||
return [
|
||||
|
||||
'preview_generation' => [
|
||||
|
||||
20
config/autoload/redis.local.php.local
Normal file
20
config/autoload/redis.local.php.local
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'redis' => [
|
||||
'servers' => 'tcp://shlink_redis:6379',
|
||||
// 'servers' => [
|
||||
// 'tcp://shlink_redis:6379',
|
||||
// ],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'aliases' => [
|
||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||
// 'lock_store' => 'redis_lock_store',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
15
config/autoload/router.global.php
Normal file
15
config/autoload/router.global.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\Expressive\Router\FastRouteRouter;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
||||
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
12
config/autoload/router.local.php.dist
Normal file
12
config/autoload/router.local.php.dist
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
use Zend\Expressive\Router\FastRouteRouter;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => false,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
20
config/autoload/swoole.global.php
Normal file
20
config/autoload/swoole.global.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'enable_coroutine' => true,
|
||||
|
||||
'swoole-http-server' => [
|
||||
'host' => '0.0.0.0',
|
||||
'process-name' => 'shlink',
|
||||
|
||||
'options' => [
|
||||
'worker_num' => 16,
|
||||
'task_worker_num' => 16,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
21
config/autoload/swoole.local.php.dist
Normal file
21
config/autoload/swoole.local.php.dist
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Zend\Expressive\Swoole\HotCodeReload\FileWatcher\InotifyFileWatcher;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
return [
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'hot-code-reload' => [
|
||||
'enable' => true,
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
InotifyFileWatcher::class => InvokableFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,9 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'twig' => [
|
||||
'cache_dir' => 'data/cache/twig',
|
||||
'templates' => [
|
||||
'extension' => 'phtml',
|
||||
],
|
||||
|
||||
'plates' => [
|
||||
'extensions' => [
|
||||
// extension service names or instances
|
||||
],
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common;
|
||||
|
||||
return [
|
||||
|
||||
'translator' => [
|
||||
'locale' => env('DEFAULT_LOCALE', 'en'),
|
||||
'locale' => Common\env('DEFAULT_LOCALE', 'en'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
@@ -8,7 +12,12 @@ return [
|
||||
'schema' => env('SHORTENED_URL_SCHEMA', 'http'),
|
||||
'hostname' => env('SHORTENED_URL_HOSTNAME'),
|
||||
],
|
||||
'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortener::DEFAULT_CHARS),
|
||||
'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortenerOptions::DEFAULT_CHARS),
|
||||
'validate_url' => true,
|
||||
'not_found_short_url' => [
|
||||
'enable_redirection' => false,
|
||||
'redirect_to' => null,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
13
config/autoload/wkhtmltopdf.global.php
Normal file
13
config/autoload/wkhtmltopdf.global.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'wkhtmltopdf' => [
|
||||
'images' => [
|
||||
'binary' => __DIR__ . '/../../bin/wkhtmltoimage',
|
||||
'type' => 'jpg',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'debug' => false,
|
||||
'config_cache_enabled' => true,
|
||||
|
||||
];
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
/** @var ContainerInterface|ServiceManager $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
/** @var EntityManager $em */
|
||||
$em = $container->get(EntityManager::class);
|
||||
|
||||
return ConsoleRunner::createHelperSet($em);
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Acelaya\ExpressiveErrorHandler;
|
||||
use Shlinkio\Shlink\CLI;
|
||||
use Shlinkio\Shlink\Common;
|
||||
use Shlinkio\Shlink\Core;
|
||||
use Shlinkio\Shlink\Rest;
|
||||
use Zend\Expressive\ConfigManager;
|
||||
use Zend\ConfigAggregator;
|
||||
use Zend\Expressive;
|
||||
|
||||
/**
|
||||
* Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
|
||||
* then ``local.php`` and finally ``*.local.php``. This way local settings overwrite global settings.
|
||||
*
|
||||
* The configuration can be cached. This can be done by setting ``config_cache_enabled`` to ``true``.
|
||||
*
|
||||
* Obviously, if you use closures in your config you can't cache it.
|
||||
*/
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return (new ConfigManager\ConfigManager([
|
||||
return (new ConfigAggregator\ConfigAggregator([
|
||||
Expressive\ConfigProvider::class,
|
||||
Expressive\Router\ConfigProvider::class,
|
||||
Expressive\Router\FastRouteRouter\ConfigProvider::class,
|
||||
Expressive\Plates\ConfigProvider::class,
|
||||
Expressive\Swoole\ConfigProvider::class,
|
||||
ExpressiveErrorHandler\ConfigProvider::class,
|
||||
Common\ConfigProvider::class,
|
||||
IpGeolocation\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
CLI\ConfigProvider::class,
|
||||
Rest\ConfigProvider::class,
|
||||
new ConfigManager\ZendConfigProvider('config/{autoload/{{,*.}global,{,*.}local},params/generated_config}.php'),
|
||||
], 'data/cache/app_config.php'))->getMergedConfig();
|
||||
EventDispatcher\ConfigProvider::class,
|
||||
PreviewGenerator\ConfigProvider::class,
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||
env('APP_ENV') === 'test'
|
||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
||||
: new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
||||
], 'data/cache/app_config.php', [
|
||||
Core\SimplifiedConfigParser::class,
|
||||
]))->getMergedConfig();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
use Dotenv\Dotenv;
|
||||
declare(strict_types=1);
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
@@ -9,9 +11,9 @@ require 'vendor/autoload.php';
|
||||
// If the Dotenv class exists, load env vars and enable errors
|
||||
if (class_exists(Dotenv::class)) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
$dotenv = new Dotenv(__DIR__ . '/..');
|
||||
$dotenv->load();
|
||||
ini_set('display_errors', '1');
|
||||
$dotenv = new Dotenv();
|
||||
$dotenv->load(__DIR__ . '/../.env');
|
||||
}
|
||||
|
||||
// Build container
|
||||
|
||||
27
config/test/bootstrap_api_tests.php
Normal file
27
config/test/bootstrap_api_tests.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\TestUtils;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function file_exists;
|
||||
use function touch;
|
||||
|
||||
// Create an empty .env file
|
||||
if (! file_exists('.env')) {
|
||||
touch('.env');
|
||||
}
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = require __DIR__ . '/../container.php';
|
||||
$testHelper = $container->get(Helper\TestHelper::class);
|
||||
$config = $container->get('config');
|
||||
$em = $container->get(EntityManager::class);
|
||||
|
||||
$testHelper->createTestDb();
|
||||
ApiTest\ApiTestCase::setApiClient($container->get('shlink_test_api_client'));
|
||||
ApiTest\ApiTestCase::setSeedFixturesCallback(function () use ($testHelper, $em, $config) {
|
||||
$testHelper->seedFixtures($em, $config['data_fixtures'] ?? []);
|
||||
});
|
||||
19
config/test/bootstrap_db_tests.php
Normal file
19
config/test/bootstrap_db_tests.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\TestUtils;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function file_exists;
|
||||
use function touch;
|
||||
|
||||
// Create an empty .env file
|
||||
if (! file_exists('.env')) {
|
||||
touch('.env');
|
||||
}
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = require __DIR__ . '/../container.php';
|
||||
$container->get(Helper\TestHelper::class)->createTestDb();
|
||||
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));
|
||||
101
config/test/test_config.global.php
Normal file
101
config/test/test_config.global.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use PDO;
|
||||
use Zend\ConfigAggregator\ConfigAggregator;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
use function sprintf;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
$swooleTestingHost = '127.0.0.1';
|
||||
$swooleTestingPort = 9999;
|
||||
|
||||
$buildDbConnection = function () {
|
||||
$driver = env('DB_DRIVER', 'sqlite');
|
||||
$isCi = env('TRAVIS', false);
|
||||
|
||||
switch ($driver) {
|
||||
case 'sqlite':
|
||||
return [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
||||
];
|
||||
case 'mysql':
|
||||
return [
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db',
|
||||
'user' => 'root',
|
||||
'password' => $isCi ? '' : 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
];
|
||||
case 'postgres':
|
||||
return [
|
||||
'driver' => 'pdo_pgsql',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
||||
'user' => 'postgres',
|
||||
'password' => $isCi ? '' : 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
|
||||
'debug' => true,
|
||||
ConfigAggregator::ENABLE_CACHE => false,
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => 'http',
|
||||
'hostname' => 'doma.in',
|
||||
],
|
||||
],
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'swoole-http-server' => [
|
||||
'host' => $swooleTestingHost,
|
||||
'port' => $swooleTestingPort,
|
||||
'process-name' => 'shlink_test',
|
||||
'options' => [
|
||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
||||
'worker_num' => 1,
|
||||
'task_worker_num' => 1,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'services' => [
|
||||
'shlink_test_api_client' => new Client([
|
||||
'base_uri' => sprintf('http://%s:%s/', $swooleTestingHost, $swooleTestingPort),
|
||||
'http_errors' => false,
|
||||
]),
|
||||
],
|
||||
'factories' => [
|
||||
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
'entity_manager' => [
|
||||
'connection' => $buildDbConnection(),
|
||||
],
|
||||
|
||||
'data_fixtures' => [
|
||||
'paths' => [
|
||||
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
2
data/infra/database/.gitignore
vendored
Executable file
2
data/infra/database/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
data/infra/database_pg/.gitignore
vendored
Executable file
2
data/infra/database_pg/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
11
data/infra/examples/apache-vhost.conf
Normal file
11
data/infra/examples/apache-vhost.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
<VirtualHost *:80>
|
||||
ServerName doma.in
|
||||
DocumentRoot "/path/to/shlink/public"
|
||||
|
||||
<Directory "/path/to/shlink/public">
|
||||
Options FollowSymLinks Includes ExecCGI
|
||||
AllowOverride all
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
22
data/infra/examples/nginx-vhost.conf
Normal file
22
data/infra/examples/nginx-vhost.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
server {
|
||||
server_name doma.in;
|
||||
listen 80;
|
||||
root /path/to/shlink/public;
|
||||
index index.php;
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
13
data/infra/examples/shlink-daemon-logrotate.conf
Normal file
13
data/infra/examples/shlink-daemon-logrotate.conf
Normal file
@@ -0,0 +1,13 @@
|
||||
/var/log/shlink/shlink_swoole.log {
|
||||
su root root
|
||||
daily
|
||||
missingok
|
||||
rotate 120
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 0640 root root
|
||||
postrotate
|
||||
/etc/init.d/shlink_swoole restart
|
||||
endscript
|
||||
}
|
||||
54
data/infra/examples/shlink-daemon.sh
Normal file
54
data/infra/examples/shlink-daemon.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: shlink_swoole
|
||||
# Required-Start: $local_fs $network $named $time $syslog
|
||||
# Required-Stop: $local_fs $network $named $time $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: Shlink non-blocking server with swoole
|
||||
### END INIT INFO
|
||||
|
||||
SCRIPT=/path/to/shlink/vendor/bin/zend-expressive-swoole\ start
|
||||
RUNAS=root
|
||||
|
||||
PIDFILE=/var/run/shlink_swoole.pid
|
||||
LOGDIR=/var/log/shlink
|
||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
||||
|
||||
start() {
|
||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole already running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Starting shlink with swoole' >&2
|
||||
mkdir -p "$LOGDIR"
|
||||
touch "$LOGFILE"
|
||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||
su -c "$CMD" $RUNAS > "$PIDFILE"
|
||||
echo 'Shlink started' >&2
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||
echo 'Shlink with swoole not running' >&2
|
||||
return 1
|
||||
fi
|
||||
echo 'Stopping shlink with swoole' >&2
|
||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||
echo 'Shlink stopped' >&2
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
2
data/infra/nginx/.gitignore
vendored
Executable file
2
data/infra/nginx/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
72
data/infra/php.Dockerfile
Normal file
72
data/infra/php.Dockerfile
Normal file
@@ -0,0 +1,72 @@
|
||||
FROM php:7.3.1-fpm-alpine3.8
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.16
|
||||
ENV APCU_BC_VERSION 1.0.4
|
||||
ENV XDEBUG_VERSION "2.7.0RC1"
|
||||
|
||||
RUN apk update
|
||||
|
||||
# Install common php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install iconv
|
||||
RUN docker-php-ext-install mbstring
|
||||
RUN docker-php-ext-install calendar
|
||||
|
||||
RUN apk add --no-cache --virtual sqlite-libs
|
||||
RUN apk add --no-cache --virtual sqlite-dev
|
||||
RUN docker-php-ext-install pdo_sqlite
|
||||
|
||||
RUN apk add --no-cache --virtual icu-dev
|
||||
RUN docker-php-ext-install intl
|
||||
|
||||
RUN apk add --no-cache --virtual libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache --virtual libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
# 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
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure apcu\
|
||||
&& docker-php-ext-install apcu
|
||||
# cleanup
|
||||
RUN rm /tmp/apcu.tar.gz
|
||||
|
||||
# Install APCu-BC extension
|
||||
ADD https://pecl.php.net/get/apcu_bc-$APCU_BC_VERSION.tgz /tmp/apcu_bc.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
||||
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure apcu-bc\
|
||||
&& docker-php-ext-install apcu-bc
|
||||
# cleanup
|
||||
RUN rm /tmp/apcu_bc.tar.gz
|
||||
|
||||
# Load APCU.ini before APC.ini
|
||||
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
||||
RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||
|
||||
# Install xdebug
|
||||
ADD https://pecl.php.net/get/xdebug-$XDEBUG_VERSION /tmp/xdebug.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/xdebug\
|
||||
&& tar xf /tmp/xdebug.tar.gz -C /usr/src/php/ext/xdebug --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure xdebug\
|
||||
&& docker-php-ext-install xdebug
|
||||
# cleanup
|
||||
RUN rm /tmp/xdebug.tar.gz
|
||||
|
||||
# Install composer
|
||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
RUN chmod +x composer.phar
|
||||
RUN mv composer.phar /usr/local/bin/composer
|
||||
|
||||
# Make home directory writable by anyone
|
||||
RUN chmod 777 /home
|
||||
1
data/infra/php.ini
Normal file
1
data/infra/php.ini
Normal file
@@ -0,0 +1 @@
|
||||
date.timezone = Europe/Madrid
|
||||
92
data/infra/swoole.Dockerfile
Normal file
92
data/infra/swoole.Dockerfile
Normal file
@@ -0,0 +1,92 @@
|
||||
FROM php:7.3.1-cli-alpine3.8
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.16
|
||||
ENV APCU_BC_VERSION 1.0.4
|
||||
ENV INOTIFY_VERSION 2.0.0
|
||||
|
||||
RUN apk update
|
||||
|
||||
# Install common php extensions
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
RUN docker-php-ext-install iconv
|
||||
RUN docker-php-ext-install mbstring
|
||||
RUN docker-php-ext-install calendar
|
||||
|
||||
RUN apk add --no-cache --virtual sqlite-libs
|
||||
RUN apk add --no-cache --virtual sqlite-dev
|
||||
RUN docker-php-ext-install pdo_sqlite
|
||||
|
||||
RUN apk add --no-cache --virtual icu-dev
|
||||
RUN docker-php-ext-install intl
|
||||
|
||||
RUN apk add --no-cache --virtual libzip-dev zlib-dev
|
||||
RUN docker-php-ext-install zip
|
||||
|
||||
RUN apk add --no-cache --virtual libpng-dev
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
RUN apk add --no-cache postgresql-dev
|
||||
RUN docker-php-ext-install pdo_pgsql
|
||||
|
||||
# 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
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure apcu\
|
||||
&& docker-php-ext-install apcu
|
||||
# cleanup
|
||||
RUN rm /tmp/apcu.tar.gz
|
||||
|
||||
# Install APCu-BC extension
|
||||
ADD https://pecl.php.net/get/apcu_bc-$APCU_BC_VERSION.tgz /tmp/apcu_bc.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
||||
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure apcu-bc\
|
||||
&& docker-php-ext-install apcu-bc
|
||||
# cleanup
|
||||
RUN rm /tmp/apcu_bc.tar.gz
|
||||
|
||||
# Load APCU.ini before APC.ini
|
||||
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
||||
RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||
|
||||
# Install inotify extension
|
||||
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
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure inotify\
|
||||
&& docker-php-ext-install inotify
|
||||
# cleanup
|
||||
RUN rm /tmp/inotify.tar.gz
|
||||
|
||||
# Install swoole
|
||||
# First line fixes an error when installing pecl extensions. Found in https://github.com/docker-library/php/issues/233
|
||||
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \
|
||||
pecl install swoole && \
|
||||
docker-php-ext-enable swoole && \
|
||||
apk del .phpize-deps
|
||||
|
||||
# Install composer
|
||||
RUN php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
RUN chmod +x composer.phar
|
||||
RUN mv composer.phar /usr/local/bin/composer
|
||||
|
||||
# Make home directory writable by anyone
|
||||
RUN chmod 777 /home
|
||||
|
||||
VOLUME /home/shlink
|
||||
WORKDIR /home/shlink
|
||||
|
||||
# Expose swoole 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, swoole 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/zend-expressive-swoole start; do sleep 1 ; done
|
||||
21
data/infra/vhost.conf
Normal file
21
data/infra/vhost.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name shlink.local;
|
||||
root /home/shlink/www/public;
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
error_log /home/shlink/www/data/infra/nginx/shlink.error.log;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
root /home/shlink/www/public;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass shlink_php:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
}
|
||||
2
data/locks/.gitignore
vendored
Normal file
2
data/locks/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -1,22 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
class Version20160819142757 extends AbstractMigration
|
||||
{
|
||||
const MYSQL = 'mysql';
|
||||
const SQLITE = 'sqlite';
|
||||
private const MYSQL = 'mysql';
|
||||
private const SQLITE = 'sqlite';
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws DBALException
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema)
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$db = $this->connection->getDatabasePlatform()->getName();
|
||||
$table = $schema->getTable('short_urls');
|
||||
@@ -30,9 +34,9 @@ class Version20160819142757 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function down(Schema $schema)
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$db = $this->connection->getDatabasePlatform()->getName();
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
class Version20160820191203 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function up(Schema $schema)
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Check if the tables already exist
|
||||
$tables = $schema->getTables();
|
||||
@@ -28,7 +26,7 @@ class Version20160820191203 extends AbstractMigration
|
||||
$this->createShortUrlsInTagsTable($schema);
|
||||
}
|
||||
|
||||
protected function createTagsTable(Schema $schema)
|
||||
private function createTagsTable(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('tags');
|
||||
$table->addColumn('id', Type::BIGINT, [
|
||||
@@ -45,7 +43,7 @@ class Version20160820191203 extends AbstractMigration
|
||||
$table->setPrimaryKey(['id']);
|
||||
}
|
||||
|
||||
protected function createShortUrlsInTagsTable(Schema $schema)
|
||||
private function createShortUrlsInTagsTable(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('short_urls_in_tags');
|
||||
$table->addColumn('short_url_id', Type::BIGINT, [
|
||||
@@ -69,10 +67,7 @@ class Version20160820191203 extends AbstractMigration
|
||||
$table->setPrimaryKey(['short_url_id', 'tag_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function down(Schema $schema)
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$schema->dropTable('short_urls_in_tags');
|
||||
$schema->dropTable('tags');
|
||||
|
||||
47
data/migrations/Version20171021093246.php
Normal file
47
data/migrations/Version20171021093246.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
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', Type::DATETIME, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$shortUrls->addColumn('valid_until', Type::DATETIME, [
|
||||
'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');
|
||||
}
|
||||
}
|
||||
44
data/migrations/Version20171022064541.php
Normal file
44
data/migrations/Version20171022064541.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
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', Type::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');
|
||||
}
|
||||
}
|
||||
45
data/migrations/Version20180801183328.php
Normal file
45
data/migrations/Version20180801183328.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20180801183328 extends AbstractMigration
|
||||
{
|
||||
private const NEW_SIZE = 255;
|
||||
private const OLD_SIZE = 10;
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->setSize($schema, self::NEW_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->setSize($schema, self::OLD_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @param int $size
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function setSize(Schema $schema, int $size): void
|
||||
{
|
||||
$schema->getTable('short_urls')->getColumn('short_code')->setLength($size);
|
||||
}
|
||||
}
|
||||
75
data/migrations/Version20180913205455.php
Normal file
75
data/migrations/Version20180913205455.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Nothing to create
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws DBALException
|
||||
*/
|
||||
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)->getObfuscatedCopy();
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Nothing to rollback
|
||||
}
|
||||
}
|
||||
50
data/migrations/Version20180915110857.php
Normal file
50
data/migrations/Version20180915110857.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20180915110857 extends AbstractMigration
|
||||
{
|
||||
private const ON_DELETE_MAP = [
|
||||
'visit_locations' => 'SET NULL',
|
||||
'short_urls' => 'CASCADE',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @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
|
||||
}
|
||||
}
|
||||
68
data/migrations/Version20181020060559.php
Normal file
68
data/migrations/Version20181020060559.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
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',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @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, Type::STRING, ['notnull' => false]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
* @throws DBALException
|
||||
*/
|
||||
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->execute();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// No down
|
||||
}
|
||||
}
|
||||
40
data/migrations/Version20181020065148.php
Normal file
40
data/migrations/Version20181020065148.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class 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
|
||||
}
|
||||
}
|
||||
36
data/migrations/Version20181110175521.php
Normal file
36
data/migrations/Version20181110175521.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
36
data/migrations/Version20190824075137.php
Normal file
36
data/migrations/Version20190824075137.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
20
data/migrations_template.txt
Normal file
20
data/migrations_template.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace <namespace>;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version<version> extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
<up>
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
<down>
|
||||
}
|
||||
}
|
||||
0
data/proxies/.gitignore
vendored
Normal file → Executable file
0
data/proxies/.gitignore
vendored
Normal file → Executable file
26
docker-compose.override.yml.dist
Normal file
26
docker-compose.override.yml.dist
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
shlink_php:
|
||||
user: 1000:1000
|
||||
volumes:
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
|
||||
shlink_swoole:
|
||||
user: 1000:1000
|
||||
volumes:
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
|
||||
shlink_db:
|
||||
user: 1000:1000
|
||||
volumes:
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
|
||||
shlink_db_postgres:
|
||||
user: 1000:1000
|
||||
volumes:
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
72
docker-compose.yml
Normal file
72
docker-compose.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
shlink_nginx:
|
||||
container_name: shlink_nginx
|
||||
image: nginx:1.15.9-alpine
|
||||
ports:
|
||||
- "8000:80"
|
||||
volumes:
|
||||
- ./:/home/shlink/www
|
||||
- ./docs:/home/shlink/www/public/docs
|
||||
- ./data/infra/vhost.conf:/etc/nginx/conf.d/default.conf
|
||||
links:
|
||||
- shlink_php
|
||||
|
||||
shlink_php:
|
||||
container_name: shlink_php
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./data/infra/php.Dockerfile
|
||||
volumes:
|
||||
- ./:/home/shlink/www
|
||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||
links:
|
||||
- shlink_db
|
||||
- shlink_db_postgres
|
||||
- shlink_redis
|
||||
|
||||
shlink_swoole:
|
||||
container_name: shlink_swoole
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./data/infra/swoole.Dockerfile
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./:/home/shlink
|
||||
links:
|
||||
- shlink_db
|
||||
- shlink_db_postgres
|
||||
- shlink_redis
|
||||
|
||||
shlink_db:
|
||||
container_name: shlink_db
|
||||
image: mysql:5.7
|
||||
ports:
|
||||
- "3307:3306"
|
||||
volumes:
|
||||
- ./:/home/shlink/www
|
||||
- ./data/infra/database:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: shlink
|
||||
|
||||
shlink_db_postgres:
|
||||
container_name: shlink_db_postgres
|
||||
image: postgres:10.7-alpine
|
||||
ports:
|
||||
- "5433:5432"
|
||||
volumes:
|
||||
- ./:/home/shlink/www
|
||||
- ./data/infra/database_pg:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: root
|
||||
POSTGRES_DB: shlink
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
|
||||
shlink_redis:
|
||||
container_name: shlink_redis
|
||||
image: redis:5.0-alpine
|
||||
ports:
|
||||
- "6380:6379"
|
||||
204
docker/README.md
Normal file
204
docker/README.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Shlink Docker image
|
||||
|
||||
[](https://hub.docker.com/r/shlinkio/shlink/)
|
||||
[](https://hub.docker.com/r/shlinkio/shlink/)
|
||||
|
||||
This image provides an easy way to set up [shlink](https://shlink.io) on a container-based runtime.
|
||||
|
||||
It exposes a shlink instance served with [swoole](https://www.swoole.co.uk/), which persists data in a local [sqlite](https://www.sqlite.org/index.html) database.
|
||||
|
||||
## Usage
|
||||
|
||||
Shlink docker image exposes port `8080` in order to interact with its HTTP interface.
|
||||
|
||||
It also expects these two env vars to be provided, in order to properly generate short URLs at runtime.
|
||||
|
||||
* `SHORT_DOMAIN_HOST`: The custom short domain used for this shlink instance. For example **doma.in**.
|
||||
* `SHORT_DOMAIN_SCHEMA`: Either **http** or **https**.
|
||||
|
||||
So based on this, to run shlink on a local docker service, you should run a command like this:
|
||||
|
||||
```bash
|
||||
docker run --name shlink -p 8080:8080 -e SHORT_DOMAIN_HOST=doma.in -e SHORT_DOMAIN_SCHEMA=https shlinkio/shlink
|
||||
```
|
||||
|
||||
### Interact with shlink's CLI on a running container.
|
||||
|
||||
Once the shlink container is running, you can interact with the CLI tool by running `shlink` with any of the supported commands.
|
||||
|
||||
For example, if the container is called `shlink_container`, you can generate a new API key with:
|
||||
|
||||
```bash
|
||||
docker exec -it shlink_container shlink api-key:generate
|
||||
```
|
||||
|
||||
Or you can list all tags with:
|
||||
|
||||
```bash
|
||||
docker exec -it shlink_container shlink tag:list
|
||||
```
|
||||
|
||||
Or process remaining visits with:
|
||||
|
||||
```bash
|
||||
docker exec -it shlink_container shlink visit:process
|
||||
```
|
||||
|
||||
All shlink commands will work the same way.
|
||||
|
||||
You can also list all available commands just by running this:
|
||||
|
||||
```bash
|
||||
docker exec -it shlink_container shlink
|
||||
```
|
||||
|
||||
## Use an external DB
|
||||
|
||||
The image comes with a working sqlite database, but in production you will probably want to usa a distributed database.
|
||||
|
||||
It is possible to use a set of env vars to make this shlink instance interact with an external MySQL or PostgreSQL database.
|
||||
|
||||
* `DB_DRIVER`: **[Mandatory]**. Use the value **mysql** or **postgres** to prevent the sqlite database to be used.
|
||||
* `DB_NAME`: [Optional]. The database name to be used. Defaults to **shlink**.
|
||||
* `DB_USER`: **[Mandatory]**. The username credential for the database server.
|
||||
* `DB_PASSWORD`: **[Mandatory]**. The password credential for the database server.
|
||||
* `DB_HOST`: **[Mandatory]**. The host name of the server running the database engine.
|
||||
* `DB_PORT`: [Optional]. The port in which the database service is running.
|
||||
* Default value is based on the driver:
|
||||
* **mysql** -> `3306`
|
||||
* **postgres** -> `5432`
|
||||
|
||||
> PostgreSQL is supported since v1.16.1 of this image. Do not try to use it with previous versions.
|
||||
|
||||
Taking this into account, you could run shlink on a local docker service like this:
|
||||
|
||||
```bash
|
||||
docker run --name shlink -p 8080:8080 -e SHORT_DOMAIN_HOST=doma.in -e SHORT_DOMAIN_SCHEMA=https -e DB_DRIVER=mysql -e DB_USER=root -e DB_PASSWORD=123abc -e DB_HOST=something.rds.amazonaws.com shlinkio/shlink
|
||||
```
|
||||
|
||||
You could even link to a local database running on a different container:
|
||||
|
||||
```bash
|
||||
docker run --name shlink -p 8080:8080 [...] -e DB_HOST=some_mysql_container --link some_mysql_container shlinkio/shlink
|
||||
```
|
||||
|
||||
> If you have considered using SQLite but sharing the database file with a volume, read [this issue](https://github.com/shlinkio/shlink-docker-image/issues/40) first.
|
||||
|
||||
## Supported env vars
|
||||
|
||||
A few env vars have been already used in previous examples, but this image supports others that can be used to customize its behavior.
|
||||
|
||||
This is the complete list of supported env vars:
|
||||
|
||||
* `SHORT_DOMAIN_HOST`: The custom short domain used for this shlink instance. For example **doma.in**.
|
||||
* `SHORT_DOMAIN_SCHEMA`: Either **http** or **https**.
|
||||
* `SHORTCODE_CHARS`: A charset to use when building short codes. Only needed when using more than one shlink instance ([Multi instance considerations](#multi-instance-considerations)).
|
||||
* `DB_DRIVER`: **sqlite** (which is the default value), **mysql** or **postgres**.
|
||||
* `DB_NAME`: The database name to be used when using an external database driver. Defaults to **shlink**.
|
||||
* `DB_USER`: The username credential to be used when using an external database driver.
|
||||
* `DB_PASSWORD`: The password credential to be used when using an external database driver.
|
||||
* `DB_HOST`: The host name of the database server when using an external database driver.
|
||||
* `DB_PORT`: The port in which the database service is running when using an external database driver. Defaults to **3306**.
|
||||
* `DISABLE_TRACK_PARAM`: The name of a query param that can be used to visit short URLs avoiding the visit to be tracked. This feature won't be available if not value is provided.
|
||||
* `DELETE_SHORT_URL_THRESHOLD`: The amount of visits on short URLs which will not allow them to be deleted. Defaults to `15`.
|
||||
* `LOCALE`: Defines the default language for error pages when a user accesses a short URL which does not exist. Supported values are **es** and **en**. Defaults to **en**.
|
||||
* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x (after following redirects) is returned when trying to shorten a URL. Defaults to `true`.
|
||||
* `NOT_FOUND_REDIRECT_TO`: If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
|
||||
* `REDIS_SERVERS`: A comma-separated list of redis servers where Shlink locks are stored (locks are used to prevent some operations to be run more than once in parallel).
|
||||
|
||||
This is important when running more than one Shlink instance ([Multi instance considerations](#multi-instance-considerations)). If not provided, Shlink stores locks on every instance separately.
|
||||
|
||||
If more than one server is provided, Shlink will expect them to be configured as a [redis cluster](https://redis.io/topics/cluster-tutorial).
|
||||
|
||||
In the future, these redis servers could be used for other caching operations performed by shlink.
|
||||
|
||||
An example using all env vars could look like this:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--name shlink \
|
||||
-p 8080:8080 \
|
||||
-e SHORT_DOMAIN_HOST=doma.in \
|
||||
-e SHORT_DOMAIN_SCHEMA=https \
|
||||
-e DB_DRIVER=mysql \
|
||||
-e DB_NAME=shlink \
|
||||
-e DB_USER=root \
|
||||
-e DB_PASSWORD=123abc \
|
||||
-e DB_HOST=something.rds.amazonaws.com \
|
||||
-e DB_PORT=3306 \
|
||||
-e DISABLE_TRACK_PARAM="no-track" \
|
||||
-e DELETE_SHORT_URL_THRESHOLD=30 \
|
||||
-e LOCALE=es \
|
||||
-e VALIDATE_URLS=false \
|
||||
-e "NOT_FOUND_REDIRECT_TO=https://www.google.com" \
|
||||
-e "REDIS_SERVERS=tcp://172.20.0.1:6379,tcp://172.20.0.2:6379" \
|
||||
shlinkio/shlink
|
||||
```
|
||||
|
||||
## Provide config via volumes
|
||||
|
||||
Rather than providing custom configuration via env vars, it is also possible ot provide config files in json format.
|
||||
|
||||
Mounting a volume at `config/params` you will make shlink load all the files on it with the `.config.json` suffix.
|
||||
|
||||
The whole configuration should have this format, but it can be split into multiple files that will be merged:
|
||||
|
||||
```json
|
||||
{
|
||||
"disable_track_param": "my_param",
|
||||
"delete_short_url_threshold": 30,
|
||||
"locale": "es",
|
||||
"short_domain_schema": "https",
|
||||
"short_domain_host": "doma.in",
|
||||
"validate_url": false,
|
||||
"not_found_redirect_to": "https://my-landing-page.com",
|
||||
"redis_servers": [
|
||||
"tcp://172.20.0.1:6379",
|
||||
"tcp://172.20.0.2:6379"
|
||||
],
|
||||
"db_config": {
|
||||
"driver": "pdo_mysql",
|
||||
"dbname": "shlink",
|
||||
"user": "root",
|
||||
"password": "123abc",
|
||||
"host": "something.rds.amazonaws.com",
|
||||
"port": "3306"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> This is internally parsed to how shlink expects the config. If you are using a version previous to 1.17.0, this parser is not present and you need to provide a config structure like the one [documented previously](https://github.com/shlinkio/shlink-docker-image/tree/v1.16.3#provide-config-via-volumes).
|
||||
|
||||
Once created just run shlink with the volume:
|
||||
|
||||
```bash
|
||||
docker run --name shlink -p 8080:8080 -v ${PWD}/my/config/dir:/etc/shlink/config/params shlinkio/shlink
|
||||
```
|
||||
|
||||
## Multi instance considerations
|
||||
|
||||
These are some considerations to take into account when running multiple instances of shlink.
|
||||
|
||||
* The first time shlink is run, it generates a charset used to generate short codes, which is a shuffled base62 charset.
|
||||
|
||||
If you are using several shlink instances, you will probably want all of them to use the same charset.
|
||||
|
||||
You can get a shuffled base62 charset by going to [https://shlink.io/short-code-chars](https://shlink.io/short-code-chars), and then you just need to pass it to all shlink instances using the `SHORTCODE_CHARS` env var.
|
||||
|
||||
If you don't do this, each shlink instance will use a different charset. However this shouldn't be a problem in practice, since the chances to get a collision will be very low.
|
||||
|
||||
* Some operations performed by Shlink should never be run more than once at the same time (like creating the database for the first time, or downloading the GeoLite2 database). For this reason, Shlink uses a locking system.
|
||||
|
||||
However, these locks are locally scoped to each Shlink instance by default.
|
||||
|
||||
You can (and should) make the locks to be shared by all Shlink instances by using a redis server/cluster. Just define the `REDIS_SERVERS` env var with the list of servers.
|
||||
|
||||
## Versions
|
||||
|
||||
Versions of this image match the shlink version it contains.
|
||||
|
||||
For example, installing `shlinkio/shlink:1.15.0`, you will get an image containing shlink v1.15.0.
|
||||
|
||||
The `latest` docker tag always holds the latest contents in master, and it's considered unestable and not suitable for production.
|
||||
|
||||
> There are no official shlink images previous to v1.15.0.
|
||||
175
docker/config/shlink_in_docker.local.php
Normal file
175
docker/config/shlink_in_docker.local.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function implode;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
use function sprintf;
|
||||
use function str_shuffle;
|
||||
use function substr;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
$helper = new class {
|
||||
private const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
private const DB_DRIVERS_MAP = [
|
||||
'mysql' => 'pdo_mysql',
|
||||
'postgres' => 'pdo_pgsql',
|
||||
];
|
||||
private const DB_PORTS_MAP = [
|
||||
'mysql' => '3306',
|
||||
'postgres' => '5432',
|
||||
];
|
||||
|
||||
/** @var string */
|
||||
private $charset;
|
||||
/** @var string */
|
||||
private $secretKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
[$this->charset, $this->secretKey] = $this->initShlinkKeys();
|
||||
}
|
||||
|
||||
private function initShlinkKeys(): array
|
||||
{
|
||||
$keysFile = sprintf('%s/shlink.keys', sys_get_temp_dir());
|
||||
if (file_exists($keysFile)) {
|
||||
return explode(',', file_get_contents($keysFile));
|
||||
}
|
||||
|
||||
$keys = [
|
||||
env('SHORTCODE_CHARS', $this->generateShortcodeChars()),
|
||||
env('SECRET_KEY', $this->generateSecretKey()),
|
||||
];
|
||||
|
||||
file_put_contents($keysFile, implode(',', $keys));
|
||||
return $keys;
|
||||
}
|
||||
|
||||
private function generateShortcodeChars(): string
|
||||
{
|
||||
return str_shuffle(self::BASE62);
|
||||
}
|
||||
|
||||
private function generateSecretKey(): string
|
||||
{
|
||||
return substr(str_shuffle(self::BASE62), 0, 32);
|
||||
}
|
||||
|
||||
public function getShortcodeChars(): string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
public function getSecretKey(): string
|
||||
{
|
||||
return $this->secretKey;
|
||||
}
|
||||
|
||||
public function getDbConfig(): array
|
||||
{
|
||||
$driver = env('DB_DRIVER');
|
||||
if ($driver === null || $driver === 'sqlite') {
|
||||
return [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => 'data/database.sqlite',
|
||||
];
|
||||
}
|
||||
|
||||
$driverOptions = $driver !== 'mysql' ? [] : [
|
||||
// PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
1002 => 'SET NAMES utf8',
|
||||
];
|
||||
return [
|
||||
'driver' => self::DB_DRIVERS_MAP[$driver],
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'host' => env('DB_HOST'),
|
||||
'port' => env('DB_PORT', self::DB_PORTS_MAP[$driver]),
|
||||
'driverOptions' => $driverOptions,
|
||||
];
|
||||
}
|
||||
|
||||
public function getNotFoundConfig(): array
|
||||
{
|
||||
$notFoundRedirectTo = env('NOT_FOUND_REDIRECT_TO');
|
||||
|
||||
return [
|
||||
'enable_redirection' => $notFoundRedirectTo !== null,
|
||||
'redirect_to' => $notFoundRedirectTo,
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
|
||||
'config_cache_enabled' => false,
|
||||
|
||||
'app_options' => [
|
||||
'secret_key' => $helper->getSecretKey(),
|
||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
||||
],
|
||||
|
||||
'delete_short_urls' => [
|
||||
'check_visits_threshold' => true,
|
||||
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', 15),
|
||||
],
|
||||
|
||||
'translator' => [
|
||||
'locale' => env('LOCALE', 'en'),
|
||||
],
|
||||
|
||||
'entity_manager' => [
|
||||
'connection' => $helper->getDbConfig(),
|
||||
],
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => env('SHORT_DOMAIN_SCHEMA', 'http'),
|
||||
'hostname' => env('SHORT_DOMAIN_HOST', ''),
|
||||
],
|
||||
'shortcode_chars' => $helper->getShortcodeChars(),
|
||||
'validate_url' => (bool) env('VALIDATE_URLS', true),
|
||||
'not_found_short_url' => $helper->getNotFoundConfig(),
|
||||
],
|
||||
|
||||
'logger' => [
|
||||
'handlers' => [
|
||||
'shlink_rotating_handler' => [
|
||||
'level' => Logger::EMERGENCY, // This basically disables regular file logs
|
||||
],
|
||||
'shlink_stdout_handler' => [
|
||||
'class' => StreamHandler::class,
|
||||
'level' => Logger::INFO,
|
||||
'stream' => 'php://stdout',
|
||||
'formatter' => 'dashed',
|
||||
],
|
||||
],
|
||||
|
||||
'loggers' => [
|
||||
'Shlink' => [
|
||||
'handlers' => ['shlink_stdout_handler'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'aliases' => env('REDIS_SERVERS') === null ? [] : [
|
||||
'lock_store' => 'redis_lock_store',
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'servers' => env('REDIS_SERVERS'),
|
||||
],
|
||||
|
||||
];
|
||||
17
docker/docker-entrypoint.sh
Normal file
17
docker/docker-entrypoint.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
cd /etc/shlink
|
||||
|
||||
echo "Creating fresh database if needed..."
|
||||
php bin/cli db:create -n -q
|
||||
|
||||
echo "Updating database..."
|
||||
php bin/cli db:migrate -n -q
|
||||
|
||||
echo "Generating proxies..."
|
||||
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n -q
|
||||
|
||||
# When restarting the container, swoole 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/zendframework/zend-expressive-swoole/bin/zend-expressive-swoole start; do sleep 1 ; done
|
||||
13
docs/swagger/definitions/Error.json
Normal file
13
docs/swagger/definitions/Error.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "A machine unique code"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A human-friendly error message"
|
||||
}
|
||||
}
|
||||
}
|
||||
31
docs/swagger/definitions/Health.json
Normal file
31
docs/swagger/definitions/Health.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pass",
|
||||
"fail"
|
||||
],
|
||||
"description": "The status of the service"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Shlink version"
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"about": {
|
||||
"type": "string",
|
||||
"description": "About shlink"
|
||||
},
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "Shlink project repository"
|
||||
}
|
||||
},
|
||||
"description": "A list of links"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
docs/swagger/definitions/Pagination.json
Normal file
25
docs/swagger/definitions/Pagination.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currentPage": {
|
||||
"type": "integer",
|
||||
"description": "The number of current page."
|
||||
},
|
||||
"pagesCount": {
|
||||
"type": "integer",
|
||||
"description": "The total number of pages that can be obtained."
|
||||
},
|
||||
"itemsPerPage": {
|
||||
"type": "integer",
|
||||
"description": "The number of items for every page."
|
||||
},
|
||||
"itemsInCurrentPage": {
|
||||
"type": "integer",
|
||||
"description": "The number of items in current page (could be smaller than itemsPerPage)."
|
||||
},
|
||||
"totalItems": {
|
||||
"type": "integer",
|
||||
"description": "The total number of items among all pages."
|
||||
}
|
||||
}
|
||||
}
|
||||
41
docs/swagger/definitions/ShortUrl.json
Normal file
41
docs/swagger/definitions/ShortUrl.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shortCode": {
|
||||
"type": "string",
|
||||
"description": "The short code for this short URL."
|
||||
},
|
||||
"shortUrl": {
|
||||
"type": "string",
|
||||
"description": "The short URL."
|
||||
},
|
||||
"longUrl": {
|
||||
"type": "string",
|
||||
"description": "The original long URL."
|
||||
},
|
||||
"dateCreated": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "The date in which the short URL was created in ISO format."
|
||||
},
|
||||
"visitsCount": {
|
||||
"type": "integer",
|
||||
"description": "The number of visits that this short URL has recieved."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "A list of tags applied to this short URL"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "./ShortUrlMeta.json"
|
||||
},
|
||||
"originalUrl": {
|
||||
"deprecated": true,
|
||||
"type": "string",
|
||||
"description": "The original long URL. [DEPRECATED. Use longUrl instead]"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
docs/swagger/definitions/ShortUrlMeta.json
Normal file
21
docs/swagger/definitions/ShortUrlMeta.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["validSince", "validUntil", "maxVisits"],
|
||||
"properties": {
|
||||
"validSince": {
|
||||
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"validUntil": {
|
||||
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"maxVisits": {
|
||||
"description": "The maximum number of allowed visits for this short code",
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
26
docs/swagger/definitions/Visit.json
Normal file
26
docs/swagger/definitions/Visit.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"referer": {
|
||||
"type": "string",
|
||||
"description": "The origin from which the visit was performed"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "The date in which the visit was performed"
|
||||
},
|
||||
"userAgent": {
|
||||
"type": "string",
|
||||
"description": "The user agent from which the visit was performed"
|
||||
},
|
||||
"visitLocation": {
|
||||
"$ref": "./VisitLocation.json"
|
||||
},
|
||||
"remoteAddr": {
|
||||
"type": "string",
|
||||
"description": "This value is deprecated and will always be null",
|
||||
"deprecated": true
|
||||
}
|
||||
}
|
||||
}
|
||||
26
docs/swagger/definitions/VisitLocation.json
Normal file
26
docs/swagger/definitions/VisitLocation.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cityName": {
|
||||
"type": "string"
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"countryName": {
|
||||
"type": "string"
|
||||
},
|
||||
"latitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"regionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"timezone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
62
docs/swagger/paths/health.json
Normal file
62
docs/swagger/paths/health.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "health",
|
||||
"tags": [
|
||||
"Monitoring"
|
||||
],
|
||||
"summary": "Check healthiness",
|
||||
"description": "Checks the healthiness of the service, making sure it can access required resources.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The passing health status",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Health.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"status": "pass",
|
||||
"version": "1.16.0",
|
||||
"links": {
|
||||
"about": "https://shlink.io",
|
||||
"project": "https://github.com/shlinkio/shlink"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "The failing health status",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Health.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"status": "fail",
|
||||
"version": "1.16.0",
|
||||
"links": {
|
||||
"about": "https://shlink.io",
|
||||
"project": "https://github.com/shlinkio/shlink"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
docs/swagger/paths/v1_authenticate.json
Normal file
84
docs/swagger/paths/v1_authenticate.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"post": {
|
||||
"deprecated": true,
|
||||
"operationId": "authenticate",
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "[Deprecated] Perform authentication",
|
||||
"description": "**This endpoint is deprecated, since the authentication can be performed via API key now**. Performs an authentication.",
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"apiKey"
|
||||
],
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"description": "The API key to authenticate with",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The authentication worked.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "The authentication token that needs to be sent in the Authorization header"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An API key was not provided.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The API key is incorrect, is disabled or has expired.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
276
docs/swagger/paths/v1_short-urls.json
Normal file
276
docs/swagger/paths/v1_short-urls.json
Normal file
@@ -0,0 +1,276 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "listShortUrls",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "List short URLs",
|
||||
"description": "Returns the list of short URLs.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"description": "The page to be displayed. Defaults to 1",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "searchTerm",
|
||||
"in": "query",
|
||||
"description": "A query used to filter results by searching for it on the longUrl and shortCode fields. (Since v1.3.0)",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tags[]",
|
||||
"in": "query",
|
||||
"description": "A list of tags used to filter the resultset. Only short URLs tagged with at least one of the provided tags will be returned. (Since v1.3.0)",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orderBy",
|
||||
"in": "query",
|
||||
"description": "The field from which you want to order the result. (Since v1.3.0)",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"longUrl",
|
||||
"shortCode",
|
||||
"dateCreated",
|
||||
"visits"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The list of short URLs",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shortUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "../definitions/ShortUrl.json"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"$ref": "../definitions/Pagination.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"shortUrls": {
|
||||
"data": [
|
||||
{
|
||||
"shortCode": "12C18",
|
||||
"shortUrl": "https://doma.in/12C18",
|
||||
"longUrl": "https://store.steampowered.com",
|
||||
"dateCreated": "2016-08-21T20:34:16+02:00",
|
||||
"visitsCount": 328,
|
||||
"tags": [
|
||||
"games",
|
||||
"tech"
|
||||
],
|
||||
"meta": {
|
||||
"validSince": "2017-01-21T00:00:00+02:00",
|
||||
"validUntil": null,
|
||||
"maxVisits": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"shortCode": "12Kb3",
|
||||
"shortUrl": "https://doma.in/12Kb3",
|
||||
"longUrl": "https://shlink.io",
|
||||
"dateCreated": "2016-05-01T20:34:16+02:00",
|
||||
"visitsCount": 1029,
|
||||
"tags": [
|
||||
"shlink"
|
||||
],
|
||||
"meta": {
|
||||
"validSince": null,
|
||||
"validUntil": null,
|
||||
"maxVisits": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"shortCode": "123bA",
|
||||
"shortUrl": "https://doma.in/123bA",
|
||||
"longUrl": "https://www.google.com",
|
||||
"dateCreated": "2015-10-01T20:34:16+02:00",
|
||||
"visitsCount": 25,
|
||||
"tags": [],
|
||||
"meta": {
|
||||
"validSince": "2017-01-21T00:00:00+02:00",
|
||||
"validUntil": null,
|
||||
"maxVisits": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"currentPage": 5,
|
||||
"pagesCount": 12,
|
||||
"itemsPerPage": 10,
|
||||
"itemsInCurrentPage": 10,
|
||||
"totalItems": 115
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"post": {
|
||||
"operationId": "createShortUrl",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "Create short URL",
|
||||
"description": "Creates a new short URL.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.<br></br>**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.",
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"longUrl"
|
||||
],
|
||||
"properties": {
|
||||
"longUrl": {
|
||||
"description": "The URL to parse",
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"description": "The URL to parse",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"validSince": {
|
||||
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"validUntil": {
|
||||
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"customSlug": {
|
||||
"description": "A unique custom slug to be used instead of the generated short code",
|
||||
"type": "string"
|
||||
},
|
||||
"maxVisits": {
|
||||
"description": "The maximum number of allowed visits for this short code",
|
||||
"type": "number"
|
||||
},
|
||||
"findIfExists": {
|
||||
"description": "Will force existing matching URL to be returned if found, instead of creating a new one",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The result of parsing the long URL",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/ShortUrl.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"shortCode": "12C18",
|
||||
"shortUrl": "https://doma.in/12C18",
|
||||
"longUrl": "https://store.steampowered.com",
|
||||
"dateCreated": "2016-08-21T20:34:16+02:00",
|
||||
"visitsCount": 0,
|
||||
"tags": [
|
||||
"games",
|
||||
"tech"
|
||||
],
|
||||
"meta": {
|
||||
"validSince": "2017-01-21T00:00:00+02:00",
|
||||
"validUntil": null,
|
||||
"maxVisits": 500
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The long URL was not provided or is invalid.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
docs/swagger/paths/v1_short-urls_shorten.json
Normal file
123
docs/swagger/paths/v1_short-urls_shorten.json
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "shortenUrl",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "Create a short URL",
|
||||
"description": "Creates a short URL in a single API call. Useful for third party integrations.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "apiKey",
|
||||
"in": "query",
|
||||
"description": "The API key used to authenticate the request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "longUrl",
|
||||
"in": "query",
|
||||
"description": "The URL to be shortened",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"in": "query",
|
||||
"description": "The format in which you want the response to be returned. You can also use the \"Accept\" header instead of this",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"txt",
|
||||
"json"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The list of short URLs",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/ShortUrl.json"
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"longUrl": "https://github.com/shlinkio/shlink",
|
||||
"shortUrl": "https://doma.in/abc123",
|
||||
"shortCode": "abc123",
|
||||
"dateCreated": "2016-08-21T20:34:16+02:00",
|
||||
"visitsCount": 0,
|
||||
"tags": [
|
||||
"games",
|
||||
"tech"
|
||||
],
|
||||
"meta": {
|
||||
"validSince": "2017-01-21T00:00:00+02:00",
|
||||
"validUntil": null,
|
||||
"maxVisits": 100
|
||||
}
|
||||
},
|
||||
"text/plain": "https://doma.in/abc123"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The long URL was not provided or is invalid.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"error": "INVALID_URL",
|
||||
"message": "Provided URL foo is invalid. Try with a different one."
|
||||
},
|
||||
"text/plain": "INVALID_URL"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"error": "UNKNOWN_ERROR",
|
||||
"message": "Unexpected error occurred"
|
||||
},
|
||||
"text/plain": "UNKNOWN_ERROR"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
334
docs/swagger/paths/v1_short-urls_{shortCode}.json
Normal file
334
docs/swagger/paths/v1_short-urls_{shortCode}.json
Normal file
@@ -0,0 +1,334 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "getShortUrl",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "Parse short code",
|
||||
"description": "Get the long URL behind a short URL's short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code to resolve.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The URL info behind a short code.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/ShortUrl.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"shortCode": "12Kb3",
|
||||
"shortUrl": "https://doma.in/12Kb3",
|
||||
"longUrl": "https://shlink.io",
|
||||
"dateCreated": "2016-05-01T20:34:16+02:00",
|
||||
"visitsCount": 1029,
|
||||
"tags": [
|
||||
"shlink"
|
||||
],
|
||||
"meta": {
|
||||
"validSince": "2017-01-21T00:00:00+02:00",
|
||||
"validUntil": null,
|
||||
"maxVisits": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Provided shortCode does not match the character set currently used by the app to generate short codes.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No URL was found for provided short code.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"patch": {
|
||||
"operationId": "editShortUrl",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "Edit short URL",
|
||||
"description": "Update certain meta arguments from an existing short URL.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code to edit.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"validSince": {
|
||||
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"validUntil": {
|
||||
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"maxVisits": {
|
||||
"description": "The maximum number of allowed visits for this short code",
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The short code has been properly updated."
|
||||
},
|
||||
"400": {
|
||||
"description": "Provided meta arguments are invalid.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No short URL was found for provided short code.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"put": {
|
||||
"deprecated": true,
|
||||
"operationId": "editShortUrlPut",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "[DEPRECATED] Edit short URL",
|
||||
"description": "**[DEPRECATED]** Use [editShortUrl](#/Short_URLs/getShortUrl) instead",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code to edit.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"validSince": {
|
||||
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"validUntil": {
|
||||
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||
"type": "string"
|
||||
},
|
||||
"maxVisits": {
|
||||
"description": "The maximum number of allowed visits for this short code",
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The short code has been properly updated."
|
||||
},
|
||||
"400": {
|
||||
"description": "Provided meta arguments are invalid.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No short URL was found for provided short code.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"delete": {
|
||||
"operationId": "deleteShortUrl",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "Delete short URL",
|
||||
"description": "Deletes the short URL for provided short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code to edit.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The short URL has been properly deleted."
|
||||
},
|
||||
"400": {
|
||||
"description": "The visits threshold in shlink does not allow this short URL to be deleted.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"error": "INVALID_SHORTCODE_DELETION",
|
||||
"message": "It is not possible to delete URL with short code \"abc123\" because it has reached more than \"15\" visits."
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No short URL was found for provided short code.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
docs/swagger/paths/v1_short-urls_{shortCode}_tags.json
Normal file
110
docs/swagger/paths/v1_short-urls_{shortCode}_tags.json
Normal file
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"put": {
|
||||
"operationId": "editShortUrlTags",
|
||||
"tags": [
|
||||
"Short URLs"
|
||||
],
|
||||
"summary": "Edit tags on short URL",
|
||||
"description": "Edit the tags on URL identified by provided short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code for the short URL in which we want to edit tags.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tags"
|
||||
],
|
||||
"properties": {
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The list of tags to set to the short URL."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of tags.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"tags": [
|
||||
"games",
|
||||
"tech"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The request body does not contain a \"tags\" param with array type.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No short URL was found for provided short code.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
154
docs/swagger/paths/v1_short-urls_{shortCode}_visits.json
Normal file
154
docs/swagger/paths/v1_short-urls_{shortCode}_visits.json
Normal file
@@ -0,0 +1,154 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "getShortUrlVisits",
|
||||
"tags": [
|
||||
"Visits"
|
||||
],
|
||||
"summary": "List visits for short URL",
|
||||
"description": "Get the list of visits on the short URL behind provided short code.<br><br>**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code for the short URL from which we want to get the visits.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "startDate",
|
||||
"in": "query",
|
||||
"description": "The date (in ISO-8601 format) from which we want to get visits.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "endDate",
|
||||
"in": "query",
|
||||
"description": "The date (in ISO-8601 format) until which we want to get visits.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"description": "The page to display. Defaults to 1",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "itemsPerPage",
|
||||
"in": "query",
|
||||
"description": "The amount of items to return on every page. Defaults to all the items",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of visits.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"visits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "../definitions/Visit.json"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"$ref": "../definitions/Pagination.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"visits": {
|
||||
"data": [
|
||||
{
|
||||
"referer": "https://twitter.com",
|
||||
"date": "2015-08-20T05:05:03+04:00",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0",
|
||||
"visitLocation": null
|
||||
},
|
||||
{
|
||||
"referer": "https://t.co",
|
||||
"date": "2015-08-20T05:05:03+04:00",
|
||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"visitLocation": {
|
||||
"cityName": "Cupertino",
|
||||
"countryCode": "US",
|
||||
"countryName": "United States",
|
||||
"latitude": "37.3042",
|
||||
"longitude": "-122.0946",
|
||||
"regionName": "California",
|
||||
"timezone": "America/Los_Angeles"
|
||||
}
|
||||
},
|
||||
{
|
||||
"referer": null,
|
||||
"date": "2015-08-20T05:05:03+04:00",
|
||||
"userAgent": "some_web_crawler/1.4",
|
||||
"visitLocation": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"currentPage": 5,
|
||||
"pagesCount": 12,
|
||||
"itemsPerPage": 10,
|
||||
"itemsInCurrentPage": 10,
|
||||
"totalItems": 115
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "The short code does not belong to any short URL.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
275
docs/swagger/paths/v1_tags.json
Normal file
275
docs/swagger/paths/v1_tags.json
Normal file
@@ -0,0 +1,275 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "listTags",
|
||||
"tags": [
|
||||
"Tags"
|
||||
],
|
||||
"summary": "List existing tags",
|
||||
"description": "Returns the list of all tags used in any short URL, ordered by name",
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The list of tags",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"tags": {
|
||||
"data": [
|
||||
"games",
|
||||
"php",
|
||||
"shlink",
|
||||
"tech"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"post": {
|
||||
"operationId": "createTags",
|
||||
"tags": [
|
||||
"Tags"
|
||||
],
|
||||
"summary": "Create tags",
|
||||
"description": "Provided a list of tags, creates all that do not yet exist",
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tags"
|
||||
],
|
||||
"properties": {
|
||||
"tags": {
|
||||
"description": "The list of tag names to create",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The list of tags",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"tags": {
|
||||
"data": [
|
||||
"games",
|
||||
"php",
|
||||
"shlink",
|
||||
"tech"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"put": {
|
||||
"operationId": "renameTag",
|
||||
"tags": [
|
||||
"Tags"
|
||||
],
|
||||
"summary": "Rename tag",
|
||||
"description": "Renames one existing tag",
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Request body.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"oldName",
|
||||
"newName"
|
||||
],
|
||||
"properties": {
|
||||
"oldName": {
|
||||
"description": "Current name of the tag",
|
||||
"type": "string"
|
||||
},
|
||||
"newName": {
|
||||
"description": "New name of the tag",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The tag has been properly renamed"
|
||||
},
|
||||
"400": {
|
||||
"description": "You have not provided either the oldName or the newName params.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "There's no tag found with the name provided in oldName param.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"delete": {
|
||||
"operationId": "deleteTags",
|
||||
"tags": [
|
||||
"Tags"
|
||||
],
|
||||
"summary": "Delete tags",
|
||||
"description": "Deletes provided list of tags",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tags[]",
|
||||
"in": "query",
|
||||
"description": "The names of the tags to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"ApiKey": []
|
||||
},
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Tags properly deleted"
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
docs/swagger/paths/{shortCode}.json
Normal file
36
docs/swagger/paths/{shortCode}.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"get": {
|
||||
"operationId": "shortUrl",
|
||||
"tags": [
|
||||
"URL Shortener"
|
||||
],
|
||||
"summary": "Short URL",
|
||||
"description": "Represents a short URL. Tracks the visit and redirects tio the corresponding long URL",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"description": "The short code to resolve.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"302": {
|
||||
"description": "Visit properly tracked and redirected"
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user