mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 12:13:13 +08:00
Compare commits
552 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca06040efc | ||
|
|
2102cc4e9a | ||
|
|
14d3493db8 | ||
|
|
d082d208e1 | ||
|
|
959efd17c8 | ||
|
|
30a7c55e84 | ||
|
|
2aec759857 | ||
|
|
54dcaaac0c | ||
|
|
8e5730f374 | ||
|
|
cb1705b6e8 | ||
|
|
0bcefda60d | ||
|
|
966620f840 | ||
|
|
bd3bb67949 | ||
|
|
69f4daa9d2 | ||
|
|
ec11155c9c | ||
|
|
c48a3a24f7 | ||
|
|
1b8bc9f0ff | ||
|
|
5bf25c7eca | ||
|
|
5a7f0ad340 | ||
|
|
8a93922da0 | ||
|
|
295de5be8e | ||
|
|
5c114b584d | ||
|
|
dad58b7610 | ||
|
|
23c1dadb4c | ||
|
|
05332e0606 | ||
|
|
453842246f | ||
|
|
38280b9027 | ||
|
|
7d7c0011bb | ||
|
|
de2d87a6d9 | ||
|
|
537152450f | ||
|
|
87f6b19207 | ||
|
|
064fef5d8a | ||
|
|
6aebaa94af | ||
|
|
a1a6ac9c08 | ||
|
|
0d936425c2 | ||
|
|
00f867c6ee | ||
|
|
bfea3f35f0 | ||
|
|
3f3cf5e20e | ||
|
|
0786a962e7 | ||
|
|
f7c0486101 | ||
|
|
2e3798b282 | ||
|
|
181740c3e9 | ||
|
|
23c51a1d5f | ||
|
|
15ce529c09 | ||
|
|
0fd941401b | ||
|
|
808ae6a442 | ||
|
|
ada8d18fa1 | ||
|
|
9752abff19 | ||
|
|
ee43e68a57 | ||
|
|
348ac78f5a | ||
|
|
0b22fb933c | ||
|
|
cbd4b4849f | ||
|
|
f8a48c16f0 | ||
|
|
8cc4e4bfca | ||
|
|
6c01bb87bf | ||
|
|
02d5a6f15e | ||
|
|
f361403888 | ||
|
|
3a4550fe24 | ||
|
|
5e722c830f | ||
|
|
5a56982ad9 | ||
|
|
13d70cd12a | ||
|
|
bb87bdce8a | ||
|
|
cc7ded1be7 | ||
|
|
d8735e6a91 | ||
|
|
813ae71aad | ||
|
|
1a75bd87d8 | ||
|
|
bdc89e2056 | ||
|
|
bf09990f9c | ||
|
|
81ba8dc518 | ||
|
|
e519aaaf1e | ||
|
|
5a90a5e6c7 | ||
|
|
b855ea92a9 | ||
|
|
7e74d06cdd | ||
|
|
1e7602bc36 | ||
|
|
7477e672fe | ||
|
|
4a4522dfa3 | ||
|
|
8afe058cfc | ||
|
|
e13103a925 | ||
|
|
8e167ff174 | ||
|
|
c0dcd31819 | ||
|
|
a83ae996db | ||
|
|
a66ddabe8a | ||
|
|
cdab1e9cae | ||
|
|
f2140d1eb0 | ||
|
|
4a3fa85b5f | ||
|
|
ade23a9650 | ||
|
|
fc547e6c47 | ||
|
|
f532b5edee | ||
|
|
da76eb5cf4 | ||
|
|
ac89f352ce | ||
|
|
198b2a2ace | ||
|
|
93a3d78111 | ||
|
|
494997d021 | ||
|
|
eb1345e5c3 | ||
|
|
dc8f5d002d | ||
|
|
9030e5e6eb | ||
|
|
2b827baeed | ||
|
|
cc6fa312f0 | ||
|
|
b8eba5b643 | ||
|
|
0c3f98cc37 | ||
|
|
cd35770d26 | ||
|
|
bd3a59e9ca | ||
|
|
ff50d601b3 | ||
|
|
a4fde0f9e6 | ||
|
|
c7a621cb31 | ||
|
|
6f62d62909 | ||
|
|
c3aa2df4e9 | ||
|
|
f4fbf2da75 | ||
|
|
288de8acaa | ||
|
|
750e6cff45 | ||
|
|
f49e94052d | ||
|
|
ceb642b745 | ||
|
|
ed1d886f01 | ||
|
|
db98d811b0 | ||
|
|
14ba11e1ab | ||
|
|
483bdddb18 | ||
|
|
d16fda3f16 | ||
|
|
c718b94937 | ||
|
|
bb21ab073f | ||
|
|
3ffe530461 | ||
|
|
95cf0d86bc | ||
|
|
9899a5fc56 | ||
|
|
952648185c | ||
|
|
69740493b7 | ||
|
|
994a28f31d | ||
|
|
b0a8a03f0a | ||
|
|
36e740f4cc | ||
|
|
a5874a3f80 | ||
|
|
0c95b978b4 | ||
|
|
e21f9dd1fb | ||
|
|
74a08b86ce | ||
|
|
8212d3c540 | ||
|
|
1ed6458b39 | ||
|
|
60c8f23a63 | ||
|
|
5e627641ea | ||
|
|
abc954aa47 | ||
|
|
3bfa27e682 | ||
|
|
4b7e122254 | ||
|
|
cfd3c13751 | ||
|
|
6a1ee2b894 | ||
|
|
cbec4a4e81 | ||
|
|
c7d8c1cab5 | ||
|
|
c39e1e649d | ||
|
|
95ab64ba77 | ||
|
|
1f8fcdb0f3 | ||
|
|
fb26a8ae50 | ||
|
|
42dbeaa1a5 | ||
|
|
3305f4c03a | ||
|
|
f5beec70c8 | ||
|
|
c2cd21c15e | ||
|
|
633e389275 | ||
|
|
f5aaf298e1 | ||
|
|
7db6136436 | ||
|
|
ce7296eebb | ||
|
|
c6226547f7 | ||
|
|
e7ec8f0489 | ||
|
|
dc466f238b | ||
|
|
f164656874 | ||
|
|
ef3c59152f | ||
|
|
14c6ead389 | ||
|
|
b0d33f3a85 | ||
|
|
066cc20ee6 | ||
|
|
0f51b5b1ce | ||
|
|
ebcf3e0119 | ||
|
|
6ee248d656 | ||
|
|
8a46b410f6 | ||
|
|
cd06cea153 | ||
|
|
8393d44c50 | ||
|
|
3e8ce80f80 | ||
|
|
80e033c91d | ||
|
|
a7dd441333 | ||
|
|
48efaa9fd7 | ||
|
|
92e831175f | ||
|
|
9b75e076b5 | ||
|
|
2c5d6d1651 | ||
|
|
c5cf116f33 | ||
|
|
66a4a9bce6 | ||
|
|
7e7ef64c79 | ||
|
|
9a31f53d4d | ||
|
|
60d6314262 | ||
|
|
eff7445804 | ||
|
|
2bfe21aef4 | ||
|
|
6ae0c7dcfc | ||
|
|
883ac1007a | ||
|
|
ff6747dab5 | ||
|
|
555e6f804c | ||
|
|
98c5c7990f | ||
|
|
27dcdb517d | ||
|
|
916d75d161 | ||
|
|
57bd16f4f5 | ||
|
|
444a1756a2 | ||
|
|
0c97c8f04f | ||
|
|
de81e81ecb | ||
|
|
40a7d5a112 | ||
|
|
7c06633a67 | ||
|
|
9abf611d63 | ||
|
|
565fe4c348 | ||
|
|
7b43403b1c | ||
|
|
9f25979b4c | ||
|
|
20f70b8b07 | ||
|
|
8fbf05acd4 | ||
|
|
6860855c71 | ||
|
|
b78660c685 | ||
|
|
6a40bbdcb5 | ||
|
|
5a1a4f5594 | ||
|
|
2ac7be4363 | ||
|
|
4ef5ab7a90 | ||
|
|
192308a6a3 | ||
|
|
c9ce111643 | ||
|
|
32fda231ad | ||
|
|
e4d4686717 | ||
|
|
ca6c6a1b6e | ||
|
|
806c4ce168 | ||
|
|
9d14597be0 | ||
|
|
dc68bb907c | ||
|
|
e4598c058a | ||
|
|
377562cdff | ||
|
|
969fcccc1f | ||
|
|
4c00764146 | ||
|
|
e98ee64695 | ||
|
|
51c7d0ed3e | ||
|
|
db93498ee6 | ||
|
|
b3af493758 | ||
|
|
7b9ebbbb5f | ||
|
|
ea735fc0a0 | ||
|
|
06227e97d0 | ||
|
|
dbc50b6d4f | ||
|
|
8b75ad1e7f | ||
|
|
8f3c740b57 | ||
|
|
24a6a0c23f | ||
|
|
267d72a76c | ||
|
|
021cecc216 | ||
|
|
4642480bbb | ||
|
|
4d48482d1e | ||
|
|
2054784a4a | ||
|
|
57d816b862 | ||
|
|
32bb66c42b | ||
|
|
e4d15e64b6 | ||
|
|
b11daeae7d | ||
|
|
8e78f8527e | ||
|
|
bc385744db | ||
|
|
02fd28edec | ||
|
|
95770ac104 | ||
|
|
2eeb762cd9 | ||
|
|
de5666d262 | ||
|
|
934d266880 | ||
|
|
b8fa234dbb | ||
|
|
bceea090ed | ||
|
|
8efda2ef56 | ||
|
|
f86cda6730 | ||
|
|
43f59a19fb | ||
|
|
eabaa94e06 | ||
|
|
20575a2b0f | ||
|
|
0096a778ac | ||
|
|
050f83e3bb | ||
|
|
32f7b4fbf6 | ||
|
|
265e8cdeaf | ||
|
|
fe5460e0c5 | ||
|
|
d4cad337fc | ||
|
|
0af6ecbd34 | ||
|
|
6466045363 | ||
|
|
67c7e503d9 | ||
|
|
01e06f0503 | ||
|
|
d6e155d874 | ||
|
|
5a2350bac1 | ||
|
|
2b97f9ac9e | ||
|
|
090b215179 | ||
|
|
32f483c333 | ||
|
|
655652f94f | ||
|
|
53b84c147c | ||
|
|
d8b4827601 | ||
|
|
5737acf759 | ||
|
|
58262e8604 | ||
|
|
b9e5eaf689 | ||
|
|
6d78cd59e9 | ||
|
|
aa00e33b6d | ||
|
|
4ef04c641e | ||
|
|
bfcccd8c33 | ||
|
|
f7d3c73c4a | ||
|
|
bfdece1c23 | ||
|
|
a68f450d36 | ||
|
|
d1df225e47 | ||
|
|
9c6ba4bc61 | ||
|
|
c01121d61a | ||
|
|
e0f0bb5523 | ||
|
|
a3b7742992 | ||
|
|
4b5fa6ddad | ||
|
|
b6aca82da6 | ||
|
|
8ee3bb4d58 | ||
|
|
46bea241e6 | ||
|
|
5e6d2881bc | ||
|
|
cd19876419 | ||
|
|
f82e103bc5 | ||
|
|
3ff4ac84c4 | ||
|
|
bf0c679a48 | ||
|
|
96c5bc164a | ||
|
|
73aead01b4 | ||
|
|
e19b3cc45d | ||
|
|
a1cab4ca7d | ||
|
|
4b89687e45 | ||
|
|
1c861fecfc | ||
|
|
a12c9f54c4 | ||
|
|
69d72e754f | ||
|
|
db3c5a3031 | ||
|
|
6327ed814a | ||
|
|
9fa32b5b6b | ||
|
|
663ae9f6bb | ||
|
|
70c73bc5d6 | ||
|
|
05d73552cf | ||
|
|
70384237c1 | ||
|
|
36e4a0dd32 | ||
|
|
3ef02d46c0 | ||
|
|
e6ce84aa14 | ||
|
|
348e34d52e | ||
|
|
2e6b3c0561 | ||
|
|
7280b48cdc | ||
|
|
2803f65479 | ||
|
|
3535688c3b | ||
|
|
9cff570c45 | ||
|
|
15c028e151 | ||
|
|
f0dc32b6e5 | ||
|
|
d423d18249 | ||
|
|
8a8e3c3fc8 | ||
|
|
111fc3c37d | ||
|
|
e9a5284dde | ||
|
|
b277f431c2 | ||
|
|
c8b8947b1f | ||
|
|
9a78d1585d | ||
|
|
09414a8834 | ||
|
|
1efa973507 | ||
|
|
e23cd6a856 | ||
|
|
743bb7a6ee | ||
|
|
086efe3c63 | ||
|
|
d751df70fd | ||
|
|
334d95c843 | ||
|
|
5ddac7866b | ||
|
|
a896fbbb90 | ||
|
|
a478699fe8 | ||
|
|
6387e50276 | ||
|
|
28c06de685 | ||
|
|
823573cea7 | ||
|
|
5d0f306bcc | ||
|
|
f30e922074 | ||
|
|
96ff0bffda | ||
|
|
d9b675fc8b | ||
|
|
104b7390da | ||
|
|
7b4456e73f | ||
|
|
86230d9bf3 | ||
|
|
1f8994ca8b | ||
|
|
f7b6f4ba19 | ||
|
|
74ea5969be | ||
|
|
c4718e7523 | ||
|
|
5de706e0fe | ||
|
|
77d06b4b03 | ||
|
|
b4d137375a | ||
|
|
0621ae7735 | ||
|
|
3a6a1f25a7 | ||
|
|
731dc64f44 | ||
|
|
d72b9cf646 | ||
|
|
0f0c4dc549 | ||
|
|
ea0820d881 | ||
|
|
312f20d2f1 | ||
|
|
f8289fa4be | ||
|
|
554209d644 | ||
|
|
4ce44034cb | ||
|
|
221b62ea57 | ||
|
|
0a5c265b12 | ||
|
|
9b55389538 | ||
|
|
60a8d6e986 | ||
|
|
d7523bcb57 | ||
|
|
562110fac4 | ||
|
|
d104265f04 | ||
|
|
4439685403 | ||
|
|
9feb72235a | ||
|
|
c372a498cc | ||
|
|
be35349350 | ||
|
|
771fd74978 | ||
|
|
3ba9ee7bf1 | ||
|
|
a0062a62e8 | ||
|
|
0f2bd77ebc | ||
|
|
744b368cc1 | ||
|
|
a03c4519c9 | ||
|
|
66327881d5 | ||
|
|
b93b14986e | ||
|
|
1ade4e9917 | ||
|
|
65f2ab6720 | ||
|
|
7d38ba12bd | ||
|
|
8128e85b6b | ||
|
|
3d99819be4 | ||
|
|
a2ca1618ea | ||
|
|
b244c56862 | ||
|
|
c931874bac | ||
|
|
1b168ac3d2 | ||
|
|
0fc123b249 | ||
|
|
c622804950 | ||
|
|
e093480a5b | ||
|
|
1498b72966 | ||
|
|
51e1c7cd50 | ||
|
|
40040b627f | ||
|
|
b752f8a357 | ||
|
|
5b93cf42b1 | ||
|
|
fa8145df9f | ||
|
|
5ddb6a7f99 | ||
|
|
8ad34357d3 | ||
|
|
81eb2684bf | ||
|
|
d2c0745efa | ||
|
|
3f2d38a86a | ||
|
|
4df4db05f4 | ||
|
|
6526fda960 | ||
|
|
32fdb257a3 | ||
|
|
9247cd874e | ||
|
|
4ceb42b88d | ||
|
|
3d99fc1708 | ||
|
|
656346bd04 | ||
|
|
6b5217ece2 | ||
|
|
0a2b388f6b | ||
|
|
25b3de84ec | ||
|
|
5c4e348078 | ||
|
|
2ac84ac8c4 | ||
|
|
f0249346b0 | ||
|
|
86651d7992 | ||
|
|
5cd5fb0071 | ||
|
|
e3bf046c30 | ||
|
|
d9af0a5547 | ||
|
|
ede7551856 | ||
|
|
a2030b6c27 | ||
|
|
9a951589dc | ||
|
|
c766cfad89 | ||
|
|
bd25572e08 | ||
|
|
4e00c950cc | ||
|
|
d932f0a204 | ||
|
|
08507272ed | ||
|
|
9c48e6578d | ||
|
|
db6c83eefd | ||
|
|
cc68cb944f | ||
|
|
a0d8d237d7 | ||
|
|
7d6d8e3a68 | ||
|
|
cc42f037c7 | ||
|
|
f4623ed028 | ||
|
|
bec467c703 | ||
|
|
bd09b1571a | ||
|
|
3ed6953d0b | ||
|
|
2fc6fb0a9a | ||
|
|
4b73bd907e | ||
|
|
a18486cc2e | ||
|
|
82f4e22f69 | ||
|
|
3497165ebd | ||
|
|
d5794a3dcb | ||
|
|
bd9ec53e7b | ||
|
|
5d98316c4e | ||
|
|
dcf2526aad | ||
|
|
85dd023c0e | ||
|
|
1fbcb44136 | ||
|
|
ab9042db24 | ||
|
|
b01487ac91 | ||
|
|
5278d7668c | ||
|
|
f7215fc2c5 | ||
|
|
d2e0413a48 | ||
|
|
0e165bc7e0 | ||
|
|
55e7f7ccb0 | ||
|
|
15061d3e0d | ||
|
|
36be44e7b5 | ||
|
|
1b4e62b823 | ||
|
|
12b07bb0ac | ||
|
|
f5666c9451 | ||
|
|
23cffce861 | ||
|
|
a1fb44f2a6 | ||
|
|
4d5dd8c8de | ||
|
|
1c492881e1 | ||
|
|
d310c53cce | ||
|
|
2289eebd91 | ||
|
|
e259bd62ab | ||
|
|
9f512705fa | ||
|
|
383fde488b | ||
|
|
b54350674c | ||
|
|
1e2b88496c | ||
|
|
919b567d46 | ||
|
|
da65c05c4f | ||
|
|
2f8ca6cf11 | ||
|
|
7121ff340a | ||
|
|
37f4d18d34 | ||
|
|
a8b424003c | ||
|
|
de4e677f18 | ||
|
|
bc632fd644 | ||
|
|
d386e1405c | ||
|
|
608742c2e2 | ||
|
|
71e91a541f | ||
|
|
ed18f10b94 | ||
|
|
4330a09793 | ||
|
|
16873201e9 | ||
|
|
2640c7b43c | ||
|
|
7824dddef7 | ||
|
|
7192480751 | ||
|
|
1da66f272c | ||
|
|
0ef1e347e7 | ||
|
|
bfba05c863 | ||
|
|
71f85350da | ||
|
|
8b54098299 | ||
|
|
356e68ca3e | ||
|
|
430c407106 | ||
|
|
31a7212a71 | ||
|
|
36a172308a | ||
|
|
e20df481a4 | ||
|
|
8fa0c95f5a | ||
|
|
4b4a859722 | ||
|
|
9cddedcdba | ||
|
|
01aebd90d5 | ||
|
|
c00105607c | ||
|
|
79ff12a1b0 | ||
|
|
e30c9c86ff | ||
|
|
c61e1e1c0e | ||
|
|
85bc5ce595 | ||
|
|
ef12e90ae7 | ||
|
|
6b0f6e4541 | ||
|
|
cdfd14e63f | ||
|
|
977058d219 | ||
|
|
c58fa586e1 | ||
|
|
1cd6fdeede | ||
|
|
09f25d78b7 | ||
|
|
82091c7951 | ||
|
|
1081211439 | ||
|
|
7e90fd45a7 | ||
|
|
08f4a424e6 | ||
|
|
063ee9c195 | ||
|
|
3f2bd657e1 | ||
|
|
903ef8e249 | ||
|
|
07b12fac3c | ||
|
|
56a2253535 | ||
|
|
752ded2f80 | ||
|
|
248d5e2fe5 | ||
|
|
158e981970 | ||
|
|
96d07c4b4e | ||
|
|
28afb8944f | ||
|
|
0d59ebfe55 | ||
|
|
bc38ecf6de | ||
|
|
755a52b78e | ||
|
|
4c008f1672 | ||
|
|
eb268fb856 | ||
|
|
b0e390ced1 | ||
|
|
741e8f625c | ||
|
|
17eb6dc4ce | ||
|
|
db997fe6f5 | ||
|
|
3b1fc2a27d | ||
|
|
0cbd965010 | ||
|
|
7d908b6545 | ||
|
|
83a29d6ed0 | ||
|
|
55ddc4ae75 | ||
|
|
088e361228 | ||
|
|
80012b8ee8 | ||
|
|
a61235a5d1 | ||
|
|
823242a6c2 | ||
|
|
0670a4dc3c |
@@ -5,7 +5,7 @@ data/log/*
|
||||
data/locks/*
|
||||
data/proxies/*
|
||||
data/migrations_template.txt
|
||||
data/GeoLite2-City.*
|
||||
data/GeoLite2-City*
|
||||
data/database.sqlite
|
||||
data/shlink-tests.db
|
||||
CHANGELOG.md
|
||||
|
||||
247
.github/workflows/ci.yml
vendored
247
.github/workflows/ci.yml
vendored
@@ -8,29 +8,12 @@ on:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
coverage: none
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: composer cs
|
||||
|
||||
static-analysis:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4']
|
||||
php-version: ['8.0']
|
||||
command: ['cs', 'stan', 'swagger:validate']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@@ -39,207 +22,90 @@ jobs:
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
extensions: openswoole-4.8.1
|
||||
coverage: none
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: composer stan
|
||||
- run: composer ${{ matrix.command }}
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-20.04
|
||||
continue-on-error: ${{ matrix.php-version == '8.0' }}
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
coverage: pcov
|
||||
ini-values: pcov.directory=module
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- run: composer test:unit:ci
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.php-version == '7.4' }}
|
||||
with:
|
||||
name: coverage-unit
|
||||
path: |
|
||||
build/coverage-unit
|
||||
build/coverage-unit.cov
|
||||
|
||||
db-tests-sqlite:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
coverage: pcov
|
||||
ini-values: pcov.directory=module
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- run: composer test:db:sqlite:ci
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.php-version == '7.4' }}
|
||||
with:
|
||||
name: coverage-db
|
||||
path: |
|
||||
build/coverage-db
|
||||
build/coverage-db.cov
|
||||
|
||||
db-tests-mysql:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Start database server
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
coverage: none
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- run: composer test:db:mysql
|
||||
|
||||
db-tests-maria:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Start database server
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
coverage: none
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- run: composer test:db:maria
|
||||
|
||||
db-tests-postgres:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
php-version: ['8.0', '8.1']
|
||||
test-group: ['unit', 'api']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Start database server
|
||||
if: ${{ matrix.test-group == 'api' }}
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
coverage: none
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- run: composer test:db:postgres
|
||||
extensions: openswoole-4.8.1
|
||||
coverage: pcov
|
||||
ini-values: pcov.directory=module
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: composer test:${{ matrix.test-group }}:ci
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.php-version == '8.0' }}
|
||||
with:
|
||||
name: coverage-${{ matrix.test-group }}
|
||||
path: |
|
||||
build/coverage-${{ matrix.test-group }}
|
||||
build/coverage-${{ matrix.test-group }}.cov
|
||||
|
||||
db-tests-ms:
|
||||
db-tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
php-version: ['8.0', '8.1']
|
||||
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
|
||||
env:
|
||||
LC_ALL: C
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install MSSQL ODBC
|
||||
if: ${{ matrix.platform == 'ms' }}
|
||||
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
||||
- name: Start database server
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_ms
|
||||
if: ${{ matrix.platform != 'sqlite:ci' }}
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ matrix.platform }}
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9, pdo_sqlsrv-5.9.0beta2
|
||||
coverage: none
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- name: Create test database
|
||||
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||
- run: composer test:db:ms
|
||||
|
||||
api-tests:
|
||||
runs-on: ubuntu-20.04
|
||||
continue-on-error: ${{ matrix.php-version == '8.0' }}
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Start database server
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
extensions: openswoole-4.8.1, pdo_sqlsrv-5.10.0beta2
|
||||
coverage: pcov
|
||||
ini-values: pcov.directory=module
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- run: bin/test/run-api-tests.sh
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.php-version == '7.4' }}
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- name: Create test database
|
||||
if: ${{ matrix.platform == 'ms' }}
|
||||
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||
- name: Run tests
|
||||
run: composer test:db:${{ matrix.platform }}
|
||||
- name: Upload code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.php-version == '8.0' && matrix.platform == 'sqlite:ci' }}
|
||||
with:
|
||||
name: coverage-api
|
||||
name: coverage-db
|
||||
path: |
|
||||
build/coverage-api
|
||||
build/coverage-api.cov
|
||||
build/coverage-db
|
||||
build/coverage-db.cov
|
||||
|
||||
mutation-tests:
|
||||
needs:
|
||||
- unit-tests
|
||||
- db-tests-sqlite
|
||||
- api-tests
|
||||
- tests
|
||||
- db-tests
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.0']
|
||||
php-version: ['8.0', '8.1']
|
||||
test-group: ['unit', 'db', 'api']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@@ -248,27 +114,28 @@ jobs:
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
extensions: openswoole-4.8.1
|
||||
coverage: pcov
|
||||
ini-values: pcov.directory=module
|
||||
- if: ${{ matrix.php-version == '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
||||
- if: ${{ matrix.php-version != '8.0' }}
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: build
|
||||
- run: composer infect:ci
|
||||
- if: ${{ matrix.test-group == 'unit' }}
|
||||
run: composer infect:ci:unit
|
||||
env:
|
||||
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
|
||||
- if: ${{ matrix.test-group != 'unit' }}
|
||||
run: composer infect:ci:${{ matrix.test-group }}
|
||||
|
||||
upload-coverage:
|
||||
needs:
|
||||
- unit-tests
|
||||
- db-tests-sqlite
|
||||
- api-tests
|
||||
- tests
|
||||
- db-tests
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4']
|
||||
php-version: ['8.0']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@@ -309,6 +176,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- uses: marceloprado/has-changed-path@v1
|
||||
id: changed-dockerfile
|
||||
with:
|
||||
|
||||
2
.github/workflows/docker-image-build.yml
vendored
2
.github/workflows/docker-image-build.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
45
.github/workflows/publish-release.yml
vendored
45
.github/workflows/publish-release.yml
vendored
@@ -7,24 +7,55 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0', '8.1']
|
||||
swoole: ['yes', 'no']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Use PHP 7.4
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.4' # Publish release with lowest supported PHP version
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: swoole-4.5.9
|
||||
- name: Generate release assets
|
||||
extensions: openswoole-4.8.1
|
||||
- if: ${{ matrix.swoole == 'yes' }}
|
||||
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||
- if: ${{ matrix.swoole == 'no' }}
|
||||
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||
path: build
|
||||
|
||||
publish:
|
||||
needs: ['build']
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: build
|
||||
- name: Publish release with assets
|
||||
uses: docker://antonyurchenko/git-release:latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ALLOW_TAG_PREFIX: "true"
|
||||
ALLOW_EMPTY_CHANGELOG: "true"
|
||||
with:
|
||||
args: |
|
||||
build/shlink_*_dist.zip
|
||||
build/*/shlink*_dist.zip
|
||||
|
||||
delete-artifacts:
|
||||
needs: ['publish']
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: [ '8.0' ]
|
||||
swoole: [ 'yes', 'no' ]
|
||||
steps:
|
||||
- uses: geekyeggo/delete-artifact@v1
|
||||
with:
|
||||
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||
|
||||
40
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
40
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Publish swagger spec
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Determine version
|
||||
id: determine_version
|
||||
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
|
||||
shell: bash
|
||||
- name: Use PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer
|
||||
extensions: openswoole-4.8.1
|
||||
coverage: none
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: composer swagger:inline
|
||||
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
||||
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/oas.json
|
||||
- name: Publish spec
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.7
|
||||
with:
|
||||
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
||||
repository-name: 'shlinkio/shlink-open-api-specs'
|
||||
branch: main
|
||||
folder: ${{ steps.determine_version.outputs.version }}
|
||||
target-folder: specs/${{ steps.determine_version.outputs.version }}
|
||||
clean: false
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,8 +6,9 @@ composer.phar
|
||||
vendor/
|
||||
data/database.sqlite
|
||||
data/shlink-tests.db
|
||||
data/GeoLite2-City.mmdb
|
||||
data/GeoLite2-City.mmdb.*
|
||||
data/GeoLite2-City.*
|
||||
docs/swagger-ui*
|
||||
docs/mercure.html
|
||||
docker-compose.override.yml
|
||||
.phpunit.result.cache
|
||||
docs/swagger/swagger-inlined.json
|
||||
|
||||
365
CHANGELOG.md
365
CHANGELOG.md
@@ -4,6 +4,371 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
|
||||
|
||||
## [2.10.0] - 2021-12-12
|
||||
### Added
|
||||
* [#1163](https://github.com/shlinkio/shlink/issues/1163) Allowed setting not-found redirects for default domain in the same way it's done for any other domain.
|
||||
|
||||
This implies a few non-breaking changes:
|
||||
|
||||
* The domains list no longer has the values of `INVALID_SHORT_URL_REDIRECT_TO`, `REGULAR_404_REDIRECT_TO` and `BASE_URL_REDIRECT_TO` on the default domain redirects.
|
||||
* The `GET /domains` endpoint includes a new `defaultRedirects` property in the response, with the default redirects set via config or env vars.
|
||||
* The `INVALID_SHORT_URL_REDIRECT_TO`, `REGULAR_404_REDIRECT_TO` and `BASE_URL_REDIRECT_TO` env vars are now deprecated, and should be replaced by `DEFAULT_INVALID_SHORT_URL_REDIRECT`, `DEFAULT_REGULAR_404_REDIRECT` and `DEFAULT_BASE_URL_REDIRECT` respectively. Deprecated ones will continue to work until v3.0.0, where they will be removed.
|
||||
|
||||
* [#868](https://github.com/shlinkio/shlink/issues/868) Added support to publish real-time updates in a RabbitMQ server.
|
||||
|
||||
Shlink will create new exchanges and queues for every topic documented in the [Async API spec](https://api-spec.shlink.io/async-api/), meaning, you will have one queue for orphan visits, one for regular visits, and one queue for every short URL with its visits.
|
||||
|
||||
The RabbitMQ server config can be provided via installer config options, or via environment variables.
|
||||
|
||||
* [#1204](https://github.com/shlinkio/shlink/issues/1204) Added support for `openswoole` and migrated official docker image to `openswoole`.
|
||||
* [#1242](https://github.com/shlinkio/shlink/issues/1242) Added support to import urls and visits from YOURLS.
|
||||
|
||||
In order to do it, you need to first install this [dedicated plugin](https://slnk.to/yourls-import) in YOURLS, and then run the `short-url:import yourls` command, as with any other source.
|
||||
|
||||
* [#1235](https://github.com/shlinkio/shlink/issues/1235) Added support to disable rounding QR codes block sizing via config option, env var or query param.
|
||||
* [#1188](https://github.com/shlinkio/shlink/issues/1188) Added support for PHP 8.1.
|
||||
|
||||
The official docker image has also been updated to use PHP 8.1 by default.
|
||||
|
||||
### Changed
|
||||
* [#844](https://github.com/shlinkio/shlink/issues/844) Added mutation checks to API tests.
|
||||
* [#1218](https://github.com/shlinkio/shlink/issues/1218) Updated to symfony/mercure 0.6.
|
||||
* [#1223](https://github.com/shlinkio/shlink/issues/1223) Updated to phpstan 1.0.
|
||||
* [#1258](https://github.com/shlinkio/shlink/issues/1258) Updated to Symfony 6 components, except symfony/console.
|
||||
* Added `domain` field to `DeleteShortUrlException` exception.
|
||||
|
||||
### Deprecated
|
||||
* [#1260](https://github.com/shlinkio/shlink/issues/1260) Deprecated `USE_HTTPS` env var that was added in previous release, in favor of the new `IS_HTTPS_ENABLED`.
|
||||
|
||||
The old one proved to be confusing and misleading, making people think it was used to actually enable HTTPS transparently, instead of its actual purpose, which is just telling Shlink it is being served with HTTPS.
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1206](https://github.com/shlinkio/shlink/issues/1206) Fixed debugging of the docker image, so that it does not run the commands with `-q` when the `SHELL_VERBOSITY` env var has been provided.
|
||||
* [#1254](https://github.com/shlinkio/shlink/issues/1254) Fixed examples in swagger docs.
|
||||
|
||||
|
||||
## [2.9.3] - 2021-11-15
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1232](https://github.com/shlinkio/shlink/issues/1232) Solved potential SQL injection by enforcing `doctrine/dbal` 3.1.4.
|
||||
|
||||
|
||||
## [2.9.2] - 2021-10-23
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1210](https://github.com/shlinkio/shlink/issues/1210) Fixed real time updates not being notified due to an incorrect handling of db transactions on multi-process tasks.
|
||||
* [#1211](https://github.com/shlinkio/shlink/issues/1211) Fixed `There is no active transaction` error when running migrations in MySQL/Mariadb after updating to doctrine-migrations 3.3.
|
||||
* [#1197](https://github.com/shlinkio/shlink/issues/1197) Fixed amount of task workers provided via config option or env var not being validated to ensure enough workers to process all parallel tasks.
|
||||
|
||||
|
||||
## [2.9.1] - 2021-10-11
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1201](https://github.com/shlinkio/shlink/issues/1201) Fixed crash when using the new `USE_HTTPS`, as it's boolean raw value was being used instead of resolving "https" or "http".
|
||||
|
||||
|
||||
## [2.9.0] - 2021-10-10
|
||||
### Added
|
||||
* [#1015](https://github.com/shlinkio/shlink/issues/1015) Shlink now accepts configuration via env vars even when not using docker.
|
||||
|
||||
The config generated with the installing tool still has precedence over the env vars, so it cannot be combined. Either you use the tool, or use env vars.
|
||||
|
||||
* [#1149](https://github.com/shlinkio/shlink/issues/1149) Allowed to set custom defaults for the QR codes.
|
||||
* [#1112](https://github.com/shlinkio/shlink/issues/1112) Added new option to define if the query string should be forwarded on a per-short URL basis.
|
||||
|
||||
The new `forwardQuery=true|false` param can be provided during short URL creation or edition, via REST API or CLI command, allowing to override the default behavior which makes the query string to always be forwarded.
|
||||
|
||||
* [#1105](https://github.com/shlinkio/shlink/issues/1105) Added support to define placeholders on not-found redirects, so that the redirected URL receives the originally visited path and/or domain.
|
||||
|
||||
Currently, `{DOMAIN}` and `{ORIGINAL_PATH}` placeholders are supported, and they can be used both in the redirected URL's path or query.
|
||||
|
||||
When they are used in the query, the values are URL encoded.
|
||||
|
||||
* [#1119](https://github.com/shlinkio/shlink/issues/1119) Added support to provide redis sentinel when using redis cache.
|
||||
* [#1016](https://github.com/shlinkio/shlink/issues/1016) Added new option to send orphan visits to webhooks, via `NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS` env var or installer tool.
|
||||
|
||||
The option is disabled by default, as the payload is backwards incompatible. You will need to adapt your webhooks to treat the `shortUrl` property as optional before enabling this option.
|
||||
|
||||
* [#1104](https://github.com/shlinkio/shlink/issues/1104) Added ability to disable tracking based on IP addresses.
|
||||
|
||||
IP addresses can be provided in the form of fixed addresses, CIDR blocks, or wildcard patterns (192.168.*.*).
|
||||
|
||||
### Changed
|
||||
* [#1142](https://github.com/shlinkio/shlink/issues/1142) Replaced `doctrine/cache` package with `symfony/cache`.
|
||||
* [#1157](https://github.com/shlinkio/shlink/issues/1157) All routes now support CORS, not only rest ones.
|
||||
* [#1144](https://github.com/shlinkio/shlink/issues/1144) Added experimental builds under PHP 8.1.
|
||||
|
||||
### Deprecated
|
||||
* [#1164](https://github.com/shlinkio/shlink/issues/1164) Deprecated `SHORT_DOMAIN_HOST` and `SHORT_DOMAIN_SCHEMA` env vars. Use `DEFAULT_DOMAIN` and `USE_HTTPS=true|false` instead.
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1165](https://github.com/shlinkio/shlink/issues/1165) Fixed warning displayed when trying to locate visits and there are none pending.
|
||||
* [#1172](https://github.com/shlinkio/shlink/pull/1172) Removed unneeded explicitly defined volumes in docker image.
|
||||
|
||||
|
||||
## [2.8.1] - 2021-08-15
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1155](https://github.com/shlinkio/shlink/issues/1155) Fixed numeric query params in long URLs being replaced by `0`.
|
||||
|
||||
|
||||
## [2.8.0] - 2021-08-04
|
||||
### Added
|
||||
* [#1089](https://github.com/shlinkio/shlink/issues/1089) Added new `ENABLE_PERIODIC_VISIT_LOCATE` env var to docker image which schedules the `visit:locate` command every hour when provided with value `true`.
|
||||
* [#1082](https://github.com/shlinkio/shlink/issues/1082) Added support for error correction level on QR codes.
|
||||
|
||||
Now, when calling the `GET /{shorCode}/qr-code` URL, you can pass the `errorCorrection` query param with values `L` for Low, `M` for Medium, `Q` for Quartile or `H` for High.
|
||||
|
||||
* [#1080](https://github.com/shlinkio/shlink/issues/1080) Added support to redirect to URLs as soon as the path starts with a valid short code, appending the rest of the path to the redirected long URL.
|
||||
|
||||
With this, if you have the `https://example.com/abc123` short URL redirecting to `https://www.twitter.com`, a visit to `https://example.com/abc123/shlinkio` will take you to `https://www.twitter.com/shlinkio`.
|
||||
|
||||
This behavior needs to be actively opted in, via installer config options or env vars.
|
||||
|
||||
* [#943](https://github.com/shlinkio/shlink/issues/943) Added support to define different "not-found" redirects for every domain handled by Shlink.
|
||||
|
||||
Shlink will continue to allow defining the default values via env vars or config, but afterwards, you can use the `domain:redirects` command or the `PATCH /domains/redirects` REST endpoint to define specific values for every single domain.
|
||||
|
||||
### Changed
|
||||
* [#1118](https://github.com/shlinkio/shlink/issues/1118) Increased phpstan required level to 8.
|
||||
* [#1127](https://github.com/shlinkio/shlink/issues/1127) Updated to infection 0.24.
|
||||
* [#1139](https://github.com/shlinkio/shlink/issues/1139) Updated project dependencies, including base docker image to use PHP 8.0.9 and Alpine 3.14.
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* [#1046](https://github.com/shlinkio/shlink/issues/1046) Dropped support for PHP 7.4.
|
||||
|
||||
### Fixed
|
||||
* [#1098](https://github.com/shlinkio/shlink/issues/1098) Fixed errors when using Redis for caching, caused by some third party lib bug that was fixed on dependencies update.
|
||||
|
||||
|
||||
## [2.7.3] - 2021-08-02
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1135](https://github.com/shlinkio/shlink/issues/1135) Fixed error when importing short URLs with no visits from another Shlink instance.
|
||||
* [#1136](https://github.com/shlinkio/shlink/issues/1136) Fixed error when fetching tag/short-url/orphan visits for a page lower than 1.
|
||||
|
||||
|
||||
## [2.7.2] - 2021-07-30
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1128](https://github.com/shlinkio/shlink/issues/1128) Increased memory limit reserved for the docker image, preventing it from crashing on GeoLite db download.
|
||||
|
||||
|
||||
## [2.7.1] - 2021-05-30
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1100](https://github.com/shlinkio/shlink/issues/1100) Fixed Shlink trying to download GeoLite2 db files even when tracking has been disabled.
|
||||
|
||||
|
||||
## [2.7.0] - 2021-05-23
|
||||
### Added
|
||||
* [#1044](https://github.com/shlinkio/shlink/issues/1044) Added ability to set names on API keys, which helps to identify them when the list grows.
|
||||
* [#819](https://github.com/shlinkio/shlink/issues/819) Visits are now always located in real time, even when not using swoole.
|
||||
|
||||
The only side effect is that a GeoLite2 db file is now installed when the docker image starts or during shlink installation or update.
|
||||
|
||||
Also, when using swoole, the file is now updated **after** tracking a visit, which means it will not apply until the next one.
|
||||
|
||||
* [#1059](https://github.com/shlinkio/shlink/issues/1059) Added ability to optionally display author API key and its name when listing short URLs from the command line.
|
||||
* [#1066](https://github.com/shlinkio/shlink/issues/1066) Added support to import short URLs and their visits from another Shlink instance using its API.
|
||||
* [#898](https://github.com/shlinkio/shlink/issues/898) Improved tracking granularity, allowing to disable visits tracking completely, or just parts of it.
|
||||
|
||||
In order to achieve it, Shlink now supports 4 new tracking-related options, that can be customized via env vars for docker, or via installer:
|
||||
|
||||
* `disable_tracking`: If true, visits will not be tracked at all.
|
||||
* `disable_ip_tracking`: If true, visits will be tracked, but neither the IP address, nor the location will be resolved.
|
||||
* `disable_referrer_tracking`: If true, the referrer will not be tracked.
|
||||
* `disable_ua_tracking`: If true, the user agent will not be tracked.
|
||||
|
||||
* [#955](https://github.com/shlinkio/shlink/issues/955) Added new option to set short URLs as crawlable, making them be listed in the robots.txt as Allowed.
|
||||
* [#900](https://github.com/shlinkio/shlink/issues/900) Shlink now tries to detect if the visit is coming from a potential bot or crawler, and allows to exclude those visits from visits lists if desired.
|
||||
|
||||
### Changed
|
||||
* [#1036](https://github.com/shlinkio/shlink/issues/1036) Updated to `happyr/doctrine-specification` 2.0.
|
||||
* [#1039](https://github.com/shlinkio/shlink/issues/1039) Updated to `endroid/qr-code` 4.0.
|
||||
* [#1008](https://github.com/shlinkio/shlink/issues/1008) Ensured all logs are sent to the filesystem while running API tests, which helps debugging the reason for tests to fail.
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1041](https://github.com/shlinkio/shlink/issues/1041) Ensured the default value for the version while building the docker image is `latest`.
|
||||
* [#1067](https://github.com/shlinkio/shlink/issues/1067) Fixed exception when persisting multiple short URLs in one batch which include the same new tags/domains. This can potentially happen when importing URLs.
|
||||
|
||||
|
||||
## [2.6.2] - 2021-03-12
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1047](https://github.com/shlinkio/shlink/issues/1047) Fixed error in migrations when doing a fresh installation using PHP8 and MySQL/Mariadb databases.
|
||||
|
||||
|
||||
## [2.6.1] - 2021-02-22
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* [#1026](https://github.com/shlinkio/shlink/issues/1026) Removed non-inclusive terms from source code.
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#1024](https://github.com/shlinkio/shlink/issues/1024) Fixed migration that is incorrectly skipped due to the wrong condition being used to check it.
|
||||
* [#1031](https://github.com/shlinkio/shlink/issues/1031) Fixed shortening of twitter URLs with URL validation enabled.
|
||||
* [#1034](https://github.com/shlinkio/shlink/issues/1034) Fixed warning displayed when shlink is stopped while running it with swoole.
|
||||
|
||||
|
||||
## [2.6.0] - 2021-02-13
|
||||
### Added
|
||||
* [#856](https://github.com/shlinkio/shlink/issues/856) Added PHP 8.0 support.
|
||||
* [#941](https://github.com/shlinkio/shlink/issues/941) Added support to provide a title for every short URL.
|
||||
|
||||
The title can also be automatically resolved from the long URL, when no title was explicitly provided, but this option needs to be opted in.
|
||||
|
||||
* [#913](https://github.com/shlinkio/shlink/issues/913) Added support to import short URLs from a standard CSV file.
|
||||
|
||||
The file requires the `Long URL` and `Short code` columns, and it also accepts the optional `title`, `domain` and `tags` columns.
|
||||
|
||||
* [#1000](https://github.com/shlinkio/shlink/issues/1000) Added support to provide a `margin` query param when generating some URL's QR code.
|
||||
* [#675](https://github.com/shlinkio/shlink/issues/675) Added ability to track visits to the base URL, invalid short URLs or any other "not found" URL, as known as orphan visits.
|
||||
|
||||
This behavior is enabled by default, but you can opt out via env vars or config options.
|
||||
|
||||
This new orphan visits can be consumed in these ways:
|
||||
|
||||
* The `https://shlink.io/new-orphan-visit` mercure topic, which gets notified when an orphan visit occurs.
|
||||
* The `GET /visits/orphan` REST endpoint, which behaves like the short URL visits and tags visits endpoints, but returns only orphan visits.
|
||||
|
||||
### Changed
|
||||
* [#977](https://github.com/shlinkio/shlink/issues/977) Migrated from `laminas/laminas-paginator` to `pagerfanta/core` to handle pagination.
|
||||
* [#986](https://github.com/shlinkio/shlink/issues/986) Updated official docker image to use PHP 8.
|
||||
* [#1010](https://github.com/shlinkio/shlink/issues/1010) Increased timeout for database commands to 10 minutes.
|
||||
* [#874](https://github.com/shlinkio/shlink/issues/874) Changed how dist files are generated. Now there will be two for every supported PHP version, with and without support for swoole.
|
||||
|
||||
The dist files will have been built under the same PHP version they are meant to be run under, ensuring resolved dependencies are the proper ones.
|
||||
|
||||
### Deprecated
|
||||
* [#959](https://github.com/shlinkio/shlink/issues/959) Deprecated all command flags using camelCase format (like `--expirationDate`), adding kebab-case replacements for all of them (like `--expiration-date`).
|
||||
|
||||
All the existing camelCase flags will continue working for now, but will be removed in Shlink 3.0.0
|
||||
|
||||
* [#862](https://github.com/shlinkio/shlink/issues/862) Deprecated the endpoint to edit tags for a short URL (`PUT /short-urls/{shortCode}/tags`).
|
||||
|
||||
The short URL edition endpoint (`PATCH /short-urls/{shortCode}`) now supports setting the tags too. Use it instead.
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [#988](https://github.com/shlinkio/shlink/issues/988) Fixed serving zero-byte static files in apache and apache-compatible web servers.
|
||||
* [#990](https://github.com/shlinkio/shlink/issues/990) Fixed short URLs not properly composed in REST API endpoints when both custom domain and custom base path are used.
|
||||
* [#1002](https://github.com/shlinkio/shlink/issues/1002) Fixed weird behavior in which GeoLite2 metadata's `buildEpoch` is parsed as string instead of int.
|
||||
* [#851](https://github.com/shlinkio/shlink/issues/851) Fixed error when trying to schedule swoole tasks in ARM architectures (like raspberry).
|
||||
|
||||
|
||||
## [2.5.2] - 2021-01-24
|
||||
### Added
|
||||
* [#965](https://github.com/shlinkio/shlink/issues/965) Added docs section for Architectural Decision Records, including the one for API key roles.
|
||||
|
||||
@@ -33,7 +33,7 @@ Then you will have to follow these steps:
|
||||
|
||||
Once you finish this, you will have the project exposed in ports `8000` through nginx+php-fpm and `8080` through swoole.
|
||||
|
||||
> Note: The `indocker` shell script is a helper used to run commands inside the main docker container.
|
||||
> Note: The `indocker` shell script is a helper tool used to run commands inside the main docker container.
|
||||
|
||||
## Project structure
|
||||
|
||||
@@ -88,19 +88,21 @@ In order to ensure stability and no regressions are introduced while developing
|
||||
|
||||
* **Unit tests**: These are the simplest to run, and usually test individual pieces of code, replacing any external dependency by mocks.
|
||||
|
||||
The code coverage of unit tests is pretty high, and only entity repositories are excluded because of their nature.
|
||||
The code coverage of unit tests is pretty high, and only components which work closer to the database, like entity repositories, are excluded because of their nature.
|
||||
|
||||
* **Database tests**: These are integration tests that run against a real database, and only cover entity repositories.
|
||||
* **Database tests**: These are integration tests that run against a real database, and only cover components which work closer to the database.
|
||||
|
||||
Its purpose is to verify all the database queries behave as expected and return what's expected.
|
||||
|
||||
The project provides some tooling to run them against any of the supported database engines.
|
||||
|
||||
* **API tests**: These are E2E tests that spin up an instance of the app and test it from the outside, by interacting with the REST API.
|
||||
* **API tests**: These are E2E tests that spin up an instance of the app with swoole, and test it from the outside by interacting with the REST API.
|
||||
|
||||
These are the best tests to catch regressions, and to verify everything interacts as expected.
|
||||
These are the best tests to catch regressions, and to verify everything behaves as expected.
|
||||
|
||||
They use MySQL as the database engine, and include some fixtures that ensure the same data exists at the beginning of the execution.
|
||||
They use Postgres as the database engine, and include some fixtures that ensure the same data exists at the beginning of the execution.
|
||||
|
||||
Since the app instance is run on a process different from the one running the tests, when a test fails it might not be obvious why. To help debugging that, the app will dump all its logs inside `data/log/api-tests`, where you will find the `shlink.log` and `access.log` files.
|
||||
|
||||
* **CLI tests**: *TBD. Once included, its purpose will be the same as API tests, but running through the command line*
|
||||
|
||||
@@ -114,13 +116,14 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
|
||||
* Run `./indocker composer test:unit` to run the unit tests.
|
||||
* Run `./indocker composer test:db` to run the database integration tests.
|
||||
|
||||
This command runs the same test suite against all supported database engines. If you just want to run one of them, you can add one of `:sqlite`, `:mysql`, `:maria`, `:postgres`, `:mssql` at the end of the command.
|
||||
This command runs the same test suite against all supported database engines in parallel. If you just want to run one of them, you can add one of `:sqlite`, `:mysql`, `:maria`, `:postgres`, `:mssql` at the end of the command.
|
||||
|
||||
For example, `test:db:postgres`.
|
||||
|
||||
* Run `./indocker composer test:api` to run API E2E tests. For these, the MySQL database engine is used.
|
||||
* Run `./indocker composer infect:test` ti run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
||||
* Run `./indocker composer test:api` to run API E2E tests. For these, the Postgres database engine is used.
|
||||
* Run `./indocker composer infect:test` to run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
||||
* Run `./indocker composer ci` to run all previous commands together. This command is run during the project's continuous integration.
|
||||
* Run `./indocker composer ci:parallel` to do the same as in previous case, but parallelizing non-conflicting tasks as much as possible.
|
||||
|
||||
> Note: Due to some limitations in the tooling used by shlink, the testing databases need to exist beforehand, both for db and api tests (except sqlite).
|
||||
>
|
||||
@@ -130,11 +133,15 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
|
||||
|
||||
## Pull request process
|
||||
|
||||
In order to provide pull requests to this project, you should always start by creating a new branch, where you will make all desired changes.
|
||||
**Important!**: Before starting to work on a pull request, make sure you always [open an issue](https://github.com/shlinkio/shlink/issues/new/choose) first.
|
||||
|
||||
This is important because any contribution needs to be discussed first. Maybe there's someone else already working on something similar, or there are other considerations to have in mind.
|
||||
|
||||
Once everything is clear, to provide a pull request to this project, you should always start by creating a new branch, where you will make all desired changes.
|
||||
|
||||
The base branch should always be `develop`, and the target branch for the pull request should also be `develop`.
|
||||
|
||||
Before your branch can be merged, all the checks described in [Running code checks](#running-code-checks) have to be passing. You can verify that manually by running `./indocker composer ci`, or wait for the build to be run automatically after the pull request is created.
|
||||
Before your branch can be merged, all the checks described in [Running code checks](#running-code-checks) have to be passing. You can verify that manually by running `./indocker composer ci:parallel`, or wait for the build to be run automatically after the pull request is created.
|
||||
|
||||
## Architectural Decision Records
|
||||
|
||||
|
||||
41
Dockerfile
41
Dockerfile
@@ -1,16 +1,18 @@
|
||||
FROM php:7.4.11-alpine3.12 as base
|
||||
FROM php:8.1.0-alpine3.15 as base
|
||||
|
||||
ARG SHLINK_VERSION=2.4.0
|
||||
ARG SHLINK_VERSION=latest
|
||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||
ENV SWOOLE_VERSION 4.5.9
|
||||
ENV OPENSWOOLE_VERSION 4.8.1
|
||||
ENV PDO_SQLSRV_VERSION 5.10.0beta2
|
||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
||||
ENV LC_ALL "C"
|
||||
|
||||
WORKDIR /etc/shlink
|
||||
|
||||
# Install required PHP extensions
|
||||
RUN \
|
||||
# Install mysql and calendar
|
||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar && \
|
||||
# Install extensions with no extra dependencies
|
||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar sockets bcmath && \
|
||||
# Install sqlite
|
||||
apk add --no-cache sqlite-libs sqlite-dev && \
|
||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||
@@ -29,19 +31,19 @@ RUN \
|
||||
|
||||
# Install sqlsrv driver
|
||||
RUN if [ $(uname -m) == "x86_64" ]; then \
|
||||
wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
||||
pecl install pdo_sqlsrv && \
|
||||
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \
|
||||
docker-php-ext-enable pdo_sqlsrv && \
|
||||
apk del .phpize-deps && \
|
||||
rm msodbcsql17_17.5.1.1-1_amd64.apk ; \
|
||||
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
|
||||
fi
|
||||
|
||||
# Install swoole
|
||||
# Install openswoole
|
||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
|
||||
pecl install swoole-${SWOOLE_VERSION} && \
|
||||
docker-php-ext-enable swoole && \
|
||||
pecl install openswoole-${OPENSWOOLE_VERSION} && \
|
||||
docker-php-ext-enable openswoole && \
|
||||
apk del .phpize-deps
|
||||
|
||||
|
||||
@@ -63,15 +65,22 @@ LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
||||
COPY --from=builder /etc/shlink .
|
||||
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink
|
||||
|
||||
# Expose default swoole port
|
||||
# Expose default openswoole 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
|
||||
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
||||
|
||||
# Change the ownership of /etc/shlink/data to be writable, then change the user to non-root
|
||||
# FIXME Disabled for now, as it conflicts with ENABLE_PERIODIC_VISIT_LOCATE, which is used to configure a cron as root.
|
||||
# Ref: https://github.com/shlinkio/shlink/issues/1132
|
||||
#RUN chown 1001 /etc/shlink/data
|
||||
#RUN chown 1001 /etc/shlink/data/locks
|
||||
#RUN chown 1001 /etc/shlink/data/proxies
|
||||
#RUN chown 1001 /etc/shlink/data/cache
|
||||
#RUN chown 1001 /etc/shlink/data/log
|
||||
#USER 1001
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Alejandro Celaya
|
||||
Copyright (c) 2016-2021 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
|
||||
|
||||
13
README.md
13
README.md
@@ -2,6 +2,7 @@
|
||||
|
||||
[](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Continuous+integration%22)
|
||||
[](https://app.codecov.io/gh/shlinkio/shlink)
|
||||
[](https://dashboard.stryker-mutator.io/reports/github.com/shlinkio/shlink/develop)
|
||||
[](https://packagist.org/packages/shlinkio/shlink)
|
||||
[](https://hub.docker.com/r/shlinkio/shlink/)
|
||||
[](https://github.com/shlinkio/shlink/blob/main/LICENSE)
|
||||
@@ -33,9 +34,11 @@ The idea is that you can just generate a container using the image and provide t
|
||||
|
||||
First, make sure the host where you are going to run shlink fulfills these requirements:
|
||||
|
||||
* PHP 7.4 with JSON, curl, PDO, intl and gd extensions enabled (PHP 8.0 support is coming).
|
||||
* apcu extension is recommended if you don't plan to use swoole.
|
||||
* PHP 8.0 or 8.1
|
||||
* The next PHP extensions: json, curl, pdo, intl, gd and gmp.
|
||||
* apcu extension is recommended if you don't plan to use swoole or openswoole.
|
||||
* xml extension is required if you want to generate QR codes in svg format.
|
||||
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||
* MySQL, MariaDB, PostgreSQL, Microsoft SQL Server or SQLite.
|
||||
* The web server of your choice with PHP integration (Apache or Nginx recommended).
|
||||
|
||||
@@ -47,7 +50,7 @@ In order to run Shlink, you will need a built version of the project. There are
|
||||
|
||||
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
|
||||
|
||||
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink_x.x.x_dist.zip` file you will find there.
|
||||
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink*_dist.zip` file that suits your needs. You will find one for every supported PHP version and with/without swoole/openswoole integration.
|
||||
|
||||
Finally, decompress the file in the location of your choice.
|
||||
|
||||
@@ -57,9 +60,9 @@ In order to run Shlink, you will need a built version of the project. There are
|
||||
|
||||
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
||||
* 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).
|
||||
* Run `./build.sh 1.0.0`, replacing the version with the version number you are going to build (the version number is used as part of the generated dist file name, and to set the value returned when running `shlink -V` from the command line).
|
||||
|
||||
After that, you will have a `shlink_x.x.x_dist.zip` dist file inside the `build` directory, that you need to decompress in the location fo your choice.
|
||||
After that, you will have a dist file inside the `build` directory, that you need to decompress in the location of your choice.
|
||||
|
||||
> This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by a [GitHub workflow](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Publish+release%22), attaching the generated dist file to it.
|
||||
|
||||
|
||||
7
bin/cli
7
bin/cli
@@ -3,5 +3,8 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$run = require __DIR__ . '/../config/run.php';
|
||||
$run(true);
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
/** @var Application $app */
|
||||
$app = require __DIR__ . '/../config/cli-app.php';
|
||||
$app->run();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
export APP_ENV=test
|
||||
export DB_DRIVER=mysql
|
||||
export DB_DRIVER=postgres
|
||||
export TEST_ENV=api
|
||||
export GENERATE_COVERAGE=${GENERATE_COVERAGE:-"no"}
|
||||
|
||||
rm -rf data/log/api-tests
|
||||
|
||||
# Try to stop server just in case it hanged in last execution
|
||||
vendor/bin/laminas mezzio:swoole:stop
|
||||
|
||||
36
build.sh
36
build.sh
@@ -1,35 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [[ "$#" -ne 1 ]]; then
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || ([ "$#" == 2 ] && [ "$2" != "--no-swoole" ]); then
|
||||
echo "Usage:" >&2
|
||||
echo " $0 {version}" >&2
|
||||
echo " $0 {version} [--no-swoole]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$1
|
||||
builtcontent="./build/shlink_${version}_dist"
|
||||
noSwoole=$2
|
||||
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
||||
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_swoole"
|
||||
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
||||
builtContent="./build/${distId}"
|
||||
projectdir=$(pwd)
|
||||
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
||||
|
||||
# Copy project content to temp dir
|
||||
echo 'Copying project files...'
|
||||
rm -rf "${builtcontent}"
|
||||
mkdir -p "${builtcontent}"
|
||||
rsync -av * "${builtcontent}" \
|
||||
rm -rf "${builtContent}"
|
||||
mkdir -p "${builtContent}"
|
||||
rsync -av * "${builtContent}" \
|
||||
--exclude=*docker* \
|
||||
--exclude=Dockerfile \
|
||||
--include=.htaccess \
|
||||
--exclude-from=./.dockerignore
|
||||
cd "${builtcontent}"
|
||||
cd "${builtContent}"
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing dependencies with $composerBin..."
|
||||
composerFlags="--optimize-autoloader --no-progress --no-interaction"
|
||||
${composerBin} self-update
|
||||
${composerBin} install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction
|
||||
${composerBin} install --no-dev --prefer-dist $composerFlags
|
||||
|
||||
# Copy mezzio helper script to vendor (deprecated - Remove with Shlink 3.0.0)
|
||||
cp "${projectdir}/bin/helper/mezzio-swoole" "./vendor/bin"
|
||||
if [[ $noSwoole ]]; then
|
||||
# If generating a dist not for swoole, uninstall mezzio-swoole
|
||||
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
|
||||
else
|
||||
# Copy mezzio helper script to vendor (deprecated - Remove with Shlink 3.0.0)
|
||||
cp "${projectdir}/bin/helper/mezzio-swoole" "./vendor/bin"
|
||||
fi
|
||||
|
||||
# Delete development files
|
||||
echo 'Deleting dev files...'
|
||||
@@ -41,9 +51,9 @@ sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
||||
# Compressing file
|
||||
echo 'Compressing files...'
|
||||
cd "${projectdir}"/build
|
||||
rm -f ./shlink_${version}_dist.zip
|
||||
zip -ry ./shlink_${version}_dist.zip ./shlink_${version}_dist
|
||||
rm -f ./${distId}.zip
|
||||
zip -ry ./${distId}.zip ./${distId}
|
||||
cd "${projectdir}"
|
||||
rm -rf "${builtcontent}"
|
||||
rm -rf "${builtContent}"
|
||||
|
||||
echo 'Done!'
|
||||
|
||||
135
composer.json
135
composer.json
@@ -12,68 +12,72 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4",
|
||||
"php": "^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-pdo": "*",
|
||||
"akrabat/ip-address-middleware": "^2.0",
|
||||
"cakephp/chronos": "^2.0",
|
||||
"akrabat/ip-address-middleware": "^2.1",
|
||||
"cakephp/chronos": "^2.3",
|
||||
"cocur/slugify": "^4.0",
|
||||
"doctrine/cache": "^1.9",
|
||||
"doctrine/migrations": "^3.0.2",
|
||||
"doctrine/orm": "^2.8",
|
||||
"endroid/qr-code": "^3.6",
|
||||
"geoip2/geoip2": "^2.9",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"happyr/doctrine-specification": "2.0.x-dev#cb116d3 as 2.0",
|
||||
"laminas/laminas-config": "^3.3",
|
||||
"laminas/laminas-config-aggregator": "^1.1",
|
||||
"laminas/laminas-diactoros": "^2.1.3",
|
||||
"laminas/laminas-inputfilter": "^2.10",
|
||||
"laminas/laminas-paginator": "^2.8",
|
||||
"laminas/laminas-servicemanager": "^3.6",
|
||||
"laminas/laminas-stdlib": "^3.2",
|
||||
"lcobucci/jwt": "^4.0",
|
||||
"league/uri": "^6.2",
|
||||
"lstrojny/functional-php": "^1.15",
|
||||
"mezzio/mezzio": "^3.2",
|
||||
"mezzio/mezzio-fastroute": "^3.1",
|
||||
"mezzio/mezzio-helpers": "^5.3",
|
||||
"mezzio/mezzio-problem-details": "^1.1",
|
||||
"mezzio/mezzio-swoole": "^3.1",
|
||||
"monolog/monolog": "^2.0",
|
||||
"doctrine/migrations": "^3.3",
|
||||
"doctrine/orm": "^2.10",
|
||||
"endroid/qr-code": "^4.4",
|
||||
"geoip2/geoip2": "^2.12",
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"happyr/doctrine-specification": "^2.0",
|
||||
"jaybizzle/crawler-detect": "^1.2.110",
|
||||
"laminas/laminas-config": "^3.7",
|
||||
"laminas/laminas-config-aggregator": "^1.7",
|
||||
"laminas/laminas-diactoros": "^2.8",
|
||||
"laminas/laminas-inputfilter": "^2.13",
|
||||
"laminas/laminas-servicemanager": "^3.10",
|
||||
"laminas/laminas-stdlib": "^3.6",
|
||||
"lcobucci/jwt": "^4.1",
|
||||
"league/uri": "^6.4",
|
||||
"lstrojny/functional-php": "^1.17",
|
||||
"mezzio/mezzio": "^3.7",
|
||||
"mezzio/mezzio-fastroute": "^3.3",
|
||||
"mezzio/mezzio-problem-details": "^1.5",
|
||||
"mezzio/mezzio-swoole": "^3.5",
|
||||
"mlocati/ip-lib": "^1.17",
|
||||
"monolog/monolog": "^2.3",
|
||||
"nikolaposa/monolog-factory": "^3.1",
|
||||
"ocramius/proxy-manager": "^2.11",
|
||||
"pagerfanta/core": "^3.5",
|
||||
"php-amqplib/php-amqplib": "^3.1",
|
||||
"php-middleware/request-id": "^4.1",
|
||||
"predis/predis": "^1.1",
|
||||
"pugx/shortid-php": "^0.7",
|
||||
"ramsey/uuid": "^3.9",
|
||||
"shlinkio/shlink-common": "^3.4",
|
||||
"shlinkio/shlink-config": "^1.0",
|
||||
"shlinkio/shlink-event-dispatcher": "^2.0",
|
||||
"shlinkio/shlink-importer": "^2.1",
|
||||
"shlinkio/shlink-installer": "^5.3",
|
||||
"shlinkio/shlink-ip-geolocation": "^1.5",
|
||||
"symfony/console": "^5.1",
|
||||
"symfony/filesystem": "^5.1",
|
||||
"symfony/lock": "^5.1",
|
||||
"symfony/mercure": "^0.4.1",
|
||||
"symfony/process": "^5.1",
|
||||
"symfony/string": "^5.1"
|
||||
"pugx/shortid-php": "^1.0",
|
||||
"ramsey/uuid": "^4.2",
|
||||
"shlinkio/shlink-common": "^4.2",
|
||||
"shlinkio/shlink-config": "^1.4",
|
||||
"shlinkio/shlink-event-dispatcher": "^2.3",
|
||||
"shlinkio/shlink-importer": "^2.5",
|
||||
"shlinkio/shlink-installer": "^6.3",
|
||||
"shlinkio/shlink-ip-geolocation": "^2.2",
|
||||
"symfony/console": "^5.4",
|
||||
"symfony/filesystem": "^6.0 || ^5.4",
|
||||
"symfony/lock": "^6.0 || ^5.4",
|
||||
"symfony/mercure": "^0.6",
|
||||
"symfony/process": "^6.0 || ^5.4",
|
||||
"symfony/string": "^6.0 || ^5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"cebe/php-openapi": "^1.5",
|
||||
"devster/ubench": "^2.1",
|
||||
"dms/phpunit-arraysubset-asserts": "^0.2.1",
|
||||
"dms/phpunit-arraysubset-asserts": "^0.3.0",
|
||||
"eaglewu/swoole-ide-helper": "dev-master",
|
||||
"infection/infection": "^0.20.2",
|
||||
"infection/infection": "^0.25.4",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpstan/phpstan": "^0.12.64",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpstan/phpstan-doctrine": "^1.0",
|
||||
"phpstan/phpstan-symfony": "^1.0",
|
||||
"phpunit/php-code-coverage": "^9.2",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"shlinkio/php-coding-standard": "~2.1.1",
|
||||
"shlinkio/shlink-test-utils": "^2.0",
|
||||
"symfony/var-dumper": "^5.2",
|
||||
"veewee/composer-run-parallel": "^0.1.0"
|
||||
"shlinkio/php-coding-standard": "~2.2.0",
|
||||
"shlinkio/shlink-test-utils": "^2.5",
|
||||
"symfony/var-dumper": "^6.0",
|
||||
"veewee/composer-run-parallel": "^1.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -82,6 +86,7 @@
|
||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
||||
},
|
||||
"files": [
|
||||
"config/constants.php",
|
||||
"module/Core/functions/functions.php"
|
||||
]
|
||||
},
|
||||
@@ -103,16 +108,17 @@
|
||||
"ci": [
|
||||
"@cs",
|
||||
"@stan",
|
||||
"@swagger:validate",
|
||||
"@test:ci",
|
||||
"@infect:ci"
|
||||
],
|
||||
"ci:parallel": [
|
||||
"@parallel cs stan test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||
"@parallel test:api infect:ci:unit infect:ci:db"
|
||||
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||
"@parallel infect:test:api infect:ci:unit infect:ci:db"
|
||||
],
|
||||
"cs": "phpcs",
|
||||
"cs:fix": "phpcbf",
|
||||
"stan": "phpstan analyse module/*/src/ module/*/config config docker/config data/migrations --level=6",
|
||||
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/config config docker/config data/migrations --level=8",
|
||||
"test": [
|
||||
"@test:unit",
|
||||
"@test:db",
|
||||
@@ -121,10 +127,11 @@
|
||||
"test:ci": [
|
||||
"@test:unit:ci",
|
||||
"@test:db",
|
||||
"@test:api"
|
||||
"@test:api:ci"
|
||||
],
|
||||
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
|
||||
"test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
||||
"test:unit:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit/coverage-html",
|
||||
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
||||
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
||||
@@ -133,19 +140,30 @@
|
||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
||||
"test:api": "bin/test/run-api-tests.sh",
|
||||
"test:unit:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit-html",
|
||||
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --skip-initial-tests",
|
||||
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
|
||||
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --only-covering-test-cases --skip-initial-tests",
|
||||
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
||||
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
|
||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db",
|
||||
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json",
|
||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api",
|
||||
"infect:test": [
|
||||
"@parallel test:unit:ci test:db:sqlite:ci",
|
||||
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
||||
"@infect:ci"
|
||||
],
|
||||
"infect:test:unit": [
|
||||
"@test:unit:ci",
|
||||
"@infect:ci:unit"
|
||||
],
|
||||
"infect:test:api": [
|
||||
"@test:api:ci",
|
||||
"@infect:ci:api"
|
||||
],
|
||||
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
||||
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"</>",
|
||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
|
||||
"ci:parallel": "<fg=blue;options=bold>Same as \"ci\", but parallelizing tasks as much as possible</>",
|
||||
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
||||
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
||||
@@ -154,6 +172,7 @@
|
||||
"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:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB, PostgreSQL and MsSQL</>",
|
||||
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
||||
"test:db:sqlite:ci": "<fg=blue;options=bold>Runs database test suites on a SQLite database, generating all needed reports and logs for CI envs</>",
|
||||
@@ -162,11 +181,13 @@
|
||||
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
||||
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a Miscrosoft SQL Server database</>",
|
||||
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
||||
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage reports</>",
|
||||
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
||||
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
||||
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
||||
"swagger:inline": "<fg=blue;options=bold>Inlines swagger docs in a single file</>",
|
||||
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
||||
},
|
||||
"config": {
|
||||
|
||||
@@ -7,7 +7,6 @@ return [
|
||||
'app_options' => [
|
||||
'name' => 'Shlink',
|
||||
'version' => '%SHLINK_VERSION%',
|
||||
'disable_track_param' => null,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -4,11 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_DELETE_SHORT_URL_THRESHOLD;
|
||||
|
||||
return [
|
||||
|
||||
'delete_short_urls' => [
|
||||
'visits_threshold' => 15,
|
||||
'check_visits_threshold' => true,
|
||||
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -2,24 +2,52 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||
|
||||
use Happyr\DoctrineSpecification\EntitySpecificationRepository;
|
||||
use function Functional\contains;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
return (static function (): array {
|
||||
$driver = env('DB_DRIVER');
|
||||
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
|
||||
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'proxies_dir' => 'data/proxies',
|
||||
'load_mappings_using_functional_style' => true,
|
||||
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||
$resolveDriver = static fn () => match ($driver) {
|
||||
'postgres' => 'pdo_pgsql',
|
||||
'mssql' => 'pdo_sqlsrv',
|
||||
default => 'pdo_mysql',
|
||||
};
|
||||
$resolveDefaultPort = static fn () => match ($driver) {
|
||||
'postgres' => '5432',
|
||||
'mssql' => '1433',
|
||||
default => '3306',
|
||||
};
|
||||
$resolveConnection = static fn () => match (true) {
|
||||
$driver === null || $driver === 'sqlite' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => 'data/database.sqlite',
|
||||
],
|
||||
'connection' => [
|
||||
'user' => '',
|
||||
'password' => '',
|
||||
'dbname' => 'shlink',
|
||||
default => [
|
||||
'driver' => $resolveDriver(),
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
|
||||
'port' => env('DB_PORT', $resolveDefaultPort()),
|
||||
'unix_socket' => $isMysqlCompatible ? env('DB_UNIX_SOCKET') : null,
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
];
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'proxies_dir' => 'data/proxies',
|
||||
'load_mappings_using_functional_style' => true,
|
||||
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||
],
|
||||
'connection' => $resolveConnection(),
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
// When running tests, any mysql-specific option can interfere with other drivers
|
||||
$driverOptions = env('APP_ENV') === 'test' ? [] : [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
@@ -17,8 +9,9 @@ return [
|
||||
'user' => 'root',
|
||||
'password' => 'root',
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => 'shlink_db',
|
||||
'driverOptions' => $driverOptions,
|
||||
'host' => 'shlink_db_mysql',
|
||||
'dbname' => 'shlink',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
'geolite2' => [
|
||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||
'temp_dir' => __DIR__ . '/../../data',
|
||||
'license_key' => 'G4Lm0C60yJsnkdPi', // Deprecated. Remove hardcoded license on v3
|
||||
'license_key' => env('GEOLITE_LICENSE_KEY', 'G4Lm0C60yJsnkdPi'), // Deprecated. Remove hardcoded license on v3
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI;
|
||||
|
||||
use Shlinkio\Shlink\Installer\Config\Option;
|
||||
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
|
||||
|
||||
return [
|
||||
|
||||
@@ -21,10 +24,10 @@ return [
|
||||
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
||||
Option\UrlShortener\ValidateUrlConfigOption::class,
|
||||
Option\Visit\VisitsWebhooksConfigOption::class,
|
||||
Option\Visit\OrphanVisitsWebhooksConfigOption::class,
|
||||
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
||||
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
||||
Option\Redirect\Regular404RedirectConfigOption::class,
|
||||
Option\DisableTrackParamConfigOption::class,
|
||||
Option\Visit\CheckVisitsThresholdConfigOption::class,
|
||||
Option\Visit\VisitsThresholdConfigOption::class,
|
||||
Option\BasePathConfigOption::class,
|
||||
@@ -37,17 +40,40 @@ return [
|
||||
Option\Mercure\MercureInternalUrlConfigOption::class,
|
||||
Option\Mercure\MercureJwtSecretConfigOption::class,
|
||||
Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
|
||||
Option\UrlShortener\IpAnonymizationConfigOption::class,
|
||||
Option\UrlShortener\RedirectStatusCodeConfigOption::class,
|
||||
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
||||
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
|
||||
Option\UrlShortener\AppendExtraPathConfigOption::class,
|
||||
Option\Tracking\IpAnonymizationConfigOption::class,
|
||||
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
||||
Option\Tracking\DisableTrackParamConfigOption::class,
|
||||
Option\Tracking\DisableTrackingFromConfigOption::class,
|
||||
Option\Tracking\DisableTrackingConfigOption::class,
|
||||
Option\Tracking\DisableIpTrackingConfigOption::class,
|
||||
Option\Tracking\DisableReferrerTrackingConfigOption::class,
|
||||
Option\Tracking\DisableUaTrackingConfigOption::class,
|
||||
Option\QrCode\DefaultSizeConfigOption::class,
|
||||
Option\QrCode\DefaultMarginConfigOption::class,
|
||||
Option\QrCode\DefaultFormatConfigOption::class,
|
||||
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
||||
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
||||
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
||||
],
|
||||
|
||||
'installation_commands' => [
|
||||
'db_create_schema' => [
|
||||
'command' => 'bin/cli db:create',
|
||||
InstallationCommand::DB_CREATE_SCHEMA => [
|
||||
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
|
||||
],
|
||||
'db_migrate' => [
|
||||
'command' => 'bin/cli db:migrate',
|
||||
InstallationCommand::DB_MIGRATE => [
|
||||
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
|
||||
],
|
||||
InstallationCommand::GEOLITE_DOWNLOAD_DB => [
|
||||
'command' => 'bin/cli ' . Command\Visit\DownloadGeoLiteDbCommand::NAME,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
use Shlinkio\Shlink\Common\Lock\RetryLockStoreDelegatorFactory;
|
||||
use Predis\ClientInterface as PredisClient;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||
use Symfony\Component\Lock;
|
||||
|
||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||
|
||||
return [
|
||||
|
||||
@@ -24,16 +25,12 @@ return [
|
||||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||
'lock_store' => 'local_lock_store',
|
||||
'lock_store' => env('REDIS_SERVERS') === null ? 'local_lock_store' : 'redis_lock_store',
|
||||
|
||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||
],
|
||||
'delegators' => [
|
||||
Lock\Store\RedisStore::class => [
|
||||
RetryLockStoreDelegatorFactory::class,
|
||||
],
|
||||
Lock\LockFactory::class => [
|
||||
LoggerAwareDelegatorFactory::class,
|
||||
],
|
||||
@@ -42,7 +39,7 @@ return [
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
||||
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
||||
Lock\Store\RedisStore::class => [PredisClient::class],
|
||||
Lock\LockFactory::class => ['lock_store'],
|
||||
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
$isSwoole = extension_loaded('swoole');
|
||||
$isSwoole = extension_loaded('openswoole');
|
||||
|
||||
// For swoole, send logs to standard output
|
||||
$handler = $isSwoole
|
||||
|
||||
@@ -4,33 +4,39 @@ declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
||||
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
||||
use Symfony\Component\Mercure\Publisher;
|
||||
use Symfony\Component\Mercure\PublisherInterface;
|
||||
use Symfony\Component\Mercure\Hub;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
|
||||
return [
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => null,
|
||||
'internal_hub_url' => null,
|
||||
'jwt_secret' => null,
|
||||
'jwt_issuer' => 'Shlink',
|
||||
],
|
||||
return (static function (): array {
|
||||
$publicUrl = env('MERCURE_PUBLIC_HUB_URL');
|
||||
|
||||
'dependencies' => [
|
||||
'delegators' => [
|
||||
LcobucciJwtProvider::class => [
|
||||
LazyServiceFactory::class,
|
||||
return [
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => $publicUrl,
|
||||
'internal_hub_url' => env('MERCURE_INTERNAL_HUB_URL', $publicUrl),
|
||||
'jwt_secret' => env('MERCURE_JWT_SECRET'),
|
||||
'jwt_issuer' => 'Shlink',
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'delegators' => [
|
||||
LcobucciJwtProvider::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
Hub::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
],
|
||||
Publisher::class => [
|
||||
LazyServiceFactory::class,
|
||||
'lazy_services' => [
|
||||
'class_map' => [
|
||||
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
||||
Hub::class => HubInterface::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
'lazy_services' => [
|
||||
'class_map' => [
|
||||
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
||||
Publisher::class => PublisherInterface::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
})();
|
||||
|
||||
@@ -5,24 +5,25 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||
use Mezzio\Helper;
|
||||
use Mezzio\ProblemDetails;
|
||||
use Mezzio\Router;
|
||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
||||
use RKA\Middleware\IpAddress;
|
||||
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'error-handler' => [
|
||||
'middleware' => [
|
||||
Helper\ContentLengthMiddleware::class,
|
||||
ContentLengthMiddleware::class,
|
||||
ErrorHandler::class,
|
||||
Rest\Middleware\CrossDomainMiddleware::class,
|
||||
],
|
||||
],
|
||||
'error-handler-rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Rest\Middleware\CrossDomainMiddleware::class,
|
||||
RequestIdMiddleware::class,
|
||||
ProblemDetails\ProblemDetailsMiddleware::class,
|
||||
],
|
||||
@@ -64,6 +65,11 @@ return [
|
||||
],
|
||||
'not-found' => [
|
||||
'middleware' => [
|
||||
// This middleware is in front of tracking actions explicitly. Putting here for orphan visits tracking
|
||||
IpAddress::class,
|
||||
Core\ErrorHandler\NotFoundTypeResolverMiddleware::class,
|
||||
Core\ShortUrl\Middleware\ExtraPathRedirectMiddleware::class,
|
||||
Core\ErrorHandler\NotFoundTrackerMiddleware::class,
|
||||
Core\ErrorHandler\NotFoundRedirectHandler::class,
|
||||
Core\ErrorHandler\NotFoundTemplateHandler::class,
|
||||
],
|
||||
|
||||
23
config/autoload/qr-codes.global.php
Normal file
23
config/autoload/qr-codes.global.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_MARGIN;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ROUND_BLOCK_SIZE;
|
||||
use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
|
||||
|
||||
return [
|
||||
|
||||
'qr_codes' => [
|
||||
'size' => (int) env('DEFAULT_QR_CODE_SIZE', DEFAULT_QR_CODE_SIZE),
|
||||
'margin' => (int) env('DEFAULT_QR_CODE_MARGIN', DEFAULT_QR_CODE_MARGIN),
|
||||
'format' => env('DEFAULT_QR_CODE_FORMAT', DEFAULT_QR_CODE_FORMAT),
|
||||
'error_correction' => env('DEFAULT_QR_CODE_ERROR_CORRECTION', DEFAULT_QR_CODE_ERROR_CORRECTION),
|
||||
'round_block_size' => (bool) env('DEFAULT_QR_CODE_ROUND_BLOCK_SIZE', DEFAULT_QR_CODE_ROUND_BLOCK_SIZE),
|
||||
],
|
||||
|
||||
];
|
||||
48
config/autoload/rabbit.global.php
Normal file
48
config/autoload/rabbit.global.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
||||
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
'rabbitmq' => [
|
||||
'enabled' => (bool) env('RABBITMQ_ENABLED', false),
|
||||
'host' => env('RABBITMQ_HOST'),
|
||||
'port' => (int) env('RABBITMQ_PORT', '5672'),
|
||||
'user' => env('RABBITMQ_USER'),
|
||||
'password' => env('RABBITMQ_PASSWORD'),
|
||||
'vhost' => env('RABBITMQ_VHOST', '/'),
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
AMQPStreamConnection::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
'delegators' => [
|
||||
AMQPStreamConnection::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
],
|
||||
'lazy_services' => [
|
||||
'class_map' => [
|
||||
AMQPStreamConnection::class => AMQPStreamConnection::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
AMQPStreamConnection::class => [
|
||||
'config.rabbitmq.host',
|
||||
'config.rabbitmq.port',
|
||||
'config.rabbitmq.user',
|
||||
'config.rabbitmq.password',
|
||||
'config.rabbitmq.vhost',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
14
config/autoload/rabbit.local.php.dist
Normal file
14
config/autoload/rabbit.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'rabbitmq' => [
|
||||
'enabled' => true,
|
||||
'host' => 'shlink_rabbitmq',
|
||||
'user' => 'rabbit',
|
||||
'password' => 'rabbit',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -2,12 +2,24 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
use const Shlinkio\Shlink\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
|
||||
|
||||
return [
|
||||
|
||||
'not_found_redirects' => [
|
||||
'invalid_short_url' => null,
|
||||
'regular_404' => null,
|
||||
'base_url' => null,
|
||||
// Deprecated env vars
|
||||
'invalid_short_url' => env('DEFAULT_INVALID_SHORT_URL_REDIRECT', env('INVALID_SHORT_URL_REDIRECT_TO')),
|
||||
'regular_404' => env('DEFAULT_REGULAR_404_REDIRECT', env('REGULAR_404_REDIRECT_TO')),
|
||||
'base_url' => env('DEFAULT_BASE_URL_REDIRECT', env('BASE_URL_REDIRECT_TO')),
|
||||
],
|
||||
|
||||
'url_shortener' => [
|
||||
// TODO Move these options to their own config namespace. Maybe "redirects".
|
||||
'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE),
|
||||
'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
21
config/autoload/redis.global.php
Normal file
21
config/autoload/redis.global.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return (static function (): array {
|
||||
$redisServers = env('REDIS_SERVERS');
|
||||
|
||||
return match (true) {
|
||||
$redisServers === null => [],
|
||||
default => [
|
||||
'cache' => [
|
||||
'redis' => [
|
||||
'servers' => $redisServers,
|
||||
'sentinel_service' => env('REDIS_SENTINEL_SERVICE'),
|
||||
],
|
||||
],
|
||||
],
|
||||
};
|
||||
})();
|
||||
@@ -4,10 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
use Mezzio\Router\FastRouteRouter;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
'router' => [
|
||||
'base_path' => '',
|
||||
'base_path' => env('BASE_PATH', ''),
|
||||
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
||||
|
||||
@@ -2,21 +2,30 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
'mezzio-swoole' => [
|
||||
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
||||
'enable_coroutine' => false,
|
||||
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
||||
|
||||
'swoole-http-server' => [
|
||||
'host' => '0.0.0.0',
|
||||
'process-name' => 'shlink',
|
||||
return (static function () {
|
||||
$taskWorkers = (int) env('TASK_WORKER_NUM', 16);
|
||||
|
||||
'options' => [
|
||||
'worker_num' => 16,
|
||||
'task_worker_num' => 16,
|
||||
return [
|
||||
|
||||
'mezzio-swoole' => [
|
||||
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
||||
'enable_coroutine' => false,
|
||||
|
||||
'swoole-http-server' => [
|
||||
'host' => '0.0.0.0',
|
||||
'port' => (int) env('PORT', 8080),
|
||||
'process-name' => 'shlink',
|
||||
|
||||
'options' => [
|
||||
'worker_num' => (int) env('WEB_WORKER_NUM', 16),
|
||||
'task_worker_num' => $taskWorkers < MIN_TASK_WORKERS ? MIN_TASK_WORKERS : $taskWorkers,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
})();
|
||||
|
||||
36
config/autoload/tracking.global.php
Normal file
36
config/autoload/tracking.global.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
'tracking' => [
|
||||
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
||||
// This applies only if IP address tracking is enabled
|
||||
'anonymize_remote_addr' => (bool) env('ANONYMIZE_REMOTE_ADDR', true),
|
||||
|
||||
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||
'track_orphan_visits' => (bool) env('TRACK_ORPHAN_VISITS', true),
|
||||
|
||||
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
||||
|
||||
// If true, visits will not be tracked at all
|
||||
'disable_tracking' => (bool) env('DISABLE_TRACKING', false),
|
||||
|
||||
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||
'disable_ip_tracking' => (bool) env('DISABLE_IP_TRACKING', false),
|
||||
|
||||
// If true, the referrer will not be tracked
|
||||
'disable_referrer_tracking' => (bool) env('DISABLE_REFERRER_TRACKING', false),
|
||||
|
||||
// If true, the user agent will not be tracked
|
||||
'disable_ua_tracking' => (bool) env('DISABLE_UA_TRACKING', false),
|
||||
|
||||
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
||||
'disable_tracking_from' => env('DISABLE_TRACKING_FROM'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -2,23 +2,41 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
||||
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => 'https',
|
||||
'hostname' => '',
|
||||
return (static function (): array {
|
||||
$shortCodesLength = max(
|
||||
(int) env('DEFAULT_SHORT_CODES_LENGTH', DEFAULT_SHORT_CODES_LENGTH),
|
||||
MIN_SHORT_CODES_LENGTH,
|
||||
);
|
||||
$resolveSchema = static function (): string {
|
||||
// Deprecated. For v3, IS_HTTPS_ENABLED should be true by default, instead of null
|
||||
// return ((bool) env('IS_HTTPS_ENABLED', true)) ? 'https' : 'http';
|
||||
$isHttpsEnabled = env('IS_HTTPS_ENABLED', env('USE_HTTPS'));
|
||||
if ($isHttpsEnabled !== null) {
|
||||
$boolIsHttpsEnabled = (bool) $isHttpsEnabled;
|
||||
return $boolIsHttpsEnabled ? 'https' : 'http';
|
||||
}
|
||||
|
||||
return env('SHORT_DOMAIN_SCHEMA', 'http');
|
||||
};
|
||||
|
||||
return [
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
// Deprecated SHORT_DOMAIN_* env vars
|
||||
'schema' => $resolveSchema(),
|
||||
'hostname' => env('DEFAULT_DOMAIN', env('SHORT_DOMAIN_HOST', '')),
|
||||
],
|
||||
'validate_url' => (bool) env('VALIDATE_URLS', false), // Deprecated
|
||||
'default_short_codes_length' => $shortCodesLength,
|
||||
'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false),
|
||||
'append_extra_path' => (bool) env('REDIRECT_APPEND_EXTRA_PATH', false),
|
||||
],
|
||||
'validate_url' => false,
|
||||
'anonymize_remote_addr' => true,
|
||||
'visits_webhooks' => [],
|
||||
'default_short_codes_length' => DEFAULT_SHORT_CODES_LENGTH,
|
||||
'redirect_status_code' => DEFAULT_REDIRECT_STATUS_CODE,
|
||||
'redirect_cache_lifetime' => DEFAULT_REDIRECT_CACHE_LIFETIME,
|
||||
],
|
||||
|
||||
];
|
||||
];
|
||||
})();
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$isSwoole = extension_loaded('openswoole');
|
||||
|
||||
return [
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => 'http',
|
||||
'hostname' => 'localhost:8080',
|
||||
'hostname' => sprintf('localhost:%s', $isSwoole ? '8080' : '8000'),
|
||||
],
|
||||
'auto_resolve_titles' => true,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
19
config/autoload/webhooks.global.php
Normal file
19
config/autoload/webhooks.global.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return (static function (): array {
|
||||
$webhooks = env('VISITS_WEBHOOKS');
|
||||
|
||||
return [
|
||||
|
||||
'url_shortener' => [
|
||||
// TODO Move these options to their own config namespace
|
||||
'visits_webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
|
||||
'notify_orphan_visits_to_webhooks' => (bool) env('NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS', false),
|
||||
],
|
||||
|
||||
];
|
||||
})();
|
||||
12
config/cli-app.php
Normal file
12
config/cli-app.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
|
||||
return (static function () {
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
return $container->get(CliApp::class);
|
||||
})();
|
||||
@@ -4,12 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
return (function () {
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
$em = $container->get(EntityManager::class);
|
||||
|
||||
return (static function () {
|
||||
/** @var EntityManager $em */
|
||||
$em = include __DIR__ . '/entity-manager.php';
|
||||
return ConsoleRunner::createHelperSet($em);
|
||||
})();
|
||||
|
||||
@@ -8,14 +8,22 @@ use Laminas\ConfigAggregator;
|
||||
use Laminas\Diactoros;
|
||||
use Mezzio;
|
||||
use Mezzio\ProblemDetails;
|
||||
use Mezzio\Swoole;
|
||||
|
||||
use function class_exists;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
use const PHP_SAPI;
|
||||
|
||||
$isCli = PHP_SAPI === 'cli';
|
||||
|
||||
return (new ConfigAggregator\ConfigAggregator([
|
||||
Mezzio\ConfigProvider::class,
|
||||
Mezzio\Router\ConfigProvider::class,
|
||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||
Mezzio\Swoole\ConfigProvider::class,
|
||||
$isCli && class_exists(Swoole\ConfigProvider::class)
|
||||
? Swoole\ConfigProvider::class
|
||||
: new ConfigAggregator\ArrayProvider([]),
|
||||
ProblemDetails\ConfigProvider::class,
|
||||
Diactoros\ConfigProvider::class,
|
||||
Common\ConfigProvider::class,
|
||||
@@ -29,6 +37,7 @@ return (new ConfigAggregator\ConfigAggregator([
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||
env('APP_ENV') === 'test'
|
||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
||||
// Deprecated. When the SimplifiedConfigParser is removed, load only generated_config.php here
|
||||
: new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
||||
], 'data/cache/app_config.php', [
|
||||
Core\Config\SimplifiedConfigParser::class,
|
||||
|
||||
22
config/constants.php
Normal file
22
config/constants.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
|
||||
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||
const MIN_SHORT_CODES_LENGTH = 4;
|
||||
const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND;
|
||||
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||
const CUSTOM_SLUGS_REGEXP = '/[^\pL\pN._~]/u'; // Any unicode letter or number, plus ".", "_" and "~" chars
|
||||
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside an html title tag
|
||||
const DEFAULT_QR_CODE_SIZE = 300;
|
||||
const DEFAULT_QR_CODE_MARGIN = 0;
|
||||
const DEFAULT_QR_CODE_FORMAT = 'png';
|
||||
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
||||
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
||||
const MIN_TASK_WORKERS = 4;
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
use Laminas\ServiceManager\ServiceManager;
|
||||
use Symfony\Component\Lock;
|
||||
|
||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
|
||||
|
||||
12
config/entity-manager.php
Normal file
12
config/entity-manager.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
return (static function () {
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
return $container->get(EntityManager::class);
|
||||
})();
|
||||
@@ -4,12 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
use Mezzio\Application;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
|
||||
return function (bool $isCli = false): void {
|
||||
return static function (): void {
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/container.php';
|
||||
$app = $container->get($isCli ? CliApp::class : Application::class);
|
||||
$app = $container->get(Application::class);
|
||||
|
||||
$app->run();
|
||||
};
|
||||
|
||||
@@ -20,8 +20,7 @@ $config = $container->get('config');
|
||||
$em = $container->get(EntityManager::class);
|
||||
$httpClient = $container->get('shlink_test_api_client');
|
||||
|
||||
// Start code coverage collecting on swoole process, and stop it when process shuts down
|
||||
$httpClient->request('GET', sprintf('http://%s:%s/api-tests/start-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT));
|
||||
// Dump code coverage when process shuts down
|
||||
register_shutdown_function(function () use ($httpClient): void {
|
||||
$httpClient->request(
|
||||
'GET',
|
||||
@@ -29,6 +28,6 @@ register_shutdown_function(function () use ($httpClient): void {
|
||||
);
|
||||
});
|
||||
|
||||
$testHelper->createTestDb();
|
||||
$testHelper->createTestDb(['bin/cli', 'db:create'], ['bin/cli', 'db:migrate']);
|
||||
ApiTest\ApiTestCase::setApiClient($httpClient);
|
||||
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
||||
|
||||
@@ -8,12 +8,16 @@ use GuzzleHttp\Client;
|
||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
use Laminas\Stdlib\Glob;
|
||||
use PDO;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use PHPUnit\Runner\Version;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||
use SebastianBergmann\CodeCoverage\Filter;
|
||||
use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html;
|
||||
use SebastianBergmann\CodeCoverage\Report\PHP;
|
||||
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
||||
|
||||
@@ -26,38 +30,24 @@ use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
||||
|
||||
$isApiTest = env('TEST_ENV') === 'api';
|
||||
if ($isApiTest) {
|
||||
$generateCoverage = env('GENERATE_COVERAGE') === 'yes';
|
||||
if ($isApiTest && $generateCoverage) {
|
||||
$filter = new Filter();
|
||||
foreach (Glob::glob(__DIR__ . '/../../module/*/src') as $item) {
|
||||
$filter->includeDirectory($item);
|
||||
}
|
||||
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
|
||||
$filter->includeDirectory(__DIR__ . '/../../module/Rest/src');
|
||||
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
||||
}
|
||||
|
||||
$buildDbConnection = function (): array {
|
||||
$buildDbConnection = static function (): array {
|
||||
$driver = env('DB_DRIVER', 'sqlite');
|
||||
$isCi = env('CI', false);
|
||||
$getMysqlHost = fn (string $driver) => sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria');
|
||||
$getCiMysqlPort = fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
||||
$getCiMysqlPort = static fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
||||
|
||||
$driverConfigMap = [
|
||||
return match ($driver) {
|
||||
'sqlite' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
||||
],
|
||||
'mysql' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => $isCi ? '127.0.0.1' : $getMysqlHost($driver),
|
||||
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
||||
'user' => 'root',
|
||||
'password' => 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||||
],
|
||||
],
|
||||
'postgres' => [
|
||||
'driver' => 'pdo_pgsql',
|
||||
'host' => $isCi ? '127.0.0.1' : 'shlink_db_postgres',
|
||||
@@ -74,12 +64,30 @@ $buildDbConnection = function (): array {
|
||||
'password' => 'Passw0rd!',
|
||||
'dbname' => 'shlink_test',
|
||||
],
|
||||
];
|
||||
$driverConfigMap['maria'] = $driverConfigMap['mysql'];
|
||||
|
||||
return $driverConfigMap[$driver] ?? [];
|
||||
default => [ // mysql and maria
|
||||
'driver' => 'pdo_mysql',
|
||||
'host' => $isCi ? '127.0.0.1' : sprintf('shlink_db_%s', $driver),
|
||||
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
||||
'user' => 'root',
|
||||
'password' => 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
$buildTestLoggerConfig = fn (string $handlerName, string $filename) => [
|
||||
'handlers' => [
|
||||
$handlerName => [
|
||||
'name' => StreamHandler::class,
|
||||
'params' => [
|
||||
'level' => Logger::DEBUG,
|
||||
'stream' => sprintf('data/log/api-tests/%s', $filename),
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
'debug' => true,
|
||||
@@ -107,26 +115,18 @@ return [
|
||||
],
|
||||
|
||||
'routes' => !$isApiTest ? [] : [
|
||||
[
|
||||
'name' => 'start_collecting_coverage',
|
||||
'path' => '/api-tests/start-coverage',
|
||||
'middleware' => middleware(static function () use (&$coverage) {
|
||||
if ($coverage) {
|
||||
$coverage->start('API tests');
|
||||
}
|
||||
return new EmptyResponse();
|
||||
}),
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'dump_coverage',
|
||||
'path' => '/api-tests/stop-coverage',
|
||||
'middleware' => middleware(static function () use (&$coverage) {
|
||||
if ($coverage) {
|
||||
// TODO I have tried moving this block to a listener so that it's invoked automatically,
|
||||
// but then the coverage is generated empty ¯\_(ツ)_/¯
|
||||
if ($coverage) { // @phpstan-ignore-line
|
||||
$basePath = __DIR__ . '/../../build/coverage-api';
|
||||
$coverage->stop();
|
||||
|
||||
(new PHP())->process($coverage, $basePath . '.cov');
|
||||
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
||||
(new Html())->process($coverage, $basePath . '/coverage-html');
|
||||
}
|
||||
|
||||
return new EmptyResponse();
|
||||
@@ -135,6 +135,24 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
'middleware_pipeline' => !$isApiTest ? [] : [
|
||||
'capture_code_coverage' => [
|
||||
'middleware' => middleware(static function (
|
||||
ServerRequestInterface $req,
|
||||
RequestHandlerInterface $handler,
|
||||
) use (&$coverage): ResponseInterface {
|
||||
$coverage?->start($req->getHeaderLine('x-coverage-id'));
|
||||
|
||||
try {
|
||||
return $handler->handle($req);
|
||||
} finally {
|
||||
$coverage?->stop();
|
||||
}
|
||||
}),
|
||||
'priority' => 9999,
|
||||
],
|
||||
],
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => null,
|
||||
'internal_hub_url' => null,
|
||||
@@ -163,4 +181,9 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
'logger' => [
|
||||
'Shlink' => $buildTestLoggerConfig('shlink_handler', 'shlink.log'),
|
||||
'Access' => $buildTestLoggerConfig('access_handler', 'access.log'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -11,7 +11,7 @@ server {
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
FROM php:7.4.11-fpm-alpine3.12
|
||||
FROM php:8.1.0-fpm-alpine3.15
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.18
|
||||
ENV APCU_BC_VERSION 1.0.5
|
||||
ENV APCU_VERSION 5.1.21
|
||||
ENV PDO_SQLSRV_VERSION 5.10.0beta2
|
||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
||||
|
||||
RUN apk update
|
||||
|
||||
@@ -33,38 +34,27 @@ RUN docker-php-ext-install pdo_pgsql
|
||||
RUN apk add --no-cache gmp-dev
|
||||
RUN docker-php-ext-install gmp
|
||||
|
||||
RUN docker-php-ext-install sockets
|
||||
RUN docker-php-ext-install bcmath
|
||||
|
||||
# Install APCu extension
|
||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
||||
# 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
|
||||
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||
&& docker-php-ext-configure apcu \
|
||||
&& docker-php-ext-install apcu \
|
||||
&& rm /tmp/apcu.tar.gz \
|
||||
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||
|
||||
# Install pcov and sqlsrv driver
|
||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||
pecl install pdo_sqlsrv pcov && \
|
||||
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||
apk del .phpize-deps && \
|
||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
||||
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
FROM php:7.4.11-alpine3.12
|
||||
FROM php:8.1.0-alpine3.15
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
ENV APCU_VERSION 5.1.18
|
||||
ENV APCU_BC_VERSION 1.0.5
|
||||
ENV INOTIFY_VERSION 2.0.0
|
||||
ENV SWOOLE_VERSION 4.5.9
|
||||
ENV APCU_VERSION 5.1.21
|
||||
ENV INOTIFY_VERSION 3.0.0
|
||||
ENV OPENSWOOLE_VERSION 4.8.1
|
||||
ENV PDO_SQLSRV_VERSION 5.10.0beta2
|
||||
ENV MS_ODBC_SQL_VERSION 17.5.2.2
|
||||
|
||||
RUN apk update
|
||||
|
||||
@@ -35,48 +36,35 @@ RUN docker-php-ext-install pdo_pgsql
|
||||
RUN apk add --no-cache gmp-dev
|
||||
RUN docker-php-ext-install gmp
|
||||
|
||||
RUN docker-php-ext-install sockets
|
||||
RUN docker-php-ext-install bcmath
|
||||
|
||||
# Install APCu extension
|
||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
||||
# 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
|
||||
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||
&& docker-php-ext-configure apcu \
|
||||
&& docker-php-ext-install apcu \
|
||||
&& rm /tmp/apcu.tar.gz \
|
||||
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||
|
||||
# Install inotify extension
|
||||
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/inotify\
|
||||
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1
|
||||
# configure and install
|
||||
RUN docker-php-ext-configure inotify\
|
||||
&& docker-php-ext-install inotify
|
||||
# cleanup
|
||||
RUN rm /tmp/inotify.tar.gz
|
||||
RUN mkdir -p /usr/src/php/ext/inotify \
|
||||
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1 \
|
||||
&& docker-php-ext-configure inotify \
|
||||
&& docker-php-ext-install inotify \
|
||||
&& rm /tmp/inotify.tar.gz
|
||||
|
||||
# Install swoole, pcov and mssql driver
|
||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
||||
# Install openswoole, pcov and mssql driver
|
||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --allow-untrusted msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||
pecl install swoole-${SWOOLE_VERSION} pdo_sqlsrv pcov && \
|
||||
docker-php-ext-enable swoole pdo_sqlsrv pcov && \
|
||||
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||
docker-php-ext-enable openswoole pdo_sqlsrv pcov && \
|
||||
apk del .phpize-deps && \
|
||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
||||
rm msodbcsql17_${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||
@@ -87,12 +75,12 @@ RUN chmod 777 /home
|
||||
VOLUME /home/shlink
|
||||
WORKDIR /home/shlink
|
||||
|
||||
# Expose swoole port
|
||||
# Expose openswoole port
|
||||
EXPOSE 8080
|
||||
|
||||
CMD \
|
||||
# Install dependencies if the vendor dir does not exist
|
||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||
# When restarting the container, swoole might think it is already in execution
|
||||
# When restarting the container, openswoole might think it is already in execution
|
||||
# This forces the app to be started every second until the exit code is 0
|
||||
until php ./vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
@@ -18,7 +18,7 @@ class Version20160819142757 extends AbstractMigration
|
||||
private const SQLITE = 'sqlite';
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
@@ -35,10 +35,15 @@ class Version20160819142757 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$db = $this->connection->getDatabasePlatform()->getName();
|
||||
$this->connection->getDatabasePlatform()->getName();
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,4 +73,9 @@ class Version20160820191203 extends AbstractMigration
|
||||
$schema->dropTable('short_urls_in_tags');
|
||||
$schema->dropTable('tags');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +45,9 @@ class Version20171021093246 extends AbstractMigration
|
||||
$shortUrls->dropColumn('valid_since');
|
||||
$shortUrls->dropColumn('valid_until');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,4 +42,9 @@ class Version20171022064541 extends AbstractMigration
|
||||
|
||||
$shortUrls->dropColumn('max_visits');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,9 @@ final class Version20180801183328 extends AbstractMigration
|
||||
{
|
||||
$schema->getTable('short_urls')->getColumn('short_code')->setLength($size);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use PDO;
|
||||
@@ -16,15 +16,13 @@ use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
*/
|
||||
final class Version20180913205455 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Nothing to create
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
@@ -59,15 +57,18 @@ final class Version20180913205455 extends AbstractMigration
|
||||
|
||||
try {
|
||||
return (string) IpAddress::fromString($addr)->getAnonymizedCopy();
|
||||
} catch (InvalidArgumentException $e) {
|
||||
} catch (InvalidArgumentException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Nothing to rollback
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,9 @@ final class Version20180915110857 extends AbstractMigration
|
||||
{
|
||||
// Nothing to run
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
@@ -42,7 +42,7 @@ final class Version20181020060559 extends AbstractMigration
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
@@ -65,4 +65,9 @@ final class Version20181020060559 extends AbstractMigration
|
||||
{
|
||||
// No down
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,4 +38,9 @@ final class Version20181020065148 extends AbstractMigration
|
||||
{
|
||||
// No down
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,9 @@ final class Version20181110175521 extends AbstractMigration
|
||||
{
|
||||
return $schema->getTable('visits')->getColumn('user_agent');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,9 @@ final class Version20190824075137 extends AbstractMigration
|
||||
{
|
||||
return $schema->getTable('visits')->getColumn('referer');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +52,9 @@ final class Version20190930165521 extends AbstractMigration
|
||||
$schema->getTable('short_urls')->dropColumn('domain_id');
|
||||
$schema->dropTable('domains');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,9 @@ final class Version20191001201532 extends AbstractMigration
|
||||
$shortUrls->dropIndex('unique_short_code_plus_domain');
|
||||
$shortUrls->addUniqueIndex(['short_code']);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,9 @@ final class Version20191020074522 extends AbstractMigration
|
||||
{
|
||||
return $schema->getTable('short_urls')->getColumn('original_url');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
@@ -16,7 +16,7 @@ final class Version20200105165647 extends AbstractMigration
|
||||
private const COLUMNS = ['lat' => 'latitude', 'lon' => 'longitude'];
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function preUp(Schema $schema): void
|
||||
{
|
||||
@@ -43,7 +43,7 @@ final class Version20200105165647 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
@@ -57,7 +57,7 @@ final class Version20200105165647 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
@@ -83,7 +83,7 @@ final class Version20200105165647 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
@@ -93,4 +93,9 @@ final class Version20200105165647 extends AbstractMigration
|
||||
$visitLocations->dropColumn($colName);
|
||||
}
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
@@ -16,7 +16,7 @@ final class Version20200106215144 extends AbstractMigration
|
||||
private const COLUMNS = ['latitude', 'longitude'];
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
@@ -32,7 +32,7 @@ final class Version20200106215144 extends AbstractMigration
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
@@ -44,4 +44,9 @@ final class Version20200106215144 extends AbstractMigration
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,9 @@ final class Version20200110182849 extends AbstractMigration
|
||||
{
|
||||
// No need (and no way) to undo this migration
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,4 +42,9 @@ final class Version20200323190014 extends AbstractMigration
|
||||
|
||||
$visitLocations->dropColumn('is_empty');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,9 @@ final class Version20200503170404 extends AbstractMigration
|
||||
$this->skipIf(! $visits->hasIndex(self::INDEX_NAME));
|
||||
$visits->dropIndex(self::INDEX_NAME);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +41,9 @@ final class Version20201023090929 extends AbstractMigration
|
||||
$shortUrls->dropColumn('import_original_short_code');
|
||||
$shortUrls->dropIndex('unique_imports');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +60,7 @@ final class Version20201102113208 extends AbstractMigration
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|int|null
|
||||
*/
|
||||
private function resolveOneApiKeyId(Result $result)
|
||||
private function resolveOneApiKeyId(Result $result): string|int|null
|
||||
{
|
||||
$results = [];
|
||||
while ($row = $result->fetchAssociative()) {
|
||||
@@ -86,4 +83,9 @@ final class Version20201102113208 extends AbstractMigration
|
||||
$shortUrls->removeForeignKey('FK_' . self::API_KEY_COLUMN);
|
||||
$shortUrls->dropColumn(self::API_KEY_COLUMN);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,9 @@ final class Version20210102174433 extends AbstractMigration
|
||||
$schema->getTable(self::TABLE_NAME)->dropIndex('UQ_role_plus_api_key');
|
||||
$schema->dropTable(self::TABLE_NAME);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,9 @@ final class Version20210118153932 extends AbstractMigration
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
41
data/migrations/Version20210202181026.php
Normal file
41
data/migrations/Version20210202181026.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210202181026 extends AbstractMigration
|
||||
{
|
||||
private const TITLE = 'title';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf($shortUrls->hasColumn(self::TITLE));
|
||||
|
||||
$shortUrls->addColumn(self::TITLE, Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 512,
|
||||
]);
|
||||
$shortUrls->addColumn('title_was_auto_resolved', Types::BOOLEAN, [
|
||||
'default' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf(! $shortUrls->hasColumn(self::TITLE));
|
||||
$shortUrls->dropColumn(self::TITLE);
|
||||
$shortUrls->dropColumn('title_was_auto_resolved');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
48
data/migrations/Version20210207100807.php
Normal file
48
data/migrations/Version20210207100807.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
|
||||
final class Version20210207100807 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visits = $schema->getTable('visits');
|
||||
$this->skipIf($visits->hasColumn('visited_url'));
|
||||
|
||||
$shortUrlId = $visits->getColumn('short_url_id');
|
||||
$shortUrlId->setNotnull(false);
|
||||
|
||||
$visits->addColumn('visited_url', Types::STRING, [
|
||||
'length' => Visitor::VISITED_URL_MAX_LENGTH,
|
||||
'notnull' => false,
|
||||
]);
|
||||
$visits->addColumn('type', Types::STRING, [
|
||||
'length' => 255,
|
||||
'default' => Visit::TYPE_VALID_SHORT_URL,
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visits = $schema->getTable('visits');
|
||||
$this->skipIf(! $visits->hasColumn('visited_url'));
|
||||
|
||||
$shortUrlId = $visits->getColumn('short_url_id');
|
||||
$shortUrlId->setNotnull(true);
|
||||
$visits->dropColumn('visited_url');
|
||||
$visits->dropColumn('type');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
42
data/migrations/Version20210306165711.php
Normal file
42
data/migrations/Version20210306165711.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210306165711 extends AbstractMigration
|
||||
{
|
||||
private const TABLE = 'api_keys';
|
||||
private const COLUMN = 'name';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$apiKeys = $schema->getTable(self::TABLE);
|
||||
$this->skipIf($apiKeys->hasColumn(self::COLUMN));
|
||||
|
||||
$apiKeys->addColumn(
|
||||
self::COLUMN,
|
||||
Types::STRING,
|
||||
[
|
||||
'notnull' => false,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$apiKeys = $schema->getTable(self::TABLE);
|
||||
$this->skipIf(! $apiKeys->hasColumn(self::COLUMN));
|
||||
|
||||
$apiKeys->dropColumn(self::COLUMN);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
31
data/migrations/Version20210522051601.php
Normal file
31
data/migrations/Version20210522051601.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210522051601 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf($shortUrls->hasColumn('crawlable'));
|
||||
$shortUrls->addColumn('crawlable', Types::BOOLEAN, ['default' => false]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf(! $shortUrls->hasColumn('crawlable'));
|
||||
$shortUrls->dropColumn('crawlable');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
33
data/migrations/Version20210522124633.php
Normal file
33
data/migrations/Version20210522124633.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210522124633 extends AbstractMigration
|
||||
{
|
||||
private const POTENTIAL_BOT_COLUMN = 'potential_bot';
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visits = $schema->getTable('visits');
|
||||
$this->skipIf($visits->hasColumn(self::POTENTIAL_BOT_COLUMN));
|
||||
$visits->addColumn(self::POTENTIAL_BOT_COLUMN, Types::BOOLEAN, ['default' => false]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$visits = $schema->getTable('visits');
|
||||
$this->skipIf(! $visits->hasColumn(self::POTENTIAL_BOT_COLUMN));
|
||||
$visits->dropColumn(self::POTENTIAL_BOT_COLUMN);
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
46
data/migrations/Version20210720143824.php
Normal file
46
data/migrations/Version20210720143824.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210720143824 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$domainsTable = $schema->getTable('domains');
|
||||
$this->skipIf($domainsTable->hasColumn('base_url_redirect'));
|
||||
|
||||
$this->createRedirectColumn($domainsTable, 'base_url_redirect');
|
||||
$this->createRedirectColumn($domainsTable, 'regular_not_found_redirect');
|
||||
$this->createRedirectColumn($domainsTable, 'invalid_short_url_redirect');
|
||||
}
|
||||
|
||||
private function createRedirectColumn(Table $table, string $columnName): void
|
||||
{
|
||||
$table->addColumn($columnName, Types::STRING, [
|
||||
'notnull' => false,
|
||||
'default' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$domainsTable = $schema->getTable('domains');
|
||||
$this->skipIf(! $domainsTable->hasColumn('base_url_redirect'));
|
||||
|
||||
$domainsTable->dropColumn('base_url_redirect');
|
||||
$domainsTable->dropColumn('regular_not_found_redirect');
|
||||
$domainsTable->dropColumn('invalid_short_url_redirect');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
31
data/migrations/Version20211002072605.php
Normal file
31
data/migrations/Version20211002072605.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20211002072605 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf($shortUrls->hasColumn('forward_query'));
|
||||
$shortUrls->addColumn('forward_query', Types::BOOLEAN, ['default' => true]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$shortUrls = $schema->getTable('short_urls');
|
||||
$this->skipIf(! $shortUrls->hasColumn('forward_query'));
|
||||
$shortUrls->dropColumn('forward_query');
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,9 @@ final class <className> extends AbstractMigration
|
||||
{
|
||||
<down>
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
shlink_db:
|
||||
shlink_db_mysql:
|
||||
environment:
|
||||
MYSQL_DATABASE: shlink_test
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ services:
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
|
||||
shlink_db:
|
||||
shlink_db_mysql:
|
||||
user: 1000:1000
|
||||
volumes:
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
|
||||
@@ -3,7 +3,7 @@ version: '3'
|
||||
services:
|
||||
shlink_nginx:
|
||||
container_name: shlink_nginx
|
||||
image: nginx:1.17.10-alpine
|
||||
image: nginx:1.19.6-alpine
|
||||
ports:
|
||||
- "8000:80"
|
||||
volumes:
|
||||
@@ -22,19 +22,22 @@ services:
|
||||
- ./:/home/shlink/www
|
||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||
links:
|
||||
- shlink_db
|
||||
- shlink_db_mysql
|
||||
- shlink_db_postgres
|
||||
- shlink_db_maria
|
||||
- shlink_db_ms
|
||||
- shlink_redis
|
||||
- shlink_mercure
|
||||
- shlink_mercure_proxy
|
||||
- shlink_rabbitmq
|
||||
environment:
|
||||
LC_ALL: C
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
|
||||
shlink_swoole_proxy:
|
||||
container_name: shlink_swoole_proxy
|
||||
image: nginx:1.17.10-alpine
|
||||
image: nginx:1.19.6-alpine
|
||||
ports:
|
||||
- "8002:80"
|
||||
volumes:
|
||||
@@ -55,18 +58,21 @@ services:
|
||||
- ./:/home/shlink
|
||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||
links:
|
||||
- shlink_db
|
||||
- shlink_db_mysql
|
||||
- shlink_db_postgres
|
||||
- shlink_db_maria
|
||||
- shlink_db_ms
|
||||
- shlink_redis
|
||||
- shlink_mercure
|
||||
- shlink_mercure_proxy
|
||||
- shlink_rabbitmq
|
||||
environment:
|
||||
LC_ALL: C
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
|
||||
shlink_db:
|
||||
container_name: shlink_db
|
||||
shlink_db_mysql:
|
||||
container_name: shlink_db_mysql
|
||||
image: mysql:5.7
|
||||
ports:
|
||||
- "3307:3306"
|
||||
@@ -120,7 +126,7 @@ services:
|
||||
|
||||
shlink_mercure_proxy:
|
||||
container_name: shlink_mercure_proxy
|
||||
image: nginx:1.17.10-alpine
|
||||
image: nginx:1.19.6-alpine
|
||||
ports:
|
||||
- "8001:80"
|
||||
volumes:
|
||||
@@ -131,10 +137,21 @@ services:
|
||||
|
||||
shlink_mercure:
|
||||
container_name: shlink_mercure
|
||||
image: dunglas/mercure:v0.10
|
||||
image: dunglas/mercure:v0.13
|
||||
ports:
|
||||
- "3080:80"
|
||||
environment:
|
||||
CORS_ALLOWED_ORIGINS: "*"
|
||||
JWT_KEY: "mercure_jwt_key"
|
||||
USE_FORWARDED_HEADERS: "1"
|
||||
SERVER_NAME: ":80"
|
||||
MERCURE_PUBLISHER_JWT_KEY: mercure_jwt_key
|
||||
MERCURE_SUBSCRIBER_JWT_KEY: mercure_jwt_key
|
||||
MERCURE_EXTRA_DIRECTIVES: "cors_origins https://app.shlink.io http://localhost:3000 http://127.0.0.1:3000"
|
||||
|
||||
shlink_rabbitmq:
|
||||
container_name: shlink_rabbitmq
|
||||
image: rabbitmq:3.9-management-alpine
|
||||
ports:
|
||||
- "15672:15672"
|
||||
- "5672:5672"
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: "rabbit"
|
||||
RABBITMQ_DEFAULT_PASS: "rabbit"
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
This image provides an easy way to set up [shlink](https://shlink.io) on a container-based runtime.
|
||||
|
||||
It exposes a shlink instance served with [swoole](https://www.swoole.co.uk/), which can be linked to external databases to persist data.
|
||||
It exposes a shlink instance served with [openswoole](https://www.swoole.co.uk/), which can be linked to external databases to persist data.
|
||||
|
||||
## Usage
|
||||
|
||||
The most basic way to run Shlink's docker image is by providing these mandatory env vars.
|
||||
|
||||
* `SHORT_DOMAIN_HOST`: The custom short domain used for this shlink instance. For example **doma.in**.
|
||||
* `SHORT_DOMAIN_SCHEMA`: Either **http** or **https**.
|
||||
* `DEFAULT_DOMAIN`: The default short domain used for this shlink instance. For example **doma.in**.
|
||||
* `IS_HTTPS_ENABLED`: Either **true** or **false**. Tells if Shlink is being served with HTTPs or not.
|
||||
* `GEOLITE_LICENSE_KEY`: Your GeoLite2 license key. [Learn more](https://shlink.io/documentation/geolite-license-key/) about this.
|
||||
|
||||
To run shlink on top of a local docker service, and using an internal SQLite database, do the following:
|
||||
@@ -21,8 +21,8 @@ To run shlink on top of a local docker service, and using an internal SQLite dat
|
||||
docker run \
|
||||
--name shlink \
|
||||
-p 8080:8080 \
|
||||
-e SHORT_DOMAIN_HOST=doma.in \
|
||||
-e SHORT_DOMAIN_SCHEMA=https \
|
||||
-e DEFAULT_DOMAIN=doma.in \
|
||||
-e IS_HTTPS_ENABLED=true \
|
||||
-e GEOLITE_LICENSE_KEY=kjh23ljkbndskj345 \
|
||||
shlinkio/shlink:stable
|
||||
```
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
log_errors_max_len=0
|
||||
zend.assertions=1
|
||||
assert.exception=1
|
||||
memory_limit=256M
|
||||
|
||||
@@ -7,128 +7,8 @@ namespace Shlinkio\Shlink;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
use function explode;
|
||||
use function Functional\contains;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_DELETE_SHORT_URL_THRESHOLD;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
|
||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
||||
use const Shlinkio\Shlink\Core\MIN_SHORT_CODES_LENGTH;
|
||||
|
||||
$helper = new class {
|
||||
private const DB_DRIVERS_MAP = [
|
||||
'mysql' => 'pdo_mysql',
|
||||
'maria' => 'pdo_mysql',
|
||||
'postgres' => 'pdo_pgsql',
|
||||
'mssql' => 'pdo_sqlsrv',
|
||||
];
|
||||
private const DB_PORTS_MAP = [
|
||||
'mysql' => '3306',
|
||||
'maria' => '3306',
|
||||
'postgres' => '5432',
|
||||
'mssql' => '1433',
|
||||
];
|
||||
|
||||
public function getDbConfig(): array
|
||||
{
|
||||
$driver = env('DB_DRIVER');
|
||||
$isMysql = contains(['maria', 'mysql'], $driver);
|
||||
if ($driver === null || $driver === 'sqlite') {
|
||||
return [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => 'data/database.sqlite',
|
||||
];
|
||||
}
|
||||
|
||||
$driverOptions = ! $isMysql ? [] : [
|
||||
// 1002 -> PDO::MYSQL_ATTR_INIT_COMMAND
|
||||
1002 => 'SET NAMES utf8',
|
||||
// 1000 -> PDO::MYSQL_ATTR_USE_BUFFERED_QUERY
|
||||
1000 => true,
|
||||
];
|
||||
return [
|
||||
'driver' => self::DB_DRIVERS_MAP[$driver],
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
|
||||
'port' => env('DB_PORT', self::DB_PORTS_MAP[$driver]),
|
||||
'driverOptions' => $driverOptions,
|
||||
'unix_socket' => $isMysql ? env('DB_UNIX_SOCKET') : null,
|
||||
];
|
||||
}
|
||||
|
||||
public function getNotFoundRedirectsConfig(): array
|
||||
{
|
||||
return [
|
||||
'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO'),
|
||||
'regular_404' => env('REGULAR_404_REDIRECT_TO'),
|
||||
'base_url' => env('BASE_URL_REDIRECT_TO'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getVisitsWebhooks(): array
|
||||
{
|
||||
$webhooks = env('VISITS_WEBHOOKS');
|
||||
return $webhooks === null ? [] : explode(',', $webhooks);
|
||||
}
|
||||
|
||||
public function getRedisConfig(): ?array
|
||||
{
|
||||
$redisServers = env('REDIS_SERVERS');
|
||||
return $redisServers === null ? null : ['servers' => $redisServers];
|
||||
}
|
||||
|
||||
public function getDefaultShortCodesLength(): int
|
||||
{
|
||||
$value = (int) env('DEFAULT_SHORT_CODES_LENGTH', DEFAULT_SHORT_CODES_LENGTH);
|
||||
return $value < MIN_SHORT_CODES_LENGTH ? MIN_SHORT_CODES_LENGTH : $value;
|
||||
}
|
||||
|
||||
public function getMercureConfig(): array
|
||||
{
|
||||
$publicUrl = env('MERCURE_PUBLIC_HUB_URL');
|
||||
|
||||
return [
|
||||
'public_hub_url' => $publicUrl,
|
||||
'internal_hub_url' => env('MERCURE_INTERNAL_HUB_URL', $publicUrl),
|
||||
'jwt_secret' => env('MERCURE_JWT_SECRET'),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
|
||||
'app_options' => [
|
||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
||||
],
|
||||
|
||||
'delete_short_urls' => [
|
||||
'check_visits_threshold' => true,
|
||||
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
||||
],
|
||||
|
||||
'entity_manager' => [
|
||||
'connection' => $helper->getDbConfig(),
|
||||
],
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => env('SHORT_DOMAIN_SCHEMA', 'http'),
|
||||
'hostname' => env('SHORT_DOMAIN_HOST', ''),
|
||||
],
|
||||
'validate_url' => (bool) env('VALIDATE_URLS', false),
|
||||
'anonymize_remote_addr' => (bool) env('ANONYMIZE_REMOTE_ADDR', true),
|
||||
'visits_webhooks' => $helper->getVisitsWebhooks(),
|
||||
'default_short_codes_length' => $helper->getDefaultShortCodesLength(),
|
||||
'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE),
|
||||
'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME),
|
||||
],
|
||||
|
||||
'not_found_redirects' => $helper->getNotFoundRedirectsConfig(),
|
||||
|
||||
'logger' => [
|
||||
'Shlink' => [
|
||||
'handlers' => [
|
||||
@@ -143,34 +23,4 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'aliases' => env('REDIS_SERVERS') === null ? [] : [
|
||||
'lock_store' => 'redis_lock_store',
|
||||
],
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'redis' => $helper->getRedisConfig(),
|
||||
],
|
||||
|
||||
'router' => [
|
||||
'base_path' => env('BASE_PATH', ''),
|
||||
],
|
||||
|
||||
'mezzio-swoole' => [
|
||||
'swoole-http-server' => [
|
||||
'port' => (int) env('PORT', 8080),
|
||||
'options' => [
|
||||
'worker_num' => (int) env('WEB_WORKER_NUM', 16),
|
||||
'task_worker_num' => (int) env('TASK_WORKER_NUM', 16),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'geolite2' => [
|
||||
'license_key' => env('GEOLITE_LICENSE_KEY', 'G4Lm0C60yJsnkdPi'),
|
||||
],
|
||||
|
||||
'mercure' => $helper->getMercureConfig(),
|
||||
|
||||
];
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
# If SHELL_VERBOSITY was not explicitly provided, run commands in quite mode (-q)
|
||||
[ $SHELL_VERBOSITY ] && flags="" || flags="-q"
|
||||
|
||||
cd /etc/shlink
|
||||
|
||||
echo "Creating fresh database if needed..."
|
||||
php bin/cli db:create -n -q
|
||||
php bin/cli db:create -n ${flags}
|
||||
|
||||
echo "Updating database..."
|
||||
php bin/cli db:migrate -n -q
|
||||
php bin/cli db:migrate -n ${flags}
|
||||
|
||||
echo "Generating proxies..."
|
||||
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n -q
|
||||
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n ${flags}
|
||||
|
||||
echo "Clearing entities cache..."
|
||||
php vendor/doctrine/orm/bin/doctrine.php orm:clear-cache:metadata -n -q
|
||||
php vendor/doctrine/orm/bin/doctrine.php orm:clear-cache:metadata -n ${flags}
|
||||
|
||||
# When restarting the container, swoole might think it is already in execution
|
||||
# Try to download GeoLite2 db file only if the license key env var was defined
|
||||
if [ ! -z "${GEOLITE_LICENSE_KEY}" ]; then
|
||||
echo "Downloading GeoLite2 db file..."
|
||||
php bin/cli visit:download-db -n ${flags}
|
||||
fi
|
||||
|
||||
# Periodicaly run visit:locate every hour
|
||||
# https://shlink.io/documentation/long-running-tasks/#locate-visits
|
||||
# set env var "ENABLE_PERIODIC_VISIT_LOCATE=true" to enable
|
||||
if [ $ENABLE_PERIODIC_VISIT_LOCATE ]; then
|
||||
echo "Configuring periodic visit locate..."
|
||||
echo "0 * * * * php /etc/shlink/bin/cli visit:locate -q" > /etc/crontabs/root
|
||||
/usr/sbin/crond &
|
||||
fi
|
||||
|
||||
# When restarting the container, openswoole might think it is already in execution
|
||||
# This forces the app to be started every second until the exit code is 0
|
||||
until php vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Track visits to 'base_url', 'invalid_short_url' and 'regular_404'
|
||||
|
||||
* Status: Accepted
|
||||
* Date: 2021-02-07
|
||||
|
||||
## Context and problem statement
|
||||
|
||||
Shlink has the mechanism to return either custom errors or custom redirects when visiting the instance's base URL, an invalid short URL, or any other kind of URL that would result in a "Not found" error.
|
||||
|
||||
However, it does not track visits to any of those, just to valid short URLs.
|
||||
|
||||
The intention is to change that, and allow users to track the cases mentioned above.
|
||||
|
||||
## Considered option
|
||||
|
||||
* Create a new table to track visits o this kind.
|
||||
* Reuse the existing `visits` table, by making `short_url_id` nullable and adding a couple of other fields.
|
||||
|
||||
## Decision outcome
|
||||
|
||||
The decision is to use the existing table, as making the short URL nullable can be handled seamlessly by using named constructors, and it has a lot of benefits on regards of reusing existing components.
|
||||
|
||||
Also, the domain name this kind of visits will receive is "Orphan Visits", as they are detached from any existing short URL.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### New table
|
||||
|
||||
* Good because we don't touch existing models and tables, reducing the risk to introduce a backwards compatibility break.
|
||||
* Bad because we will have to repeat data modeling and logic, or refactor some components to support both contexts. This in turn increases the options to introduce a BC break.
|
||||
|
||||
### Reuse existing table
|
||||
|
||||
* Good because all the mechanisms in place to handle visits will work out of the box, including locating visits and such.
|
||||
* Bad because we will have more optional properties, which means more double checks in many places.
|
||||
59
docs/adr/2021-08-05-migrate-to-a-new-caching-library.md
Normal file
59
docs/adr/2021-08-05-migrate-to-a-new-caching-library.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Migrate to a new caching library
|
||||
|
||||
* Status: Accepted
|
||||
* Date: 2021-08-05
|
||||
|
||||
## Context and problem statement
|
||||
|
||||
Shlink has always used the `doctrine/cache` library to handle anything related with cache.
|
||||
|
||||
It was convenient, as it provided several adapters, and it was the library used by other doctrine packages.
|
||||
|
||||
However, after the creation of the caching PSRs ([PSR-6 - Cache](https://www.php-fig.org/psr/psr-6) and [PSR-16 - Simple cache](https://www.php-fig.org/psr/psr-16)), most library authors have moved to those interfaces, and the doctrine team has decided to recommend using any other existing package and decommission their own solution.
|
||||
|
||||
Also, Shlink needs support for Redis clusters and Redis sentinels, which is not supported by `doctrine/cache` Redis adapters.
|
||||
|
||||
## Considered option
|
||||
|
||||
After some research, the only packages that seem to support the capabilities required by Shlink and also seem healthy, are these:
|
||||
|
||||
* [Symfony cache](https://symfony.com/doc/current/components/cache.html)
|
||||
* 🟢 PSR-6 compliant: **yes**
|
||||
* 🟢 PSR-16 compliant: **yes**
|
||||
* 🟢 APCu support: **yes**
|
||||
* 🟢 Redis support: **yes**
|
||||
* 🟢 Redis cluster support: **yes**
|
||||
* 🟢 Redis sentinel support: **yes**
|
||||
* 🟢 Can use redis through Predis: **yes**
|
||||
* 🔴 Individual packages per adapter: **no**
|
||||
* [Laminas cache](https://docs.laminas.dev/laminas-cache/)
|
||||
* 🟢 PSR-6 compliant: **yes**
|
||||
* 🟢 PSR-16 compliant: **yes**
|
||||
* 🟢 APCu support: **yes**
|
||||
* 🟢 Redis support: **yes**
|
||||
* 🟢 Redis cluster support: **yes**
|
||||
* 🔴 Redis sentinel support: **no**
|
||||
* 🔴 Can use redis through Predis: **no**
|
||||
* 🟢 Individual packages per adapter: **yes**
|
||||
|
||||
## Decision outcome
|
||||
|
||||
Even though Symfony packs all their adapters in a single component, which means we will install some code that will never be used, Laminas relies on the native redis extension for anything related with redis.
|
||||
|
||||
That would make Shlink more complex to install, so it seems Symfony's package is the option where it's easier to migrate to.
|
||||
|
||||
Also, it's important that the cache component can share the Redis integration (through `Predis`, in this case), as it's also used by other components (the lock component, to name one).
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Symfony cache
|
||||
|
||||
* Good because it supports Redis Sentinel.
|
||||
* Good because it allows using a external `Predis` instance.
|
||||
* Bad because it packs all the adapters in a single component.
|
||||
|
||||
### Laminas cache
|
||||
|
||||
* Good because allows installing only the adapters you are going to use, through separated packages.
|
||||
* Bad because it requires the php-redis native extension in order to interact with Redis.
|
||||
* Bad because it does ot seem to support Redis Sentinels.
|
||||
@@ -2,4 +2,6 @@
|
||||
|
||||
Here listed you will find the different architectural decisions taken in the project, including all the reasoning behind it, options considered, and final outcome.
|
||||
|
||||
* [2021-08-05 Migrate to a new caching library](2021-08-05-migrate-to-a-new-caching-library.md)
|
||||
* [2021-02-07 Track visits to 'base_url', 'invalid_short_url' and 'regular_404'](2021-02-07-track-visits-to-base-url-invalid-short-url-and-regular-404.md)
|
||||
* [2021-01-17 Support restrictions and permissions in API keys](2021-01-17-support-restrictions-and-permissions-in-api-keys.md)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"defaultContentType": "application/json",
|
||||
"channels": {
|
||||
"http://shlink.io/new-visit": {
|
||||
"https://shlink.io/new-visit": {
|
||||
"subscribe": {
|
||||
"summary": "Receive information about any new visit occurring on any short URL.",
|
||||
"operationId": "newVisit",
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http://shlink.io/new-visit/{shortCode}": {
|
||||
"https://shlink.io/new-visit/{shortCode}": {
|
||||
"parameters": {
|
||||
"shortCode": {
|
||||
"description": "The short code of the short URL",
|
||||
@@ -58,6 +58,23 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"https://shlink.io/new-orphan-visit": {
|
||||
"subscribe": {
|
||||
"summary": "Receive information about any new orphan visit.",
|
||||
"operationId": "newOrphanVisit",
|
||||
"message": {
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"visit": {
|
||||
"$ref": "#/components/schemas/OrphanVisit"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -99,6 +116,15 @@
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "The domain in which the short URL was created. Null if it belongs to default domain."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "A descriptive title of the short URL."
|
||||
},
|
||||
"crawlable": {
|
||||
"type": "boolean",
|
||||
"description": "Tells if this URL will be included as 'Allow' in Shlink's robots.txt."
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
@@ -116,7 +142,9 @@
|
||||
"validUntil": null,
|
||||
"maxVisits": 100
|
||||
},
|
||||
"domain": "example.com"
|
||||
"domain": "example.com",
|
||||
"title": "The title",
|
||||
"crawlable": false
|
||||
}
|
||||
},
|
||||
"ShortUrlMeta": {
|
||||
@@ -162,6 +190,10 @@
|
||||
},
|
||||
"visitLocation": {
|
||||
"$ref": "#/components/schemas/VisitLocation"
|
||||
},
|
||||
"potentialBot": {
|
||||
"type": "boolean",
|
||||
"description": "Tells if Shlink thinks this visit comes potentially from a bot or crawler"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
@@ -176,7 +208,49 @@
|
||||
"longitude": -122.0946,
|
||||
"regionName": "California",
|
||||
"timezone": "America/Los_Angeles"
|
||||
},
|
||||
"potentialBot": false
|
||||
}
|
||||
},
|
||||
"OrphanVisit": {
|
||||
"allOf": [
|
||||
{"$ref": "#/components/schemas/Visit"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"visitedUrl": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "The originally visited URL that triggered the tracking of this visit"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"invalid_short_url",
|
||||
"base_url",
|
||||
"regular_404"
|
||||
],
|
||||
"description": "Tells the type of orphan visit"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"example": {
|
||||
"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"
|
||||
},
|
||||
"potentialBot": false,
|
||||
"visitedUrl": "https://doma.in",
|
||||
"type": "base_url"
|
||||
}
|
||||
},
|
||||
"VisitLocation": {
|
||||
|
||||
20
docs/swagger/definitions/NotFoundRedirects.json
Normal file
20
docs/swagger/definitions/NotFoundRedirects.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"baseUrlRedirect": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "URL to redirect to when a user hits the domain's base URL"
|
||||
},
|
||||
"regular404Redirect": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "URL to redirect to when a user hits a not found URL other than an invalid short URL"
|
||||
},
|
||||
"invalidShortUrlRedirect": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "URL to redirect to when a user hits an invalid short URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
docs/swagger/definitions/OrphanVisit.json
Normal file
23
docs/swagger/definitions/OrphanVisit.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["visitedUrl", "type"],
|
||||
"allOf": [{
|
||||
"$ref": "./Visit.json"
|
||||
}],
|
||||
"properties": {
|
||||
"visitedUrl": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "The originally visited URL that triggered the tracking of this visit"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"invalid_short_url",
|
||||
"base_url",
|
||||
"regular_404"
|
||||
],
|
||||
"description": "Tells the type of orphan visit"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,18 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"shortCode",
|
||||
"shortUrl",
|
||||
"longUrl",
|
||||
"dateCreated",
|
||||
"visitsCount",
|
||||
"tags",
|
||||
"meta",
|
||||
"domain",
|
||||
"title",
|
||||
"crawlable",
|
||||
"forwardQuery"
|
||||
],
|
||||
"properties": {
|
||||
"shortCode": {
|
||||
"type": "string",
|
||||
@@ -34,7 +47,21 @@
|
||||
},
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "The domain in which the short URL was created. Null if it belongs to default domain."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "A descriptive title of the short URL."
|
||||
},
|
||||
"crawlable": {
|
||||
"type": "boolean",
|
||||
"description": "Tells if this URL will be included as 'Allow' in Shlink's robots.txt."
|
||||
},
|
||||
"forwardQuery": {
|
||||
"type": "boolean",
|
||||
"description": "Tells if this URL will forward the query params to the long URL when visited, as explained in [the docs](https://shlink.io/documentation/some-features/#query-params-forwarding)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
48
docs/swagger/definitions/ShortUrlEdition.json
Normal file
48
docs/swagger/definitions/ShortUrlEdition.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"longUrl": {
|
||||
"description": "The long URL this short URL will redirect to",
|
||||
"type": "string"
|
||||
},
|
||||
"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
|
||||
},
|
||||
"validateUrl": {
|
||||
"description": "Tells if the long URL (if provided) should or should not be validated as a reachable URL. If not provided, it will fall back to app-level config",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The list of tags to set to the short URL."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "A descriptive title of the short URL.",
|
||||
"nullable": true
|
||||
},
|
||||
"crawlable": {
|
||||
"type": "boolean",
|
||||
"description": "Tells if this URL will be included as 'Allow' in Shlink's robots.txt."
|
||||
},
|
||||
"forwardQuery": {
|
||||
"type": "boolean",
|
||||
"description": "Tells if the query params should be forwarded from the short URL to the long one, as explained in [the docs](https://shlink.io/documentation/some-features/#query-params-forwarding)."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["referer", "date", "userAgent", "visitLocation"],
|
||||
"properties": {
|
||||
"referer": {
|
||||
"type": "string",
|
||||
@@ -16,6 +17,10 @@
|
||||
},
|
||||
"visitLocation": {
|
||||
"$ref": "./VisitLocation.json"
|
||||
},
|
||||
"potentialBot": {
|
||||
"type": "boolean",
|
||||
"description": "Tells if Shlink thinks this visit comes potentially from a bot or crawler"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["visitsCount"],
|
||||
"required": ["visitsCount", "orphanVisitsCount"],
|
||||
"properties": {
|
||||
"visitsCount": {
|
||||
"type": "number",
|
||||
"description": "The total amount of visits received."
|
||||
"description": "The total amount of visits received on any short URL."
|
||||
},
|
||||
"orphanVisitsCount": {
|
||||
"type": "number",
|
||||
"description": "The total amount of visits that could not be matched to a short URL (visits to the base URL, an invalid short URL or any other kind of 404)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
docs/swagger/examples/short-url-invalid-args.json
Normal file
9
docs/swagger/examples/short-url-invalid-args.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"value": {
|
||||
"title": "Invalid data",
|
||||
"type": "INVALID_ARGUMENT",
|
||||
"detail": "Provided data is not valid",
|
||||
"status": 400,
|
||||
"invalidElements": ["maxVisits", "validSince"]
|
||||
}
|
||||
}
|
||||
9
docs/swagger/examples/short-url-not-found.json
Normal file
9
docs/swagger/examples/short-url-not-found.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"value": {
|
||||
"detail":"No URL found with short code \"abc123\"",
|
||||
"title":"Short URL not found",
|
||||
"type": "INVALID_SHORTCODE",
|
||||
"status": 404,
|
||||
"shortCode": "abc123"
|
||||
}
|
||||
}
|
||||
9
docs/swagger/examples/tag-not-found.json
Normal file
9
docs/swagger/examples/tag-not-found.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"value": {
|
||||
"detail": "Tag with name \"foo\" could not be found",
|
||||
"title": "Tag not found",
|
||||
"type": "TAG_NOT_FOUND",
|
||||
"status": 404,
|
||||
"tag": "foo"
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,14 @@
|
||||
"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"
|
||||
},
|
||||
"example": {
|
||||
"status": "pass",
|
||||
"version": "2.10.0",
|
||||
"links": {
|
||||
"about": "https://shlink.io",
|
||||
"project": "https://github.com/shlinkio/shlink"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,21 +31,19 @@
|
||||
"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"
|
||||
},
|
||||
"example": {
|
||||
"status": "fail",
|
||||
"version": "2.10.0",
|
||||
"links": {
|
||||
"about": "https://shlink.io",
|
||||
"project": "https://github.com/shlinkio/shlink"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"default": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user