Merge pull request #415 from acelaya/feature/get-meta

Feature/get meta
This commit is contained in:
Alejandro Celaya 2019-07-08 19:01:22 +02:00 committed by GitHub
commit 014eb2a924
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 217 additions and 9 deletions

View file

@ -4,6 +4,46 @@ 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). The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
## [Unreleased]
#### Added
* [#411](https://github.com/shlinkio/shlink/issues/411) Added new `meta` property on the `ShortUrl` REST API model.
These endpoints are affected and include the new property when suitable:
* `GET /short-urls` - List short URLs.
* `GET /short-urls/shorten` - Create a short URL (for integrations).
* `GET /short-urls/{shortCode}` - Get one short URL.
* `POST /short-urls` - Create short URL.
The property includes the values `validSince`, `validUntil` and `maxVisits` in a single object. All of them are nullable.
```json
{
"validSince": "2016-01-01T00:00:00+02:00",
"validUntil": null,
"maxVisits": 100
}
```
#### Changed
* *Nothing*
### Deprecated
* *Nothing*
#### Removed
* *Nothing*
#### Fixed
* *Nothing*
## 1.17.0 - 2019-05-13 ## 1.17.0 - 2019-05-13
#### Added #### Added

View file

@ -29,6 +29,9 @@
}, },
"description": "A list of tags applied to this short URL" "description": "A list of tags applied to this short URL"
}, },
"meta": {
"$ref": "./ShortUrlMeta.json"
},
"originalUrl": { "originalUrl": {
"deprecated": true, "deprecated": true,
"type": "string", "type": "string",

View file

@ -0,0 +1,21 @@
{
"type": "object",
"required": ["validSince", "validUntil", "maxVisits"],
"properties": {
"validSince": {
"description": "The date (in ISO-8601 format) from which this short code will be valid",
"type": "string",
"nullable": true
},
"validUntil": {
"description": "The date (in ISO-8601 format) until which this short code will be valid",
"type": "string",
"nullable": true
},
"maxVisits": {
"description": "The maximum number of allowed visits for this short code",
"type": "number",
"nullable": true
}
}
}

View file

@ -100,7 +100,12 @@
"tags": [ "tags": [
"games", "games",
"tech" "tech"
] ],
"meta": {
"validSince": "2017-01-21T00:00:00+02:00",
"validUntil": null,
"maxVisits": 100
}
}, },
{ {
"shortCode": "12Kb3", "shortCode": "12Kb3",
@ -110,7 +115,12 @@
"visitsCount": 1029, "visitsCount": 1029,
"tags": [ "tags": [
"shlink" "shlink"
] ],
"meta": {
"validSince": null,
"validUntil": null,
"maxVisits": null
}
}, },
{ {
"shortCode": "123bA", "shortCode": "123bA",
@ -118,7 +128,12 @@
"longUrl": "https://www.google.com", "longUrl": "https://www.google.com",
"dateCreated": "2015-10-01T20:34:16+02:00", "dateCreated": "2015-10-01T20:34:16+02:00",
"visitsCount": 25, "visitsCount": 25,
"tags": [] "tags": [],
"meta": {
"validSince": "2017-01-21T00:00:00+02:00",
"validUntil": null,
"maxVisits": null
}
} }
], ],
"pagination": { "pagination": {
@ -227,7 +242,12 @@
"tags": [ "tags": [
"games", "games",
"tech" "tech"
] ],
"meta": {
"validSince": "2017-01-21T00:00:00+02:00",
"validUntil": null,
"maxVisits": 500
}
} }
} }
}, },

View file

@ -64,7 +64,12 @@
"tags": [ "tags": [
"games", "games",
"tech" "tech"
] ],
"meta": {
"validSince": "2017-01-21T00:00:00+02:00",
"validUntil": null,
"maxVisits": 100
}
}, },
"text/plain": "https://doma.in/abc123" "text/plain": "https://doma.in/abc123"
} }

View file

@ -44,7 +44,12 @@
"visitsCount": 1029, "visitsCount": 1029,
"tags": [ "tags": [
"shlink" "shlink"
] ],
"meta": {
"validSince": "2017-01-21T00:00:00+02:00",
"validUntil": null,
"maxVisits": 100
}
} }
} }
}, },

View file

@ -17,6 +17,8 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Zend\Paginator\Paginator; use Zend\Paginator\Paginator;
use function array_flip;
use function array_intersect_key;
use function array_values; use function array_values;
use function count; use function count;
use function explode; use function explode;
@ -29,6 +31,14 @@ class ListShortUrlsCommand extends Command
public const NAME = 'short-url:list'; public const NAME = 'short-url:list';
private const ALIASES = ['shortcode:list', 'short-code:list']; private const ALIASES = ['shortcode:list', 'short-code:list'];
private const COLUMNS_WHITELIST = [
'shortCode',
'shortUrl',
'longUrl',
'dateCreated',
'visitsCount',
'tags',
];
/** @var ShortUrlServiceInterface */ /** @var ShortUrlServiceInterface */
private $shortUrlService; private $shortUrlService;
@ -125,8 +135,7 @@ class ListShortUrlsCommand extends Command
unset($shortUrl['tags']); unset($shortUrl['tags']);
} }
unset($shortUrl['originalUrl']); $rows[] = array_values(array_intersect_key($shortUrl, array_flip(self::COLUMNS_WHITELIST)));
$rows[] = array_values($shortUrl);
} }
ShlinkTable::fromOutput($output)->render($headers, $rows, $this->formatCurrentPageMessage( ShlinkTable::fromOutput($output)->render($headers, $rows, $this->formatCurrentPageMessage(

View file

@ -8,6 +8,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait; use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait;
use function Functional\invoke; use function Functional\invoke;
use function Functional\invoke_if;
class ShortUrlDataTransformer implements DataTransformerInterface class ShortUrlDataTransformer implements DataTransformerInterface
{ {
@ -36,9 +37,23 @@ class ShortUrlDataTransformer implements DataTransformerInterface
'dateCreated' => $value->getDateCreated()->toAtomString(), 'dateCreated' => $value->getDateCreated()->toAtomString(),
'visitsCount' => $value->getVisitsCount(), 'visitsCount' => $value->getVisitsCount(),
'tags' => invoke($value->getTags(), '__toString'), 'tags' => invoke($value->getTags(), '__toString'),
'meta' => $this->buildMeta($value),
// Deprecated // Deprecated
'originalUrl' => $longUrl, 'originalUrl' => $longUrl,
]; ];
} }
private function buildMeta(ShortUrl $shortUrl): array
{
$validSince = $shortUrl->getValidSince();
$validUntil = $shortUrl->getValidUntil();
$maxVisits = $shortUrl->getMaxVisits();
return [
'validSince' => invoke_if($validSince, 'toAtomString'),
'validUntil' => invoke_if($validUntil, 'toAtomString'),
'maxVisits' => $maxVisits,
];
}
} }

