Merge pull request #797 from acelaya-forks/feature/deeplinks-support

Feature/deeplinks support
This commit is contained in:
Alejandro Celaya 2020-06-27 11:26:35 +02:00 committed by GitHub
commit e4f01e4cf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 33 additions and 55 deletions

View File

@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
When selecting 301 redirects, you can also configure the time redirects are cached, to mitigate deviations in stats. When selecting 301 redirects, you can also configure the time redirects are cached, to mitigate deviations in stats.
* [#734](https://github.com/shlinkio/shlink/issues/734) Added support to redirect to deeplinks and other links with schemas different from `http` and `https`.
* [#709](https://github.com/shlinkio/shlink/issues/709) Added multi-architecture builds for the docker image. * [#709](https://github.com/shlinkio/shlink/issues/709) Added multi-architecture builds for the docker image.
#### Changed #### Changed

View File

@ -31,7 +31,7 @@ Then you will have to follow these steps:
* Run `./indocker bin/cli db:migrate` to get database migrations up to date. * Run `./indocker bin/cli db:migrate` to get database migrations up to date.
* Run `./indocker bin/cli api-key:generate` to get your first API key generated. * Run `./indocker bin/cli api-key:generate` to get your first API key generated.
Once you finish this, you will have the project exposed in ports `8080` through nginx+php-fpm and `8000` through swoole. 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 used to run commands inside the main docker container.

View File

@ -34,6 +34,7 @@
"laminas/laminas-servicemanager": "^3.4", "laminas/laminas-servicemanager": "^3.4",
"laminas/laminas-stdlib": "^3.2", "laminas/laminas-stdlib": "^3.2",
"lcobucci/jwt": "^4.0@alpha", "lcobucci/jwt": "^4.0@alpha",
"league/uri": "^6.2",
"lstrojny/functional-php": "^1.9", "lstrojny/functional-php": "^1.9",
"mezzio/mezzio": "^3.2", "mezzio/mezzio": "^3.2",
"mezzio/mezzio-fastroute": "^3.0", "mezzio/mezzio-fastroute": "^3.0",

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl; namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
use Laminas\Diactoros\Uri;
use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
@ -128,19 +127,15 @@ class GenerateShortUrlCommand extends Command
$shortCodeLength = $input->getOption('shortCodeLength') ?? $this->defaultShortCodeLength; $shortCodeLength = $input->getOption('shortCodeLength') ?? $this->defaultShortCodeLength;
try { try {
$shortUrl = $this->urlShortener->urlToShortCode( $shortUrl = $this->urlShortener->urlToShortCode($longUrl, $tags, ShortUrlMeta::fromRawData([
new Uri($longUrl), ShortUrlMetaInputFilter::VALID_SINCE => $input->getOption('validSince'),
$tags, ShortUrlMetaInputFilter::VALID_UNTIL => $input->getOption('validUntil'),
ShortUrlMeta::fromRawData([ ShortUrlMetaInputFilter::CUSTOM_SLUG => $customSlug,
ShortUrlMetaInputFilter::VALID_SINCE => $input->getOption('validSince'), ShortUrlMetaInputFilter::MAX_VISITS => $maxVisits !== null ? (int) $maxVisits : null,
ShortUrlMetaInputFilter::VALID_UNTIL => $input->getOption('validUntil'), ShortUrlMetaInputFilter::FIND_IF_EXISTS => $input->getOption('findIfExists'),
ShortUrlMetaInputFilter::CUSTOM_SLUG => $customSlug, ShortUrlMetaInputFilter::DOMAIN => $input->getOption('domain'),
ShortUrlMetaInputFilter::MAX_VISITS => $maxVisits !== null ? (int) $maxVisits : null, ShortUrlMetaInputFilter::SHORT_CODE_LENGTH => $shortCodeLength,
ShortUrlMetaInputFilter::FIND_IF_EXISTS => $input->getOption('findIfExists'), ]));
ShortUrlMetaInputFilter::DOMAIN => $input->getOption('domain'),
ShortUrlMetaInputFilter::SHORT_CODE_LENGTH => $shortCodeLength,
]),
);
$io->writeln([ $io->writeln([
sprintf('Processed long URL: <info>%s</info>', $longUrl), sprintf('Processed long URL: <info>%s</info>', $longUrl),

View File

@ -8,7 +8,6 @@ use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Psr\Http\Message\UriInterface;
use Shlinkio\Shlink\CLI\Command\ShortUrl\GenerateShortUrlCommand; use Shlinkio\Shlink\CLI\Command\ShortUrl\GenerateShortUrlCommand;
use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
@ -88,7 +87,7 @@ class GenerateShortUrlCommandTest extends TestCase
{ {
$shortUrl = new ShortUrl(''); $shortUrl = new ShortUrl('');
$urlToShortCode = $this->urlShortener->urlToShortCode( $urlToShortCode = $this->urlShortener->urlToShortCode(
Argument::type(UriInterface::class), Argument::type('string'),
Argument::that(function (array $tags) { Argument::that(function (array $tags) {
Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $tags); Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $tags);
return $tags; return $tags;

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Action; namespace Shlinkio\Shlink\Core\Action;
use Fig\Http\Message\RequestMethodInterface; use Fig\Http\Message\RequestMethodInterface;
use Laminas\Diactoros\Uri; use League\Uri\Uri;
use Mezzio\Router\Middleware\ImplicitHeadMiddleware; use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
@ -67,7 +67,7 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet
private function buildUrlToRedirectTo(ShortUrl $shortUrl, array $currentQuery, ?string $disableTrackParam): string private function buildUrlToRedirectTo(ShortUrl $shortUrl, array $currentQuery, ?string $disableTrackParam): string
{ {
$uri = new Uri($shortUrl->getLongUrl()); $uri = Uri::createFromString($shortUrl->getLongUrl());
$hardcodedQuery = parse_query($uri->getQuery()); $hardcodedQuery = parse_query($uri->getQuery());
if ($disableTrackParam !== null) { if ($disableTrackParam !== null) {
unset($currentQuery[$disableTrackParam]); unset($currentQuery[$disableTrackParam]);

View File

@ -4,41 +4,32 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Model; namespace Shlinkio\Shlink\Core\Model;
use Psr\Http\Message\UriInterface;
final class CreateShortUrlData final class CreateShortUrlData
{ {
private UriInterface $longUrl; private string $longUrl;
private array $tags; private array $tags;
private ShortUrlMeta $meta; private ShortUrlMeta $meta;
public function __construct( public function __construct(string $longUrl, array $tags = [], ?ShortUrlMeta $meta = null)
UriInterface $longUrl, {
array $tags = [],
?ShortUrlMeta $meta = null
) {
$this->longUrl = $longUrl; $this->longUrl = $longUrl;
$this->tags = $tags; $this->tags = $tags;
$this->meta = $meta ?? ShortUrlMeta::createEmpty(); $this->meta = $meta ?? ShortUrlMeta::createEmpty();
} }
/** public function getLongUrl(): string
*/
public function getLongUrl(): UriInterface
{ {
return $this->longUrl; return $this->longUrl;
} }
/** /**
* @return array * @return string[]
*/ */
public function getTags(): array public function getTags(): array
{ {
return $this->tags; return $this->tags;
} }
/**
*/
public function getMeta(): ShortUrlMeta public function getMeta(): ShortUrlMeta
{ {
return $this->meta; return $this->meta;

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Service; namespace Shlinkio\Shlink\Core\Service;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\UriInterface;
use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
@ -42,10 +41,8 @@ class UrlShortener implements UrlShortenerInterface
* @throws InvalidUrlException * @throws InvalidUrlException
* @throws Throwable * @throws Throwable
*/ */
public function urlToShortCode(UriInterface $url, array $tags, ShortUrlMeta $meta): ShortUrl public function urlToShortCode(string $url, array $tags, ShortUrlMeta $meta): ShortUrl
{ {
$url = (string) $url;
// First, check if a short URL exists for all provided params // First, check if a short URL exists for all provided params
$existingShortUrl = $this->findExistingShortUrlIfExists($url, $tags, $meta); $existingShortUrl = $this->findExistingShortUrlIfExists($url, $tags, $meta);
if ($existingShortUrl !== null) { if ($existingShortUrl !== null) {

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Service; namespace Shlinkio\Shlink\Core\Service;
use Psr\Http\Message\UriInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
@ -17,5 +16,5 @@ interface UrlShortenerInterface
* @throws NonUniqueSlugException * @throws NonUniqueSlugException
* @throws InvalidUrlException * @throws InvalidUrlException
*/ */
public function urlToShortCode(UriInterface $url, array $tags, ShortUrlMeta $meta): ShortUrl; public function urlToShortCode(string $url, array $tags, ShortUrlMeta $meta): ShortUrl;
} }

View File

@ -9,7 +9,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException; use Doctrine\ORM\ORMException;
use Laminas\Diactoros\Uri;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
@ -65,7 +64,7 @@ class UrlShortenerTest extends TestCase
public function urlIsProperlyShortened(): void public function urlIsProperlyShortened(): void
{ {
$shortUrl = $this->urlShortener->urlToShortCode( $shortUrl = $this->urlShortener->urlToShortCode(
new Uri('http://foobar.com/12345/hello?foo=bar'), 'http://foobar.com/12345/hello?foo=bar',
[], [],
ShortUrlMeta::createEmpty(), ShortUrlMeta::createEmpty(),
); );
@ -89,7 +88,7 @@ class UrlShortenerTest extends TestCase
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$shortUrl = $this->urlShortener->urlToShortCode( $shortUrl = $this->urlShortener->urlToShortCode(
new Uri('http://foobar.com/12345/hello?foo=bar'), 'http://foobar.com/12345/hello?foo=bar',
[], [],
ShortUrlMeta::createEmpty(), ShortUrlMeta::createEmpty(),
); );
@ -112,7 +111,7 @@ class UrlShortenerTest extends TestCase
$this->expectException(ORMException::class); $this->expectException(ORMException::class);
$this->urlShortener->urlToShortCode( $this->urlShortener->urlToShortCode(
new Uri('http://foobar.com/12345/hello?foo=bar'), 'http://foobar.com/12345/hello?foo=bar',
[], [],
ShortUrlMeta::createEmpty(), ShortUrlMeta::createEmpty(),
); );
@ -131,7 +130,7 @@ class UrlShortenerTest extends TestCase
$this->expectException(NonUniqueSlugException::class); $this->expectException(NonUniqueSlugException::class);
$this->urlShortener->urlToShortCode( $this->urlShortener->urlToShortCode(
new Uri('http://foobar.com/12345/hello?foo=bar'), 'http://foobar.com/12345/hello?foo=bar',
[], [],
ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug']), ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug']),
); );
@ -151,7 +150,7 @@ class UrlShortenerTest extends TestCase
$findExisting = $repo->findBy(Argument::any())->willReturn([$expected]); $findExisting = $repo->findBy(Argument::any())->willReturn([$expected]);
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$result = $this->urlShortener->urlToShortCode(new Uri($url), $tags, $meta); $result = $this->urlShortener->urlToShortCode($url, $tags, $meta);
$findExisting->shouldHaveBeenCalledOnce(); $findExisting->shouldHaveBeenCalledOnce();
$getRepo->shouldHaveBeenCalledOnce(); $getRepo->shouldHaveBeenCalledOnce();
@ -235,7 +234,7 @@ class UrlShortenerTest extends TestCase
]); ]);
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$result = $this->urlShortener->urlToShortCode(new Uri($url), $tags, $meta); $result = $this->urlShortener->urlToShortCode($url, $tags, $meta);
$this->assertSame($expected, $result); $this->assertSame($expected, $result);
$findExisting->shouldHaveBeenCalledOnce(); $findExisting->shouldHaveBeenCalledOnce();

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Action\ShortUrl; namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Uri;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Model\CreateShortUrlData;
@ -28,6 +27,6 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction
} }
$meta = ShortUrlMeta::fromRawData($postData); $meta = ShortUrlMeta::fromRawData($postData);
return new CreateShortUrlData(new Uri($postData['longUrl']), (array) ($postData['tags'] ?? []), $meta); return new CreateShortUrlData($postData['longUrl'], (array) ($postData['tags'] ?? []), $meta);
} }
} }

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Action\ShortUrl; namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
use Laminas\Diactoros\Uri;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Model\CreateShortUrlData;
@ -46,6 +45,6 @@ class SingleStepCreateShortUrlAction extends AbstractCreateShortUrlAction
]); ]);
} }
return new CreateShortUrlData(new Uri($query['longUrl'])); return new CreateShortUrlData($query['longUrl']);
} }
} }

View File

@ -7,7 +7,6 @@ namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl;
use Cake\Chronos\Chronos; use Cake\Chronos\Chronos;
use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequest;
use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\Uri;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
@ -50,7 +49,7 @@ class CreateShortUrlActionTest extends TestCase
{ {
$shortUrl = new ShortUrl(''); $shortUrl = new ShortUrl('');
$shorten = $this->urlShortener->urlToShortCode( $shorten = $this->urlShortener->urlToShortCode(
Argument::type(Uri::class), Argument::type('string'),
Argument::type('array'), Argument::type('array'),
$expectedMeta, $expectedMeta,
)->willReturn($shortUrl); )->willReturn($shortUrl);

View File

@ -9,7 +9,6 @@ use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Psr\Http\Message\UriInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Exception\ValidationException;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
@ -71,8 +70,8 @@ class SingleStepCreateShortUrlActionTest extends TestCase
]); ]);
$findApiKey = $this->apiKeyService->check('abc123')->willReturn(true); $findApiKey = $this->apiKeyService->check('abc123')->willReturn(true);
$generateShortCode = $this->urlShortener->urlToShortCode( $generateShortCode = $this->urlShortener->urlToShortCode(
Argument::that(function (UriInterface $argument) { Argument::that(function (string $argument): string {
Assert::assertEquals('http://foobar.com', (string) $argument); Assert::assertEquals('http://foobar.com', $argument);
return $argument; return $argument;
}), }),
[], [],