From 48bd97fe418f3eaffa700738c0111352ad687a54 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Jan 2023 12:05:54 +0100 Subject: [PATCH] Return deviceLongUrls as part of the short URL data and document API changes --- docs/swagger/definitions/DeviceLongUrls.json | 20 +++ .../definitions/DeviceLongUrlsResp.json | 7 + docs/swagger/definitions/ShortUrl.json | 4 + docs/swagger/definitions/ShortUrlEdition.json | 3 + docs/swagger/paths/v1_short-urls.json | 20 +++ docs/swagger/paths/v1_short-urls_shorten.json | 6 + .../paths/v1_short-urls_{shortCode}.json | 10 ++ module/Core/src/ShortUrl/Entity/ShortUrl.php | 4 +- .../Transformer/ShortUrlDataTransformer.php | 1 + .../PublishingUpdatesGeneratorTest.php | 2 + .../test/ShortUrl/ShortUrlServiceTest.php | 5 +- .../test-api/Action/ListShortUrlsTest.php | 142 ++++++++++-------- 12 files changed, 159 insertions(+), 65 deletions(-) create mode 100644 docs/swagger/definitions/DeviceLongUrls.json create mode 100644 docs/swagger/definitions/DeviceLongUrlsResp.json diff --git a/docs/swagger/definitions/DeviceLongUrls.json b/docs/swagger/definitions/DeviceLongUrls.json new file mode 100644 index 00000000..25e7f322 --- /dev/null +++ b/docs/swagger/definitions/DeviceLongUrls.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "properties": { + "android": { + "description": "The long URL to redirect to when the short URL is visited from a device running Android", + "type": "string", + "nullable": true + }, + "ios": { + "description": "The long URL to redirect to when the short URL is visited from a device running iOS", + "type": "string", + "nullable": true + }, + "desktop": { + "description": "The long URL to redirect to when the short URL is visited from a desktop browser", + "type": "string", + "nullable": true + } + } +} diff --git a/docs/swagger/definitions/DeviceLongUrlsResp.json b/docs/swagger/definitions/DeviceLongUrlsResp.json new file mode 100644 index 00000000..64a56c01 --- /dev/null +++ b/docs/swagger/definitions/DeviceLongUrlsResp.json @@ -0,0 +1,7 @@ +{ + "type": "object", + "required": ["android", "ios", "desktop"], + "allOf": [{ + "$ref": "./DeviceLongUrls.json" + }] +} diff --git a/docs/swagger/definitions/ShortUrl.json b/docs/swagger/definitions/ShortUrl.json index 4d5d9f2d..4060e2f2 100644 --- a/docs/swagger/definitions/ShortUrl.json +++ b/docs/swagger/definitions/ShortUrl.json @@ -4,6 +4,7 @@ "shortCode", "shortUrl", "longUrl", + "deviceLongUrls", "dateCreated", "visitsCount", "visitsSummary", @@ -27,6 +28,9 @@ "type": "string", "description": "The original long URL." }, + "deviceLongUrls": { + "$ref": "./DeviceLongUrlsResp.json" + }, "dateCreated": { "type": "string", "format": "date-time", diff --git a/docs/swagger/definitions/ShortUrlEdition.json b/docs/swagger/definitions/ShortUrlEdition.json index 94ef6135..7a9aca7b 100644 --- a/docs/swagger/definitions/ShortUrlEdition.json +++ b/docs/swagger/definitions/ShortUrlEdition.json @@ -5,6 +5,9 @@ "description": "The long URL this short URL will redirect to", "type": "string" }, + "deviceLongUrls": { + "$ref": "./DeviceLongUrls.json" + }, "validSince": { "description": "The date (in ISO-8601 format) from which this short code will be valid", "type": "string", diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index 2bd461d8..12afe6f4 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -163,6 +163,11 @@ "shortCode": "12C18", "shortUrl": "https://s.test/12C18", "longUrl": "https://store.steampowered.com", + "deviceLongUrls": { + "android": null, + "ios": null, + "desktop": null + }, "dateCreated": "2016-08-21T20:34:16+02:00", "visitsSummary": { "total": 328, @@ -186,6 +191,11 @@ "shortCode": "12Kb3", "shortUrl": "https://s.test/12Kb3", "longUrl": "https://shlink.io", + "deviceLongUrls": { + "android": null, + "ios": "https://shlink.io/ios", + "desktop": null + }, "dateCreated": "2016-05-01T20:34:16+02:00", "visitsSummary": { "total": 1029, @@ -208,6 +218,11 @@ "shortCode": "123bA", "shortUrl": "https://example.com/123bA", "longUrl": "https://www.google.com", + "deviceLongUrls": { + "android": null, + "ios": null, + "desktop": null + }, "dateCreated": "2015-10-01T20:34:16+02:00", "visitsSummary": { "total": 25, @@ -320,6 +335,11 @@ "shortCode": "12C18", "shortUrl": "https://s.test/12C18", "longUrl": "https://store.steampowered.com", + "deviceLongUrls": { + "android": null, + "ios": null, + "desktop": null + }, "dateCreated": "2016-08-21T20:34:16+02:00", "visitsSummary": { "total": 0, diff --git a/docs/swagger/paths/v1_short-urls_shorten.json b/docs/swagger/paths/v1_short-urls_shorten.json index e0257c59..bf2889e5 100644 --- a/docs/swagger/paths/v1_short-urls_shorten.json +++ b/docs/swagger/paths/v1_short-urls_shorten.json @@ -1,6 +1,7 @@ { "get": { "operationId": "shortenUrl", + "deprecated": true, "tags": [ "Short URLs" ], @@ -52,6 +53,11 @@ }, "example": { "longUrl": "https://github.com/shlinkio/shlink", + "deviceLongUrls": { + "android": null, + "ios": null, + "desktop": null + }, "shortUrl": "https://s.test/abc123", "shortCode": "abc123", "dateCreated": "2016-08-21T20:34:16+02:00", diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}.json b/docs/swagger/paths/v1_short-urls_{shortCode}.json index bd69b4ab..e639f362 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}.json @@ -40,6 +40,11 @@ "shortCode": "12Kb3", "shortUrl": "https://s.test/12Kb3", "longUrl": "https://shlink.io", + "deviceLongUrls": { + "android": null, + "ios": null, + "desktop": null + }, "dateCreated": "2016-05-01T20:34:16+02:00", "visitsSummary": { "total": 1029, @@ -162,6 +167,11 @@ "shortCode": "12Kb3", "shortUrl": "https://s.test/12Kb3", "longUrl": "https://shlink.io", + "deviceLongUrls": { + "android": "https://shlink.io/android", + "ios": null, + "desktop": null + }, "dateCreated": "2016-05-01T20:34:16+02:00", "visitsSummary": { "total": 1029, diff --git a/module/Core/src/ShortUrl/Entity/ShortUrl.php b/module/Core/src/ShortUrl/Entity/ShortUrl.php index 9063bdd7..4e93a916 100644 --- a/module/Core/src/ShortUrl/Entity/ShortUrl.php +++ b/module/Core/src/ShortUrl/Entity/ShortUrl.php @@ -25,8 +25,10 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Rest\Entity\ApiKey; +use function array_fill_keys; use function count; use function Functional\map; +use function Shlinkio\Shlink\Core\enumValues; use function Shlinkio\Shlink\Core\generateRandomShortCode; use function Shlinkio\Shlink\Core\normalizeDate; use function Shlinkio\Shlink\Core\normalizeOptionalDate; @@ -330,7 +332,7 @@ class ShortUrl extends AbstractEntity public function deviceLongUrls(): array { - $data = []; + $data = array_fill_keys(enumValues(DeviceType::class), null); foreach ($this->deviceLongUrls as $deviceUrl) { $data[$deviceUrl->deviceType->value] = $deviceUrl->longUrl(); } diff --git a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php index 08327a98..9de5c408 100644 --- a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php @@ -27,6 +27,7 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => $this->stringifier->stringify($shortUrl), 'longUrl' => $shortUrl->getLongUrl(), + 'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'tags' => invoke($shortUrl->getTags(), '__toString'), 'meta' => $this->buildMeta($shortUrl), diff --git a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php index 9611df99..924996f9 100644 --- a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php +++ b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php @@ -52,6 +52,7 @@ class PublishingUpdatesGeneratorTest extends TestCase 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => 'http:/' . $shortUrl->getShortCode(), 'longUrl' => 'longUrl', + 'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'visitsCount' => 0, 'tags' => [], @@ -129,6 +130,7 @@ class PublishingUpdatesGeneratorTest extends TestCase 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => 'http:/' . $shortUrl->getShortCode(), 'longUrl' => 'longUrl', + 'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'visitsCount' => 0, 'tags' => [], diff --git a/module/Core/test/ShortUrl/ShortUrlServiceTest.php b/module/Core/test/ShortUrl/ShortUrlServiceTest.php index 903c3d3d..7851aa9b 100644 --- a/module/Core/test/ShortUrl/ShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/ShortUrlServiceTest.php @@ -20,6 +20,9 @@ use Shlinkio\Shlink\Core\ShortUrl\ShortUrlService; use Shlinkio\Shlink\Rest\Entity\ApiKey; use ShlinkioTest\Shlink\Core\Util\ApiKeyHelpersTrait; +use function array_fill_keys; +use function Shlinkio\Shlink\Core\enumValues; + class ShortUrlServiceTest extends TestCase { use ApiKeyHelpersTrait; @@ -74,7 +77,7 @@ class ShortUrlServiceTest extends TestCase ); $resolveDeviceLongUrls = function () use ($shortUrlEdit): array { - $result = []; + $result = array_fill_keys(enumValues(DeviceType::class), null); foreach ($shortUrlEdit->deviceLongUrls ?? [] as $longUrl) { $result[$longUrl->deviceType->value] = $longUrl->longUrl; } diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index 43d466e3..5cddfc50 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -6,6 +6,7 @@ namespace ShlinkioApiTest\Shlink\Rest\Action; use Cake\Chronos\Chronos; use GuzzleHttp\RequestOptions; +use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase; use function count; @@ -169,109 +170,124 @@ class ListShortUrlsTest extends ApiTestCase public function provideFilteredLists(): iterable { + // FIXME Cannot use enums in constants in PHP 8.1. Change this once support for PHP 8.1 is dropped + $withDeviceLongUrls = static fn (array $shortUrl, ?array $longUrls = null) => [ + ...$shortUrl, + 'deviceLongUrls' => $longUrls ?? [ + DeviceType::ANDROID->value => null, + DeviceType::IOS->value => null, + DeviceType::DESKTOP->value => null, + ], + ]; + $shortUrlMeta = $withDeviceLongUrls(self::SHORT_URL_META, [ + DeviceType::ANDROID->value => 'https://blog.alejandrocelaya.com/android', + DeviceType::IOS->value => 'https://blog.alejandrocelaya.com/ios', + DeviceType::DESKTOP->value => null, + ]); + yield [[], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_META, - self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, - self::SHORT_URL_SHLINK_WITH_TITLE, - self::SHORT_URL_DOCS, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + $withDeviceLongUrls(self::SHORT_URL_DOCS), ], 'valid_api_key']; yield [['excludePastValidUntil' => 'true'], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_META, - self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['excludeMaxVisitsReached' => 'true'], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_META, - self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, - self::SHORT_URL_DOCS, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_DOCS), ], 'valid_api_key']; yield [['orderBy' => 'shortCode'], [ - self::SHORT_URL_SHLINK_WITH_TITLE, - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, - self::SHORT_URL_META, - self::SHORT_URL_DOCS, - self::SHORT_URL_CUSTOM_DOMAIN, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_DOCS), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), ], 'valid_api_key']; yield [['orderBy' => 'shortCode-DESC'], [ - self::SHORT_URL_DOCS, - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_META, - self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_DOCS), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['orderBy' => 'title-DESC'], [ - self::SHORT_URL_META, - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_DOCS, - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, - self::SHORT_URL_SHLINK_WITH_TITLE, + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $withDeviceLongUrls(self::SHORT_URL_DOCS), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['startDate' => Chronos::parse('2018-12-01')->toAtomString()], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_META, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $shortUrlMeta, ], 'valid_api_key']; yield [['endDate' => Chronos::parse('2018-12-01')->toAtomString()], [ - self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, - self::SHORT_URL_SHLINK_WITH_TITLE, - self::SHORT_URL_DOCS, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + $withDeviceLongUrls(self::SHORT_URL_DOCS), ], 'valid_api_key']; yield [['tags' => ['foo']], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_META, - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['tags' => ['bar']], [ - self::SHORT_URL_META, + $shortUrlMeta, ], 'valid_api_key']; yield [['tags' => ['foo', 'bar']], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_META, - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['tags' => ['foo', 'bar'], 'tagsMode' => 'any'], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_META, - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['tags' => ['foo', 'bar'], 'tagsMode' => 'all'], [ - self::SHORT_URL_META, + $shortUrlMeta, ], 'valid_api_key']; yield [['tags' => ['foo', 'bar', 'baz']], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_META, - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['tags' => ['foo', 'bar', 'baz'], 'tagsMode' => 'all'], [], 'valid_api_key']; yield [['tags' => ['foo'], 'endDate' => Chronos::parse('2018-12-01')->toAtomString()], [ - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['searchTerm' => 'alejandro'], [ - self::SHORT_URL_CUSTOM_DOMAIN, - self::SHORT_URL_META, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + $shortUrlMeta, ], 'valid_api_key']; yield [['searchTerm' => 'cool'], [ - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'valid_api_key']; yield [['searchTerm' => 'example.com'], [ - self::SHORT_URL_CUSTOM_DOMAIN, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), ], 'valid_api_key']; yield [[], [ - self::SHORT_URL_CUSTOM_SLUG, - self::SHORT_URL_META, - self::SHORT_URL_SHLINK_WITH_TITLE, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), + $shortUrlMeta, + $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), ], 'author_api_key']; yield [[], [ - self::SHORT_URL_CUSTOM_DOMAIN, + $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), ], 'domain_api_key']; }