View file

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Transformer;
use Cake\Chronos\Chronos;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use function random_int;
class ShortUrlDataTransformerTest extends TestCase
{
/** @var ShortUrlDataTransformer */
private $transformer;
public function setUp(): void
{
$this->transformer = new ShortUrlDataTransformer([]);
}
/**
* @test
* @dataProvider provideShortUrls
*/
public function properMetadataIsReturned(ShortUrl $shortUrl, array $expectedMeta): void
{
['meta' => $meta] = $this->transformer->transform($shortUrl);
$this->assertEquals($expectedMeta, $meta);
}
public function provideShortUrls(): iterable
{
$maxVisits = random_int(1, 1000);
$now = Chronos::now();
yield 'no metadata' => [new ShortUrl('', ShortUrlMeta::createEmpty()), [
'validSince' => null,
'validUntil' => null,
'maxVisits' => null,
]];
yield 'max visits only' => [new ShortUrl('', ShortUrlMeta::createFromParams(null, null, null, $maxVisits)), [
'validSince' => null,
'validUntil' => null,
'maxVisits' => $maxVisits,
]];
yield 'max visits and valid since' => [
new ShortUrl('', ShortUrlMeta::createFromParams($now, null, null, $maxVisits)),
[
'validSince' => $now->toAtomString(),
'validUntil' => null,
'maxVisits' => $maxVisits,
],
];
yield 'both dates' => [
new ShortUrl('', ShortUrlMeta::createFromParams($now, $now->subDays(10))),
[
'validSince' => $now->toAtomString(),
'validUntil' => $now->subDays(10)->toAtomString(),
'maxVisits' => null,
],
];
yield 'everything' => [
new ShortUrl('', ShortUrlMeta::createFromParams($now, $now->subDays(5), null, $maxVisits)),
[
'validSince' => $now->toAtomString(),
'validUntil' => $now->subDays(5)->toAtomString(),
'maxVisits' => $maxVisits,
],
];
}
}

View file

@ -24,6 +24,11 @@ class ListShortUrlsTest extends ApiTestCase
'dateCreated' => '2019-01-01T00:00:00+00:00', 'dateCreated' => '2019-01-01T00:00:00+00:00',
'visitsCount' => 3, 'visitsCount' => 3,
'tags' => ['foo'], 'tags' => ['foo'],
'meta' => [
'validSince' => null,
'validUntil' => null,
'maxVisits' => null,
],
'originalUrl' => 'https://shlink.io', 'originalUrl' => 'https://shlink.io',
], ],
[ [
@ -35,6 +40,11 @@ class ListShortUrlsTest extends ApiTestCase
'dateCreated' => '2019-01-01T00:00:00+00:00', 'dateCreated' => '2019-01-01T00:00:00+00:00',
'visitsCount' => 2, 'visitsCount' => 2,
'tags' => ['foo', 'bar'], 'tags' => ['foo', 'bar'],
'meta' => [
'validSince' => '2020-05-01T00:00:00+00:00',
'validUntil' => null,
'maxVisits' => null,
],
'originalUrl' => 'originalUrl' =>
'https://blog.alejandrocelaya.com/2017/12/09' 'https://blog.alejandrocelaya.com/2017/12/09'
. '/acmailer-7-0-the-most-important-release-in-a-long-time/', . '/acmailer-7-0-the-most-important-release-in-a-long-time/',
@ -46,6 +56,11 @@ class ListShortUrlsTest extends ApiTestCase
'dateCreated' => '2019-01-01T00:00:00+00:00', 'dateCreated' => '2019-01-01T00:00:00+00:00',
'visitsCount' => 0, 'visitsCount' => 0,
'tags' => [], 'tags' => [],
'meta' => [
'validSince' => null,
'validUntil' => null,
'maxVisits' => 2,
],
'originalUrl' => 'https://shlink.io', 'originalUrl' => 'https://shlink.io',
], ],
], ],

View file

@ -24,7 +24,7 @@ class ShortUrlsFixture extends AbstractFixture
$defShortUrl = $this->setShortUrlDate(new ShortUrl( $defShortUrl = $this->setShortUrlDate(new ShortUrl(
'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/', 'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/',
ShortUrlMeta::createFromParams(Chronos::now()->addDays(3)) ShortUrlMeta::createFromParams(Chronos::parse('2020-05-01'))
))->setShortCode('def456'); ))->setShortCode('def456');
$manager->persist($defShortUrl); $manager->persist($defShortUrl);