mirror of
https://github.com/shlinkio/shlink.git
synced 2026-08-01 00:57:55 -05:00
Merge pull request #2608 from acelaya-forks/mago-config
Extend mago config from shlinkio/shlink-mago-config
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@ build.sh export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
docker-compose.yml export-ignore
|
||||
indocker export-ignore
|
||||
phpcs.xml export-ignore
|
||||
mago.toml export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
phpunit-api.xml export-ignore
|
||||
phpunit-db.xml export-ignore
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"phpunit/php-code-coverage": "^13.0",
|
||||
"phpunit/phpcov": "^12.0",
|
||||
"phpunit/phpunit": "^13.0",
|
||||
"shlinkio/shlink-mago-config": "^1.0",
|
||||
"shlinkio/shlink-test-utils": "^4.5",
|
||||
"symfony/var-dumper": "^8.0",
|
||||
"veewee/composer-run-parallel": "^1.4"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# For full documentation, see https://mago.carthage.software/tools/overview
|
||||
version = "1"
|
||||
php-version = "8.4.0"
|
||||
extends = "vendor/shlinkio/shlink-mago-config/mago.toml"
|
||||
|
||||
[source]
|
||||
workspace = "."
|
||||
@@ -12,27 +13,6 @@ excludes = ["vendor"]
|
||||
[source.glob]
|
||||
literal-separator = true
|
||||
|
||||
[formatter]
|
||||
print-width = 120
|
||||
tab-width = 4
|
||||
use-tabs = false
|
||||
null-type-hint = "null_pipe_last"
|
||||
space-before-arrow-function-parameter-list-parenthesis = true
|
||||
break-promoted-properties-list = false
|
||||
parameter-attribute-on-new-line = false
|
||||
preserve-breaking-member-access-chain = true
|
||||
preserve-breaking-member-access-chain-first-method-on-same-line = true
|
||||
preserve-breaking-argument-list = true
|
||||
preserve-breaking-array-like = true
|
||||
preserve-breaking-parameter-list = true
|
||||
preserve-breaking-attribute-list = true
|
||||
preserve-breaking-conditional-expression = true
|
||||
preserve-breaking-condition-expression = true
|
||||
preserve-redundant-logical-binary-expression-parentheses = true
|
||||
|
||||
[linter]
|
||||
integrations = ["phpunit"]
|
||||
|
||||
[linter.rules]
|
||||
no-literal-password = {
|
||||
exclude = [
|
||||
@@ -71,62 +51,9 @@ no-empty-loop = {
|
||||
"module/Rest/test-db"
|
||||
]
|
||||
}
|
||||
|
||||
prefer-static-closure = { level = "warning" }
|
||||
array-style = { level = "warning" }
|
||||
prefer-early-continue = { max_allowed_statements = 2 }
|
||||
no-fully-qualified-global-class-like = { enabled = true, level = "warning" }
|
||||
no-fully-qualified-global-constant = { enabled = true, level = "warning" }
|
||||
no-fully-qualified-global-function = { enabled = true, level = "warning" }
|
||||
ambiguous-constant-access = { enabled = true, level = "warning" }
|
||||
ambiguous-function-call = {
|
||||
enabled = true,
|
||||
level = "warning",
|
||||
exclude = [
|
||||
"module/Core/functions",
|
||||
"module/Core/config/entities-mappings"
|
||||
]
|
||||
}
|
||||
|
||||
no-multi-assignments = { enabled = false }
|
||||
no-assign-in-condition = { enabled = false }
|
||||
literal-named-argument = { enabled = false }
|
||||
tagged-fixme = { enabled = false }
|
||||
tagged-todo = { enabled = false }
|
||||
no-boolean-flag-parameter = { enabled = false }
|
||||
no-else-clause = { enabled = false }
|
||||
no-isset = { enabled = false }
|
||||
no-empty = { enabled = false }
|
||||
no-shorthand-ternary = { enabled = false }
|
||||
no-ini-set = { enabled = false }
|
||||
too-many-enum-cases = { enabled = false }
|
||||
function-name = { enabled = false }
|
||||
|
||||
# TODO Fine tune these
|
||||
too-many-methods = { enabled = false }
|
||||
too-many-properties = { enabled = false }
|
||||
excessive-parameter-list = { enabled = false }
|
||||
cyclomatic-complexity = { enabled = false }
|
||||
kan-defect = { enabled = false }
|
||||
halstead = { effort-threshold = 7000, enabled = false }
|
||||
|
||||
# TODO Enable this
|
||||
sensitive-parameter = { enabled = false }
|
||||
|
||||
[analyzer]
|
||||
plugins = []
|
||||
find-unused-definitions = true
|
||||
find-unused-expressions = true
|
||||
analyze-dead-code = false
|
||||
memoize-properties = true
|
||||
check-throws = true
|
||||
unchecked-exceptions = ["Error", "LogicException"]
|
||||
unchecked-exception-classes = []
|
||||
check-missing-override = true
|
||||
find-unused-parameters = true
|
||||
strict-list-index-checks = true
|
||||
strict-array-index-existence = false
|
||||
allow-array-truthy-operand = false
|
||||
no-boolean-literal-comparison = false
|
||||
check-missing-type-hints = false
|
||||
register-super-globals = true
|
||||
|
||||
@@ -39,12 +39,12 @@ final class ShlinkTable
|
||||
string|null $headerTitle = null,
|
||||
): void {
|
||||
$style = Table::getStyleDefinition(self::DEFAULT_STYLE_NAME);
|
||||
$style->setFooterTitleFormat(self::TABLE_TITLE_STYLE)
|
||||
->setHeaderTitleFormat(self::TABLE_TITLE_STYLE);
|
||||
$style->setFooterTitleFormat(self::TABLE_TITLE_STYLE)->setHeaderTitleFormat(self::TABLE_TITLE_STYLE);
|
||||
$tableRows = $this->withRowSeparators ? $this->addRowSeparators($rows) : $rows;
|
||||
|
||||
$table = clone $this->baseTable;
|
||||
$table->setStyle($style)
|
||||
$table
|
||||
->setStyle($style)
|
||||
->setHeaders($headers)
|
||||
->setRows($tableRows)
|
||||
->setFooterTitle($footerTitle)
|
||||
|
||||
@@ -23,7 +23,8 @@ class ShlinkTableTest extends TestCase
|
||||
$footerTitle = 'Footer';
|
||||
|
||||
$baseTable = $this->createMock(Table::class);
|
||||
$baseTable->expects($this->once())
|
||||
$baseTable
|
||||
->expects($this->once())
|
||||
->method('setStyle')
|
||||
->with(
|
||||
$this->isInstanceOf(TableStyle::class),
|
||||
|
||||
+16
-14
@@ -11,36 +11,38 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('domains')
|
||||
->setCustomRepositoryClass(Domain\Repository\DomainRepository::class);
|
||||
$builder->setTable('domains')->setCustomRepositoryClass(Domain\Repository\DomainRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('authority', Types::STRING), $emConfig)->unique()
|
||||
->build();
|
||||
fieldWithUtf8Charset($builder->createField('authority', Types::STRING), $emConfig)->unique()->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('baseUrlRedirect', Types::TEXT), $emConfig)->columnName(
|
||||
'base_url_redirect',
|
||||
)
|
||||
fieldWithUtf8Charset($builder->createField('baseUrlRedirect', Types::TEXT), $emConfig)
|
||||
->columnName(
|
||||
'base_url_redirect',
|
||||
)
|
||||
->nullable()
|
||||
->length(2048)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('regular404Redirect', Types::TEXT), $emConfig)->columnName(
|
||||
'regular_not_found_redirect',
|
||||
)
|
||||
fieldWithUtf8Charset($builder->createField('regular404Redirect', Types::TEXT), $emConfig)
|
||||
->columnName(
|
||||
'regular_not_found_redirect',
|
||||
)
|
||||
->nullable()
|
||||
->length(2048)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('invalidShortUrlRedirect', Types::TEXT), $emConfig)->columnName(
|
||||
'invalid_short_url_redirect',
|
||||
)
|
||||
fieldWithUtf8Charset($builder->createField('invalidShortUrlRedirect', Types::TEXT), $emConfig)
|
||||
->columnName(
|
||||
'invalid_short_url_redirect',
|
||||
)
|
||||
->nullable()
|
||||
->length(2048)
|
||||
->build();
|
||||
|
||||
+14
-7
@@ -16,18 +16,21 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
|
||||
$builder->setTable('geolocation_db_updates');
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('dateCreated', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
$builder
|
||||
->createField('dateCreated', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
->columnName('date_created')
|
||||
->build();
|
||||
|
||||
$builder->createField('dateUpdated', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
$builder
|
||||
->createField('dateUpdated', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
->columnName('date_updated')
|
||||
->nullable()
|
||||
->build();
|
||||
@@ -36,20 +39,24 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
'fieldName' => 'status',
|
||||
'type' => Types::STRING,
|
||||
'enumType' => GeolocationDbUpdateStatus::class,
|
||||
])->columnName('status')
|
||||
])
|
||||
->columnName('status')
|
||||
->length(128)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('error', Types::STRING), $emConfig)->columnName('error')
|
||||
fieldWithUtf8Charset($builder->createField('error', Types::STRING), $emConfig)
|
||||
->columnName('error')
|
||||
->length(1024)
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('reason', Types::STRING), $emConfig)->columnName('reason')
|
||||
fieldWithUtf8Charset($builder->createField('reason', Types::STRING), $emConfig)
|
||||
->columnName('reason')
|
||||
->length(1024)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('filesystemId', Types::STRING), $emConfig)->columnName('filesystem_id')
|
||||
fieldWithUtf8Charset($builder->createField('filesystemId', Types::STRING), $emConfig)
|
||||
->columnName('filesystem_id')
|
||||
->length(512)
|
||||
->build();
|
||||
|
||||
|
||||
+8
-4
@@ -15,7 +15,8 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
|
||||
$builder->setTable('redirect_conditions');
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
@@ -26,16 +27,19 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
'fieldName' => 'type',
|
||||
'type' => Types::STRING,
|
||||
'enumType' => RedirectConditionType::class,
|
||||
])->columnName('type')
|
||||
])
|
||||
->columnName('type')
|
||||
->length(255)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('matchKey', Types::STRING), $emConfig)->columnName('match_key')
|
||||
fieldWithUtf8Charset($builder->createField('matchKey', Types::STRING), $emConfig)
|
||||
->columnName('match_key')
|
||||
->length(512)
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('matchValue', Types::STRING), $emConfig)->columnName('match_value')
|
||||
fieldWithUtf8Charset($builder->createField('matchValue', Types::STRING), $emConfig)
|
||||
->columnName('match_value')
|
||||
->length(512)
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
+10
-5
@@ -13,28 +13,33 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
|
||||
$builder->setTable('short_url_redirect_rules');
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('priority', Types::INTEGER)
|
||||
$builder
|
||||
->createField('priority', Types::INTEGER)
|
||||
->columnName('priority')
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('longUrl', Types::TEXT), $emConfig)->columnName('long_url')
|
||||
fieldWithUtf8Charset($builder->createField('longUrl', Types::TEXT), $emConfig)
|
||||
->columnName('long_url')
|
||||
->length(2048)
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||
$builder
|
||||
->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||
->addJoinColumn('short_url_id', 'id', nullable: false, onDelete: 'CASCADE')
|
||||
->build();
|
||||
|
||||
// We treat this ManyToMany relation as a unidirectional OneToMany, where conditions are persisted and deleted
|
||||
// together with the rule
|
||||
$builder->createManyToMany('conditions', RedirectRule\Entity\RedirectCondition::class)
|
||||
$builder
|
||||
->createManyToMany('conditions', RedirectRule\Entity\RedirectCondition::class)
|
||||
->setJoinTable('redirect_conditions_in_short_url_redirect_rules')
|
||||
->addInverseJoinColumn('redirect_condition_id', 'id', onDelete: 'CASCADE')
|
||||
->addJoinColumn('short_url_redirect_rule_id', 'id', onDelete: 'CASCADE')
|
||||
|
||||
+39
-23
@@ -13,17 +13,18 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('short_urls')
|
||||
->setCustomRepositoryClass(ShortUrl\Repository\ShortUrlRepository::class);
|
||||
$builder->setTable('short_urls')->setCustomRepositoryClass(ShortUrl\Repository\ShortUrlRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('longUrl', Types::TEXT), $emConfig)->columnName('original_url') // Rename to long_url some day? ¯\_(ツ)_/¯
|
||||
fieldWithUtf8Charset($builder->createField('longUrl', Types::TEXT), $emConfig)
|
||||
->columnName('original_url') // Rename to long_url some day? ¯\_(ツ)_/¯
|
||||
->length(2048)
|
||||
->build();
|
||||
|
||||
@@ -31,7 +32,8 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder->createField('shortCode', Types::STRING),
|
||||
$emConfig,
|
||||
'bin',
|
||||
)->columnName('short_code')
|
||||
)
|
||||
->columnName('short_code')
|
||||
->length(255);
|
||||
if (($emConfig['connection']['driver'] ?? null) === 'pdo_sqlsrv') {
|
||||
// Make sure a case-sensitive charset is set in short code for Microsoft SQL Server
|
||||
@@ -39,85 +41,99 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
}
|
||||
$shortCodeField->build();
|
||||
|
||||
$builder->createField('dateCreated', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
$builder
|
||||
->createField('dateCreated', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
->columnName('date_created')
|
||||
->build();
|
||||
|
||||
$builder->createField('validSince', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
$builder
|
||||
->createField('validSince', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
->columnName('valid_since')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('validUntil', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
$builder
|
||||
->createField('validUntil', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
->columnName('valid_until')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('maxVisits', Types::INTEGER)
|
||||
$builder
|
||||
->createField('maxVisits', Types::INTEGER)
|
||||
->columnName('max_visits')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('importSource', Types::STRING)
|
||||
$builder
|
||||
->createField('importSource', Types::STRING)
|
||||
->columnName('import_source')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('importOriginalShortCode', Types::STRING), $emConfig)->columnName(
|
||||
'import_original_short_code',
|
||||
)
|
||||
fieldWithUtf8Charset($builder->createField('importOriginalShortCode', Types::STRING), $emConfig)
|
||||
->columnName('import_original_short_code')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createOneToMany('visits', Visit\Entity\Visit::class)
|
||||
$builder
|
||||
->createOneToMany('visits', Visit\Entity\Visit::class)
|
||||
->mappedBy('shortUrl')
|
||||
->fetchExtraLazy()
|
||||
->build();
|
||||
|
||||
$builder->createOneToMany('visitsCounts', Visit\Entity\ShortUrlVisitsCount::class)
|
||||
$builder
|
||||
->createOneToMany('visitsCounts', Visit\Entity\ShortUrlVisitsCount::class)
|
||||
->mappedBy('shortUrl')
|
||||
->fetchExtraLazy() // TODO Check if this makes sense
|
||||
->build();
|
||||
|
||||
$builder->createManyToMany('tags', Tag\Entity\Tag::class)
|
||||
$builder
|
||||
->createManyToMany('tags', Tag\Entity\Tag::class)
|
||||
->setJoinTable('short_urls_in_tags')
|
||||
->addInverseJoinColumn('tag_id', 'id', onDelete: 'CASCADE')
|
||||
->addJoinColumn('short_url_id', 'id', onDelete: 'CASCADE')
|
||||
->setOrderBy(['name' => 'ASC'])
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('domain', Domain\Entity\Domain::class)
|
||||
$builder
|
||||
->createManyToOne('domain', Domain\Entity\Domain::class)
|
||||
->addJoinColumn('domain_id', 'id', onDelete: 'RESTRICT')
|
||||
->cascadePersist()
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('authorApiKey', ApiKey::class)
|
||||
$builder
|
||||
->createManyToOne('authorApiKey', ApiKey::class)
|
||||
->addJoinColumn('author_api_key_id', 'id', onDelete: 'SET NULL')
|
||||
->build();
|
||||
|
||||
$builder->addUniqueConstraint(['short_code', 'domain_id'], 'unique_short_code_plus_domain');
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('title', Types::STRING), $emConfig)->columnName('title')
|
||||
fieldWithUtf8Charset($builder->createField('title', Types::STRING), $emConfig)
|
||||
->columnName('title')
|
||||
->length(512)
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('titleWasAutoResolved', Types::BOOLEAN)
|
||||
$builder
|
||||
->createField('titleWasAutoResolved', Types::BOOLEAN)
|
||||
->columnName('title_was_auto_resolved')
|
||||
->option('default', false)
|
||||
->build();
|
||||
|
||||
$builder->createField('crawlable', Types::BOOLEAN)
|
||||
$builder
|
||||
->createField('crawlable', Types::BOOLEAN)
|
||||
->columnName('crawlable')
|
||||
->option('default', false)
|
||||
->build();
|
||||
|
||||
$builder->createField('forwardQuery', Types::BOOLEAN)
|
||||
$builder
|
||||
->createField('forwardQuery', Types::BOOLEAN)
|
||||
->columnName('forward_query')
|
||||
->option('default', true)
|
||||
->build();
|
||||
|
||||
$builder->createOneToMany('redirectRules', RedirectRule\Entity\ShortUrlRedirectRule::class)
|
||||
$builder
|
||||
->createOneToMany('redirectRules', RedirectRule\Entity\ShortUrlRedirectRule::class)
|
||||
->mappedBy('shortUrl')
|
||||
->fetchExtraLazy()
|
||||
->build();
|
||||
|
||||
@@ -11,18 +11,17 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('tags')
|
||||
->setCustomRepositoryClass(Tag\Repository\TagRepository::class);
|
||||
$builder->setTable('tags')->setCustomRepositoryClass(Tag\Repository\TagRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('name', Types::STRING), $emConfig)->unique()
|
||||
->build();
|
||||
fieldWithUtf8Charset($builder->createField('name', Types::STRING), $emConfig)->unique()->build();
|
||||
|
||||
$builder->addInverseManyToMany('shortUrls', ShortUrl\Entity\ShortUrl::class, 'tags');
|
||||
};
|
||||
|
||||
+10
-5
@@ -11,28 +11,33 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('orphan_visits_counts')
|
||||
$builder
|
||||
->setTable('orphan_visits_counts')
|
||||
->setCustomRepositoryClass(Visit\Repository\OrphanVisitsCountRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('potentialBot', Types::BOOLEAN)
|
||||
$builder
|
||||
->createField('potentialBot', Types::BOOLEAN)
|
||||
->columnName('potential_bot')
|
||||
->option('default', false)
|
||||
->build();
|
||||
|
||||
$builder->createField('count', Types::BIGINT)
|
||||
$builder
|
||||
->createField('count', Types::BIGINT)
|
||||
->columnName('count')
|
||||
->option('unsigned', true)
|
||||
->option('default', 1)
|
||||
->build();
|
||||
|
||||
$builder->createField('slotId', Types::INTEGER)
|
||||
$builder
|
||||
->createField('slotId', Types::INTEGER)
|
||||
->columnName('slot_id')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
+12
-6
@@ -11,33 +11,39 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('short_url_visits_counts')
|
||||
$builder
|
||||
->setTable('short_url_visits_counts')
|
||||
->setCustomRepositoryClass(Visit\Repository\ShortUrlVisitsCountRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('potentialBot', Types::BOOLEAN)
|
||||
$builder
|
||||
->createField('potentialBot', Types::BOOLEAN)
|
||||
->columnName('potential_bot')
|
||||
->option('default', false)
|
||||
->build();
|
||||
|
||||
$builder->createField('count', Types::BIGINT)
|
||||
$builder
|
||||
->createField('count', Types::BIGINT)
|
||||
->columnName('count')
|
||||
->option('unsigned', true)
|
||||
->option('default', 1)
|
||||
->build();
|
||||
|
||||
$builder->createField('slotId', Types::INTEGER)
|
||||
$builder
|
||||
->createField('slotId', Types::INTEGER)
|
||||
->columnName('slot_id')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||
$builder
|
||||
->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||
->addJoinColumn('short_url_id', 'id', onDelete: 'CASCADE')
|
||||
->build();
|
||||
|
||||
|
||||
@@ -15,47 +15,54 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('visits')
|
||||
->setCustomRepositoryClass(Visit\Repository\VisitRepository::class);
|
||||
$builder->setTable('visits')->setCustomRepositoryClass(Visit\Repository\VisitRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('referer', Types::STRING), $emConfig)->nullable()
|
||||
fieldWithUtf8Charset($builder->createField('referer', Types::STRING), $emConfig)
|
||||
->nullable()
|
||||
->length(Visitor::REFERER_MAX_LENGTH)
|
||||
->build();
|
||||
|
||||
$builder->createField('date', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
$builder
|
||||
->createField('date', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
->columnName('`date`')
|
||||
->build();
|
||||
|
||||
$builder->addIndex(['date'], 'IDX_visits_date');
|
||||
|
||||
$builder->createField('remoteAddr', Types::STRING)
|
||||
$builder
|
||||
->createField('remoteAddr', Types::STRING)
|
||||
->columnName('remote_addr')
|
||||
->length(Visitor::REMOTE_ADDRESS_MAX_LENGTH)
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('userAgent', Types::STRING), $emConfig)->columnName('user_agent')
|
||||
fieldWithUtf8Charset($builder->createField('userAgent', Types::STRING), $emConfig)
|
||||
->columnName('user_agent')
|
||||
->length(Visitor::USER_AGENT_MAX_LENGTH)
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||
$builder
|
||||
->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||
->addJoinColumn('short_url_id', 'id', onDelete: 'CASCADE')
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('visitLocation', Visit\Entity\VisitLocation::class)
|
||||
$builder
|
||||
->createManyToOne('visitLocation', Visit\Entity\VisitLocation::class)
|
||||
->addJoinColumn('visit_location_id', 'id', onDelete: 'Set NULL')
|
||||
->cascadePersist()
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('visitedUrl', Types::STRING), $emConfig)->columnName('visited_url')
|
||||
fieldWithUtf8Charset($builder->createField('visitedUrl', Types::STRING), $emConfig)
|
||||
->columnName('visited_url')
|
||||
->length(Visitor::VISITED_URL_MAX_LENGTH)
|
||||
->nullable()
|
||||
->build();
|
||||
@@ -64,16 +71,19 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
'fieldName' => 'type',
|
||||
'type' => Types::STRING,
|
||||
'enumType' => VisitType::class,
|
||||
])->columnName('type')
|
||||
])
|
||||
->columnName('type')
|
||||
->length(255)
|
||||
->build();
|
||||
|
||||
$builder->createField('potentialBot', Types::BOOLEAN)
|
||||
$builder
|
||||
->createField('potentialBot', Types::BOOLEAN)
|
||||
->columnName('potential_bot')
|
||||
->option('default', false)
|
||||
->build();
|
||||
|
||||
fieldWithUtf8Charset($builder->createField('redirectUrl', Types::STRING), $emConfig)->columnName('redirect_url')
|
||||
fieldWithUtf8Charset($builder->createField('redirectUrl', Types::STRING), $emConfig)
|
||||
->columnName('redirect_url')
|
||||
->length(Visitor::REDIRECT_URL_MAX_LENGTH)
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
+10
-5
@@ -13,7 +13,8 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
|
||||
$builder->setTable('visit_locations');
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->columnName('id')
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
@@ -29,22 +30,26 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
];
|
||||
|
||||
foreach ($columns as $columnName => $fieldName) {
|
||||
fieldWithUtf8Charset($builder->createField($fieldName, Types::STRING), $emConfig)->columnName($columnName)
|
||||
fieldWithUtf8Charset($builder->createField($fieldName, Types::STRING), $emConfig)
|
||||
->columnName($columnName)
|
||||
->nullable()
|
||||
->build();
|
||||
}
|
||||
|
||||
$builder->createField('latitude', Types::FLOAT)
|
||||
$builder
|
||||
->createField('latitude', Types::FLOAT)
|
||||
->columnName('lat')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder->createField('longitude', Types::FLOAT)
|
||||
$builder
|
||||
->createField('longitude', Types::FLOAT)
|
||||
->columnName('lon')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder->createField('isEmpty', Types::BOOLEAN)
|
||||
$builder
|
||||
->createField('isEmpty', Types::BOOLEAN)
|
||||
->columnName('is_empty')
|
||||
->option('default', false)
|
||||
->nullable(false)
|
||||
|
||||
@@ -18,7 +18,8 @@ final class Version20240226214216 extends AbstractMigration
|
||||
|
||||
// Insert a rule per every device_long_url, and link it to the corresponding condition
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$rules = $qb->select('short_url_id', 'device_type', 'long_url')
|
||||
$rules = $qb
|
||||
->select('short_url_id', 'device_type', 'long_url')
|
||||
->from('device_long_urls')
|
||||
->executeQuery();
|
||||
|
||||
@@ -28,7 +29,8 @@ final class Version20240226214216 extends AbstractMigration
|
||||
$priority = $priorities[$shortUrlId] ?? 1;
|
||||
|
||||
$ruleQb = $this->connection->createQueryBuilder();
|
||||
$ruleQb->insert('short_url_redirect_rules')
|
||||
$ruleQb
|
||||
->insert('short_url_redirect_rules')
|
||||
->values([
|
||||
'priority' => ':priority',
|
||||
'long_url' => ':long_url',
|
||||
@@ -44,7 +46,8 @@ final class Version20240226214216 extends AbstractMigration
|
||||
|
||||
$deviceType = $ruleRow['device_type'];
|
||||
$conditionQb = $this->connection->createQueryBuilder();
|
||||
$conditionQb->insert('redirect_conditions')
|
||||
$conditionQb
|
||||
->insert('redirect_conditions')
|
||||
->values([
|
||||
'type' => ':type',
|
||||
'match_value' => ':match_value',
|
||||
@@ -59,7 +62,8 @@ final class Version20240226214216 extends AbstractMigration
|
||||
$conditionId = $this->connection->lastInsertId();
|
||||
|
||||
$relationQb = $this->connection->createQueryBuilder();
|
||||
$relationQb->insert('redirect_conditions_in_short_url_redirect_rules')
|
||||
$relationQb
|
||||
->insert('redirect_conditions_in_short_url_redirect_rules')
|
||||
->values([
|
||||
'redirect_condition_id' => ':redirect_condition_id',
|
||||
'short_url_redirect_rule_id' => ':short_url_redirect_rule_id',
|
||||
|
||||
@@ -15,13 +15,15 @@ final class Version20240318084804 extends AbstractMigration
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$result = $qb->select('id')
|
||||
$result = $qb
|
||||
->select('id')
|
||||
->from('short_urls')
|
||||
->executeQuery();
|
||||
|
||||
while ($shortUrlId = $result->fetchOne()) {
|
||||
$visitsQb = $this->connection->createQueryBuilder();
|
||||
$visitsQb->select('COUNT(id)')
|
||||
$visitsQb
|
||||
->select('COUNT(id)')
|
||||
->from('visits')
|
||||
->where($visitsQb->expr()->eq('short_url_id', ':short_url_id'))
|
||||
->andWhere($visitsQb->expr()->eq('potential_bot', ':potential_bot'))
|
||||
|
||||
@@ -12,7 +12,8 @@ final class Version20240331111447 extends AbstractMigration
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$visitsQb = $this->connection->createQueryBuilder();
|
||||
$visitsQb->select('COUNT(id)')
|
||||
$visitsQb
|
||||
->select('COUNT(id)')
|
||||
->from('visits')
|
||||
->where($visitsQb->expr()->isNull('short_url_id'))
|
||||
->andWhere($visitsQb->expr()->eq('potential_bot', ':potential_bot'));
|
||||
|
||||
@@ -20,14 +20,16 @@ final class Version20241105094747 extends AbstractMigration
|
||||
|
||||
// Append key to the name for all API keys that already have a name
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('api_keys')
|
||||
$qb
|
||||
->update('api_keys')
|
||||
->set('name', 'CONCAT(name, ' . $this->connection->quote(' - ') . ', ' . $keyColumnName . ')')
|
||||
->where($qb->expr()->isNotNull('name'));
|
||||
$qb->executeStatement();
|
||||
|
||||
// Set plain key as name for all API keys without a name
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('api_keys')
|
||||
$qb
|
||||
->update('api_keys')
|
||||
->set('name', $keyColumnName)
|
||||
->where($qb->expr()->isNull('name'));
|
||||
$qb->executeStatement();
|
||||
|
||||
@@ -25,7 +25,8 @@ final class Version20241105215309 extends AbstractMigration
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
$updateQb = $this->connection->createQueryBuilder();
|
||||
$updateQb->update('api_keys')
|
||||
$updateQb
|
||||
->update('api_keys')
|
||||
->set($keyColumnName, ':encryptedKey')
|
||||
->where($updateQb->expr()->eq($keyColumnName, ':plainTextKey'));
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ class NotFoundRedirectResolver implements NotFoundRedirectResolverInterface
|
||||
$query,
|
||||
);
|
||||
|
||||
return $redirectUri->withPath($replacePlaceholdersInPath($redirectUri->getPath()))
|
||||
return $redirectUri
|
||||
->withPath($replacePlaceholdersInPath($redirectUri->getPath()))
|
||||
->withQuery($replacePlaceholdersInQuery($redirectUri->getQuery()))
|
||||
->__toString();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ class DomainRepository extends EntitySpecificationRepository implements DomainRe
|
||||
public function findDomains(ApiKey|null $apiKey = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('d');
|
||||
$qb->leftJoin(ShortUrl::class, 's', Join::WITH, 's.domain = d')
|
||||
$qb
|
||||
->leftJoin(ShortUrl::class, 's', Join::WITH, 's.domain = d')
|
||||
->groupBy('d')
|
||||
->orderBy('d.authority', 'ASC')
|
||||
->having($qb->expr()->gt('COUNT(s.id)', '0'))
|
||||
@@ -58,7 +59,8 @@ class DomainRepository extends EntitySpecificationRepository implements DomainRe
|
||||
private function createDomainQueryBuilder(string $authority, ApiKey|null $apiKey): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->from(Domain::class, 'd')
|
||||
$qb
|
||||
->from(Domain::class, 'd')
|
||||
->leftJoin(ShortUrl::class, 's', Join::WITH, 's.domain = d')
|
||||
->where($qb->expr()->eq('d.authority', ':authority'))
|
||||
->setParameter('authority', $authority)
|
||||
|
||||
@@ -28,7 +28,8 @@ readonly class MatomoTrackerBuilder implements MatomoTrackerBuilderInterface
|
||||
// Create a new MatomoTracker on every request, because it infers request info during construction
|
||||
$tracker = new MatomoTracker($siteId, $this->options->baseUrl);
|
||||
// Token required to set the IP and location
|
||||
$tracker->setTokenAuth($this->options->apiToken)
|
||||
$tracker
|
||||
->setTokenAuth($this->options->apiToken)
|
||||
// Ensure params are not sent in the URL, for security reasons
|
||||
->setRequestMethodNonBulk('POST')
|
||||
// Set a reasonable timeout
|
||||
|
||||
@@ -50,7 +50,8 @@ readonly class MatomoVisitSender implements MatomoVisitSenderInterface
|
||||
{
|
||||
$tracker = $this->trackerBuilder->buildMatomoTracker();
|
||||
|
||||
$tracker->setUrl($this->resolveUrlToTrack($visit))
|
||||
$tracker
|
||||
->setUrl($this->resolveUrlToTrack($visit))
|
||||
->setCustomTrackingParameter('type', $visit->type->value)
|
||||
->setUserAgent($visit->userAgent)
|
||||
->setUrlReferrer($visit->referer)
|
||||
@@ -58,7 +59,8 @@ readonly class MatomoVisitSender implements MatomoVisitSenderInterface
|
||||
|
||||
$location = $visit->visitLocation;
|
||||
if ($location !== null) {
|
||||
$tracker->setCity($location->cityName)
|
||||
$tracker
|
||||
->setCity($location->cityName)
|
||||
->setCountry(strtolower($location->countryCode))
|
||||
->setLatitude($location->latitude)
|
||||
->setLongitude($location->longitude);
|
||||
|
||||
@@ -36,9 +36,10 @@ readonly class ShortUrlRedirectionBuilder implements ShortUrlRedirectionBuilderI
|
||||
// names are valid variable names.
|
||||
$currentQuery = Query::parse($request->getUri()->getQuery());
|
||||
|
||||
return $uri->withQuery(
|
||||
$shouldForwardQuery ? $this->resolveQuery($baseQueryString, $currentQuery) : $baseQueryString,
|
||||
)
|
||||
return $uri
|
||||
->withQuery(
|
||||
$shouldForwardQuery ? $this->resolveQuery($baseQueryString, $currentQuery) : $baseQueryString,
|
||||
)
|
||||
->withPath($this->resolvePath($basePath, $extraPath))
|
||||
->__toString();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ readonly class ShortUrlStringifier implements ShortUrlStringifierInterface
|
||||
public function stringify(ShortUrl|ShortUrlIdentifier $shortUrl): string
|
||||
{
|
||||
$shortUrlIdentifier = $shortUrl instanceof ShortUrl ? ShortUrlIdentifier::fromShortUrl($shortUrl) : $shortUrl;
|
||||
$uriWithoutShortCode = new Uri()->withScheme($this->urlShortenerOptions->schema)
|
||||
$uriWithoutShortCode = new Uri()
|
||||
->withScheme($this->urlShortenerOptions->schema)
|
||||
->withHost($this->resolveDomain($shortUrlIdentifier))
|
||||
->withPath($this->basePath)
|
||||
->__toString();
|
||||
|
||||
@@ -17,7 +17,8 @@ class CrawlableShortCodesQuery extends EntitySpecificationRepository implements
|
||||
{
|
||||
$blockSize = 1000;
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('DISTINCT s.shortCode')
|
||||
$qb
|
||||
->select('DISTINCT s.shortCode')
|
||||
->from(ShortUrl::class, 's')
|
||||
->where($qb->expr()->eq('s.crawlable', ':crawlable'))
|
||||
->setParameter('crawlable', true)
|
||||
|
||||
@@ -31,7 +31,8 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
||||
{
|
||||
$buildVisitsSubQuery = function (string $alias, bool $excludingBots): string {
|
||||
$vqb = $this->getEntityManager()->createQueryBuilder();
|
||||
$vqb->select('COALESCE(SUM(' . $alias . '.count), 0)')
|
||||
$vqb
|
||||
->select('COALESCE(SUM(' . $alias . '.count), 0)')
|
||||
->from(ShortUrlVisitsCount::class, $alias)
|
||||
->where($vqb->expr()->eq($alias . '.shortUrl', 's'));
|
||||
|
||||
@@ -43,13 +44,14 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
||||
};
|
||||
|
||||
$qb = $this->createListQueryBuilder($filtering);
|
||||
$qb->select(
|
||||
'DISTINCT s AS shortUrl, d.authority',
|
||||
'(' . $buildVisitsSubQuery('v', excludingBots: false) . ') AS ' . OrderableField::VISITS->value,
|
||||
'(' . $buildVisitsSubQuery('v2', excludingBots: true) . ') AS ' . OrderableField::NON_BOT_VISITS->value,
|
||||
// This is added only to have a consistent order by title between database engines
|
||||
'COALESCE(s.title, \'\') AS title',
|
||||
)
|
||||
$qb
|
||||
->select(
|
||||
'DISTINCT s AS shortUrl, d.authority',
|
||||
'(' . $buildVisitsSubQuery('v', excludingBots: false) . ') AS ' . OrderableField::VISITS->value,
|
||||
'(' . $buildVisitsSubQuery('v2', excludingBots: true) . ') AS ' . OrderableField::NON_BOT_VISITS->value,
|
||||
// This is added only to have a consistent order by title between database engines
|
||||
'COALESCE(s.title, \'\') AS title',
|
||||
)
|
||||
->setMaxResults($filtering->limit)
|
||||
->setFirstResult($filtering->offset)
|
||||
// This param is used in one of the sub-queries, but needs to set in the parent query
|
||||
@@ -90,7 +92,8 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
||||
private function createListQueryBuilder(ShortUrlsCountFiltering $filtering): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->from(ShortUrl::class, 's')
|
||||
$qb
|
||||
->from(ShortUrl::class, 's')
|
||||
->leftJoin('s.domain', 'd')
|
||||
->where('1=1');
|
||||
|
||||
@@ -190,7 +193,8 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
||||
|
||||
$apiKeyName = $filtering->apiKeyName;
|
||||
if ($apiKeyName !== null) {
|
||||
$qb->join('s.authorApiKey', 'a')
|
||||
$qb
|
||||
->join('s.authorApiKey', 'a')
|
||||
->andWhere($qb->expr()->eq('a.name', ':apiKeyName'))
|
||||
->setParameter('apiKeyName', $apiKeyName);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
// Investigate if the case-insensitive check can be done natively by the DB engine.
|
||||
|
||||
$qb = $this->createQueryBuilder('s');
|
||||
$qb->where($qb->expr()->eq($isStrict ? 's.shortCode' : 'LOWER(s.shortCode)', ':shortCode'))
|
||||
$qb
|
||||
->where($qb->expr()->eq($isStrict ? 's.shortCode' : 'LOWER(s.shortCode)', ':shortCode'))
|
||||
->setParameter('shortCode', $isStrict ? $identifier->shortCode : strtolower($identifier->shortCode))
|
||||
->setMaxResults(1);
|
||||
|
||||
@@ -44,7 +45,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
if ($domain === null) {
|
||||
$qb->andWhere($qb->expr()->isNull('s.domain'));
|
||||
} else {
|
||||
$qb->leftJoin('s.domain', 'd')
|
||||
$qb
|
||||
->leftJoin('s.domain', 'd')
|
||||
->andWhere($qb->expr()->orX(
|
||||
$qb->expr()->isNull('s.domain'),
|
||||
$qb->expr()->eq('d.authority', ':domain'),
|
||||
@@ -100,7 +102,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
private function createFindOneQueryBuilder(ShortUrlIdentifier $identifier, Specification|null $spec): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->from(ShortUrl::class, 's')
|
||||
$qb
|
||||
->from(ShortUrl::class, 's')
|
||||
->where($qb->expr()->isNotNull('s.shortCode'))
|
||||
->andWhere($qb->expr()->eq('s.shortCode', ':slug'))
|
||||
->setParameter('slug', $identifier->shortCode)
|
||||
@@ -117,7 +120,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('s')
|
||||
$qb
|
||||
->select('s')
|
||||
->from(ShortUrl::class, 's')
|
||||
->where($qb->expr()->eq('s.longUrl', ':longUrl'))
|
||||
->setParameter('longUrl', $creation->longUrl)
|
||||
@@ -141,7 +145,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
->setParameter('validUntil', $creation->validUntil, ChronosDateTimeType::CHRONOS_DATETIME);
|
||||
}
|
||||
if ($creation->hasDomain()) {
|
||||
$qb->join('s.domain', 'd')
|
||||
$qb
|
||||
->join('s.domain', 'd')
|
||||
->andWhere($qb->expr()->eq('d.authority', ':domain'))
|
||||
->setParameter('domain', $creation->domain);
|
||||
}
|
||||
@@ -161,7 +166,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
|
||||
// If tags where provided, we need an extra join to see the amount of tags that every short URL has, so that we
|
||||
// can discard those that also have more tags, making sure only those fully matching are included.
|
||||
$qb->join('s.tags', 't')
|
||||
$qb
|
||||
->join('s.tags', 't')
|
||||
->groupBy('s')
|
||||
->having($qb->expr()->eq('COUNT(t.id)', ':tagsAmount'))
|
||||
->setParameter('tagsAmount', $tagsAmount);
|
||||
@@ -181,7 +187,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
public function findOneByImportedUrl(ImportedShlinkUrl $url): ShortUrl|null
|
||||
{
|
||||
$qb = $this->createQueryBuilder('s');
|
||||
$qb->andWhere($qb->expr()->eq('s.importOriginalShortCode', ':shortCode'))
|
||||
$qb
|
||||
->andWhere($qb->expr()->eq('s.importOriginalShortCode', ':shortCode'))
|
||||
->setParameter('shortCode', $url->shortCode)
|
||||
->andWhere($qb->expr()->eq('s.importSource', ':importSource'))
|
||||
->setParameter('importSource', $url->source->value)
|
||||
@@ -195,7 +202,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
private function whereDomainIs(QueryBuilder $qb, string|null $domain): void
|
||||
{
|
||||
if ($domain !== null) {
|
||||
$qb->join('s.domain', 'd')
|
||||
$qb
|
||||
->join('s.domain', 'd')
|
||||
->andWhere($qb->expr()->eq('d.authority', ':authority'))
|
||||
->setParameter('authority', $domain);
|
||||
} else {
|
||||
|
||||
@@ -23,8 +23,7 @@ readonly class ShortUrlListService implements ShortUrlListServiceInterface
|
||||
{
|
||||
$defaultDomain = $this->urlShortenerOptions->defaultDomain;
|
||||
$paginator = new Paginator(new ShortUrlRepositoryAdapter($this->repo, $params, $apiKey, $defaultDomain));
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)
|
||||
->setCurrentPage($params->page);
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)->setCurrentPage($params->page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
||||
// For non-restricted API keys, we'll return tags which are not linked to any short URL
|
||||
$joiningMethod = !ApiKey::isShortUrlRestricted($apiKey) ? 'leftJoin' : 'join';
|
||||
$tagsSubQb = $conn->createQueryBuilder();
|
||||
$tagsSubQb->select('t.id AS tag_id', 't.name AS tag', 'COUNT(DISTINCT s.id) AS short_urls_count')
|
||||
$tagsSubQb
|
||||
->select('t.id AS tag_id', 't.name AS tag', 'COUNT(DISTINCT s.id) AS short_urls_count')
|
||||
->from('tags', 't')
|
||||
->groupBy('t.id', 't.name')
|
||||
->{$joiningMethod}('t', 'short_urls_in_tags', 'st', $tagsSubQb->expr()->eq('st.tag_id', 't.id'))
|
||||
@@ -81,14 +82,16 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
||||
$commonJoinCondition = $visitsSubQb->expr()->eq('sc.short_url_id', 'st.short_url_id');
|
||||
$visitsJoin = !$excludeBots
|
||||
? $commonJoinCondition
|
||||
: $visitsSubQb->expr()
|
||||
: $visitsSubQb
|
||||
->expr()
|
||||
->and(
|
||||
$commonJoinCondition,
|
||||
$visitsSubQb->expr()->eq('sc.potential_bot', $conn->quote('0')),
|
||||
)
|
||||
->__toString();
|
||||
|
||||
return $visitsSubQb->select('st.tag_id AS tag_id', 'SUM(sc.count) AS ' . $aggregateAlias)
|
||||
return $visitsSubQb
|
||||
->select('st.tag_id AS tag_id', 'SUM(sc.count) AS ' . $aggregateAlias)
|
||||
->from('short_url_visits_counts', 'sc')
|
||||
->join('sc', 'short_urls_in_tags', 'st', $visitsJoin)
|
||||
->join('sc', 'short_urls', 's', $visitsSubQb->expr()->eq('sc.short_url_id', 's.id'))
|
||||
@@ -105,12 +108,13 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
||||
// sub-queries at "from" and "join" level.
|
||||
// If no sub-query is used, the whole list is loaded even with pagination, making it very inefficient.
|
||||
$mainQb = $conn->createQueryBuilder();
|
||||
$mainQb->select(
|
||||
't.tag AS tag',
|
||||
'COALESCE(v.visits, 0) AS visits', // COALESCE required for postgres to properly order
|
||||
'COALESCE(b.non_bot_visits, 0) AS non_bot_visits',
|
||||
'COALESCE(t.short_urls_count, 0) AS short_urls_count',
|
||||
)
|
||||
$mainQb
|
||||
->select(
|
||||
't.tag AS tag',
|
||||
'COALESCE(v.visits, 0) AS visits', // COALESCE required for postgres to properly order
|
||||
'COALESCE(b.non_bot_visits, 0) AS non_bot_visits',
|
||||
'COALESCE(t.short_urls_count, 0) AS short_urls_count',
|
||||
)
|
||||
->from('(' . $tagsSubQb->getSQL() . ')', 't')
|
||||
->leftJoin('t', '(' . $allVisitsSubQb->getSQL() . ')', 'v', $mainQb->expr()->eq('t.tag_id', 'v.tag_id'))
|
||||
->leftJoin('t', '(' . $nonBotVisitsSubQb->getSQL() . ')', 'b', $mainQb->expr()->eq('t.tag_id', 'b.tag_id'))
|
||||
|
||||
@@ -42,8 +42,7 @@ readonly class TagService implements TagServiceInterface
|
||||
private function createPaginator(AdapterInterface $adapter, TagsParams $params): Paginator
|
||||
{
|
||||
$paginator = new Paginator($adapter);
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)
|
||||
->setCurrentPage($params->page);
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)->setCurrentPage($params->page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,8 @@ final class OrphanVisitsCountTracker
|
||||
|
||||
// For engines without a specific UPSERT syntax, do a regular locked select followed by an insert or update
|
||||
$qb = $conn->createQueryBuilder();
|
||||
$qb->select('id')
|
||||
$qb
|
||||
->select('id')
|
||||
->from('orphan_visits_counts')
|
||||
->where($qb->expr()->and(
|
||||
$qb->expr()->eq('potential_bot', ':potential_bot'),
|
||||
@@ -127,7 +128,8 @@ final class OrphanVisitsCountTracker
|
||||
$visitsCountId = $qb->executeQuery()->fetchOne();
|
||||
|
||||
$writeQb = !$visitsCountId
|
||||
? $conn->createQueryBuilder()
|
||||
? $conn
|
||||
->createQueryBuilder()
|
||||
->insert('orphan_visits_counts')
|
||||
->values([
|
||||
'potential_bot' => ':potential_bot',
|
||||
@@ -135,7 +137,8 @@ final class OrphanVisitsCountTracker
|
||||
])
|
||||
->setParameter('potential_bot', $potentialBot ? '1' : '0')
|
||||
->setParameter('slot_id', $slotId)
|
||||
: $conn->createQueryBuilder()
|
||||
: $conn
|
||||
->createQueryBuilder()
|
||||
->update('orphan_visits_counts')
|
||||
->set('count', 'count + 1')
|
||||
->where($qb->expr()->eq('id', ':visits_count_id'))
|
||||
|
||||
@@ -121,7 +121,8 @@ final class ShortUrlVisitsCountTracker
|
||||
|
||||
// For engines without a specific UPSERT syntax, do a regular locked select followed by an insert or update
|
||||
$qb = $conn->createQueryBuilder();
|
||||
$qb->select('id')
|
||||
$qb
|
||||
->select('id')
|
||||
->from('short_url_visits_counts')
|
||||
->where($qb->expr()->and(
|
||||
$qb->expr()->eq('short_url_id', ':short_url_id'),
|
||||
@@ -140,7 +141,8 @@ final class ShortUrlVisitsCountTracker
|
||||
$visitsCountId = $qb->executeQuery()->fetchOne();
|
||||
|
||||
$writeQb = !$visitsCountId
|
||||
? $conn->createQueryBuilder()
|
||||
? $conn
|
||||
->createQueryBuilder()
|
||||
->insert('short_url_visits_counts')
|
||||
->values([
|
||||
'short_url_id' => ':short_url_id',
|
||||
@@ -150,7 +152,8 @@ final class ShortUrlVisitsCountTracker
|
||||
->setParameter('short_url_id', $shortUrlId)
|
||||
->setParameter('potential_bot', $potentialBot ? '1' : '0')
|
||||
->setParameter('slot_id', $slotId)
|
||||
: $conn->createQueryBuilder()
|
||||
: $conn
|
||||
->createQueryBuilder()
|
||||
->update('short_url_visits_counts')
|
||||
->set('count', 'count + 1')
|
||||
->where($qb->expr()->eq('id', ':visits_count_id'))
|
||||
|
||||
@@ -16,7 +16,8 @@ class ShortUrlVisitsCountRepository extends EntitySpecificationRepository implem
|
||||
public function countNonOrphanVisits(VisitsCountFiltering $filtering): int
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('COALESCE(SUM(vc.count), 0)')
|
||||
$qb
|
||||
->select('COALESCE(SUM(vc.count), 0)')
|
||||
->from(ShortUrlVisitsCount::class, 'vc')
|
||||
->join('vc.shortUrl', 's');
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ class VisitDeleterRepository extends EntitySpecificationRepository implements Vi
|
||||
private function deleteByShortUrl(string $entityName, ShortUrl $shortUrl): int
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->delete($entityName, 'v')
|
||||
$qb
|
||||
->delete($entityName, 'v')
|
||||
->where($qb->expr()->eq('v.shortUrl', ':shortUrl'))
|
||||
->setParameter('shortUrl', $shortUrl);
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ class VisitIterationRepository extends EntitySpecificationRepository implements
|
||||
public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('v')
|
||||
$qb
|
||||
->select('v')
|
||||
->from(Visit::class, 'v')
|
||||
->where($qb->expr()->isNull('v.visitLocation'));
|
||||
|
||||
@@ -35,7 +36,8 @@ class VisitIterationRepository extends EntitySpecificationRepository implements
|
||||
public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('v')
|
||||
$qb
|
||||
->select('v')
|
||||
->from(Visit::class, 'v')
|
||||
->join('v.visitLocation', 'vl')
|
||||
->where($qb->expr()->isNotNull('v.visitLocation'))
|
||||
|
||||
@@ -89,7 +89,8 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
||||
|
||||
// Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later.
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->from(Visit::class, 'v')
|
||||
$qb
|
||||
->from(Visit::class, 'v')
|
||||
->join('v.shortUrl', 's')
|
||||
->join('s.tags', 't')
|
||||
->where($qb->expr()->eq('t.name', $conn->quote($tag)));
|
||||
@@ -270,7 +271,8 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
||||
// TODO Order by date and ID, not just by ID (order by date DESC, id DESC).
|
||||
// That ensures imported visits are properly ordered even if inserted in wrong chronological order.
|
||||
|
||||
$qb->select('v.id')
|
||||
$qb
|
||||
->select('v.id')
|
||||
->orderBy('v.id', 'DESC')
|
||||
// Falling back to values that will behave as no limit/offset, but will work around MS SQL not allowing
|
||||
// order on sub-queries without offset
|
||||
@@ -282,7 +284,8 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
||||
// sub-queries at "from" and "join" level.
|
||||
// If no sub-query is used, then performance drops dramatically while the "offset" grows.
|
||||
$nativeQb = $this->getEntityManager()->getConnection()->createQueryBuilder();
|
||||
$nativeQb->select('v.id AS visit_id', 'v.*', 'vl.*')
|
||||
$nativeQb
|
||||
->select('v.id AS visit_id', 'v.*', 'vl.*')
|
||||
->from('visits', 'v')
|
||||
// @phpstan-ignore-next-line
|
||||
->join('v', '(' . $subQuery . ')', 'sq', $nativeQb->expr()->eq('sq.id_0', 'v.id'))
|
||||
|
||||
@@ -140,8 +140,7 @@ readonly class VisitsStatsHelper implements VisitsStatsHelperInterface
|
||||
private function createPaginator(AdapterInterface $adapter, VisitsParams $params): Paginator
|
||||
{
|
||||
$paginator = new Paginator($adapter);
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)
|
||||
->setCurrentPage($params->page);
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)->setCurrentPage($params->page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
@@ -68,13 +68,15 @@ class NotFoundRedirectHandlerTest extends TestCase
|
||||
MockObject&DomainServiceInterface $domainService,
|
||||
MockObject&NotFoundRedirectResolverInterface $resolver,
|
||||
) use ($once): void {
|
||||
$domainService->expects($once())
|
||||
$domainService
|
||||
->expects($once())
|
||||
->method('findByAuthority')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
null,
|
||||
);
|
||||
$resolver->expects($once())
|
||||
$resolver
|
||||
->expects($once())
|
||||
->method('resolveRedirectResponse')
|
||||
->with(
|
||||
self::isInstanceOf(NotFoundType::class),
|
||||
@@ -87,14 +89,16 @@ class NotFoundRedirectHandlerTest extends TestCase
|
||||
MockObject&DomainServiceInterface $domainService,
|
||||
MockObject&NotFoundRedirectResolverInterface $resolver,
|
||||
) use ($once, $exactly): void {
|
||||
$domainService->expects($once())
|
||||
$domainService
|
||||
->expects($once())
|
||||
->method('findByAuthority')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
Domain::withAuthority(''),
|
||||
);
|
||||
$callCount = 0;
|
||||
$resolver->expects($exactly(2))
|
||||
$resolver
|
||||
->expects($exactly(2))
|
||||
->method('resolveRedirectResponse')
|
||||
->willReturnCallback(
|
||||
static function (mixed $arg1, mixed $arg2, mixed $arg3) use (&$callCount) {
|
||||
|
||||
@@ -185,7 +185,8 @@ class NotifyVisitToRabbitMqTest extends TestCase
|
||||
): void {
|
||||
$update = Update::forTopicAndPayload('', []);
|
||||
$updatesGenerator->expects($never())->method('newOrphanVisitUpdate');
|
||||
$updatesGenerator->expects($once())
|
||||
$updatesGenerator
|
||||
->expects($once())
|
||||
->method('newVisitUpdate')
|
||||
->withAnyParameters()
|
||||
->willReturn(
|
||||
|
||||
@@ -382,7 +382,8 @@ class ImportedLinksProcessorTest extends TestCase
|
||||
);
|
||||
|
||||
$visitRepo = $this->createMock(VisitRepository::class);
|
||||
$visitRepo->expects($importOrphanVisits ? $this->once() : $this->never())
|
||||
$visitRepo
|
||||
->expects($importOrphanVisits ? $this->once() : $this->never())
|
||||
->method(
|
||||
'findMostRecentOrphanVisit',
|
||||
)
|
||||
|
||||
@@ -54,24 +54,28 @@ class MatomoVisitSenderTest extends TestCase
|
||||
$tracker->expects($this->once())->method('setUrl')->willReturn($tracker);
|
||||
$tracker->expects($this->once())->method('setUserAgent')->willReturn($tracker);
|
||||
$tracker->expects($this->once())->method('setUrlReferrer')->willReturn($tracker);
|
||||
$tracker->expects($this->once())
|
||||
$tracker
|
||||
->expects($this->once())
|
||||
->method('doTrackPageView')
|
||||
->with($visit->shortUrl?->title() ?? '');
|
||||
$tracker->expects($this->once())
|
||||
$tracker
|
||||
->expects($this->once())
|
||||
->method('setForceVisitDateTime')
|
||||
->with(
|
||||
$visit->date->setTimezone('UTC')->toDateTimeString(),
|
||||
);
|
||||
|
||||
if ($visit->isOrphan()) {
|
||||
$tracker->expects($this->exactly(2))
|
||||
$tracker
|
||||
->expects($this->exactly(2))
|
||||
->method('setCustomTrackingParameter')
|
||||
->willReturnMap([
|
||||
['type', $visit->type->value, $tracker],
|
||||
['orphan', 'true', $tracker],
|
||||
]);
|
||||
} else {
|
||||
$tracker->expects($this->once())
|
||||
$tracker
|
||||
->expects($this->once())
|
||||
->method('setCustomTrackingParameter')
|
||||
->with(
|
||||
'type',
|
||||
@@ -81,7 +85,8 @@ class MatomoVisitSenderTest extends TestCase
|
||||
}
|
||||
|
||||
foreach ($invokedMethods as $invokedMethod => $args) {
|
||||
$tracker->expects($this->once())
|
||||
$tracker
|
||||
->expects($this->once())
|
||||
->method($invokedMethod)
|
||||
->with(...array_values($args))
|
||||
->willReturn(
|
||||
|
||||
@@ -46,7 +46,8 @@ class ShortUrlRedirectRuleServiceTest extends TestCase
|
||||
];
|
||||
|
||||
$repo = $this->createMock(EntityRepository::class);
|
||||
$repo->expects($this->once())
|
||||
$repo
|
||||
->expects($this->once())
|
||||
->method('findBy')
|
||||
->with(
|
||||
['shortUrl' => $shortUrl],
|
||||
@@ -107,7 +108,8 @@ class ShortUrlRedirectRuleServiceTest extends TestCase
|
||||
$data = new RedirectRulesData([]);
|
||||
|
||||
$repo = $this->createMock(EntityRepository::class);
|
||||
$repo->expects($this->once())
|
||||
$repo
|
||||
->expects($this->once())
|
||||
->method('findBy')
|
||||
->with(
|
||||
['shortUrl' => $shortUrl],
|
||||
|
||||
@@ -77,7 +77,8 @@ class PersistenceShortUrlRelationResolverTest extends TestCase
|
||||
$expectedPersistedTags = $expectedLookedOutTags - 1;
|
||||
|
||||
$tagRepo = $this->createMock(TagRepository::class);
|
||||
$tagRepo->expects($this->exactly($expectedLookedOutTags))
|
||||
$tagRepo
|
||||
->expects($this->exactly($expectedLookedOutTags))
|
||||
->method('findOneBy')
|
||||
->with(
|
||||
$this->isArray(),
|
||||
|
||||
@@ -63,7 +63,8 @@ class VisitsStatsHelperTest extends TestCase
|
||||
{
|
||||
$callCount = 0;
|
||||
$visitsCountRepo = $this->createMock(ShortUrlVisitsCountRepository::class);
|
||||
$visitsCountRepo->expects($this->exactly(2))
|
||||
$visitsCountRepo
|
||||
->expects($this->exactly(2))
|
||||
->method('countNonOrphanVisits')
|
||||
->willReturnCallback(
|
||||
static function (VisitsCountFiltering $options) use ($expectedCount, $apiKey, &$callCount) {
|
||||
@@ -76,7 +77,8 @@ class VisitsStatsHelperTest extends TestCase
|
||||
);
|
||||
|
||||
$orphanVisitsCountRepo = $this->createMock(OrphanVisitsCountRepository::class);
|
||||
$orphanVisitsCountRepo->expects($this->exactly(2))
|
||||
$orphanVisitsCountRepo
|
||||
->expects($this->exactly(2))
|
||||
->method('countOrphanVisits')
|
||||
->with(
|
||||
$this->isInstanceOf(VisitsCountFiltering::class),
|
||||
@@ -266,13 +268,15 @@ class VisitsStatsHelperTest extends TestCase
|
||||
{
|
||||
$list = array_map(static fn () => Visit::forBasePath(Visitor::empty()), range(0, 3));
|
||||
$repo = $this->createMock(VisitRepository::class);
|
||||
$repo->expects($this->once())
|
||||
$repo
|
||||
->expects($this->once())
|
||||
->method('countOrphanVisits')
|
||||
->with(
|
||||
$this->isInstanceOf(OrphanVisitsCountFiltering::class),
|
||||
)
|
||||
->willReturn(count($list));
|
||||
$repo->expects($this->once())
|
||||
$repo
|
||||
->expects($this->once())
|
||||
->method('findOrphanVisits')
|
||||
->with(
|
||||
$this->isInstanceOf(OrphanVisitsListFiltering::class),
|
||||
@@ -293,13 +297,15 @@ class VisitsStatsHelperTest extends TestCase
|
||||
range(0, 3),
|
||||
);
|
||||
$repo = $this->createMock(VisitRepository::class);
|
||||
$repo->expects($this->once())
|
||||
$repo
|
||||
->expects($this->once())
|
||||
->method('countNonOrphanVisits')
|
||||
->with(
|
||||
$this->isInstanceOf(WithDOmainVisitsCountFiltering::class),
|
||||
)
|
||||
->willReturn(count($list));
|
||||
$repo->expects($this->once())
|
||||
$repo
|
||||
->expects($this->once())
|
||||
->method('findNonOrphanVisits')
|
||||
->with(
|
||||
$this->isInstanceOf(WithDOmainVisitsListFiltering::class),
|
||||
|
||||
@@ -13,34 +13,37 @@ use Shlinkio\Shlink\Rest\Entity\ApiKeyRole;
|
||||
return static function (ClassMetadata $metadata): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('api_keys')
|
||||
->setCustomRepositoryClass(ApiKey\Repository\ApiKeyRepository::class);
|
||||
$builder->setTable('api_keys')->setCustomRepositoryClass(ApiKey\Repository\ApiKeyRepository::class);
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('key', Types::STRING)
|
||||
$builder
|
||||
->createField('key', Types::STRING)
|
||||
->columnName('`key`')
|
||||
->unique()
|
||||
->build();
|
||||
|
||||
$builder->createField('name', Types::STRING)
|
||||
$builder
|
||||
->createField('name', Types::STRING)
|
||||
->columnName('`name`')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('expirationDate', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
$builder
|
||||
->createField('expirationDate', ChronosDateTimeType::CHRONOS_DATETIME)
|
||||
->columnName('expiration_date')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('enabled', Types::BOOLEAN)
|
||||
->build();
|
||||
$builder->createField('enabled', Types::BOOLEAN)->build();
|
||||
|
||||
$builder->createOneToMany('roles', ApiKeyRole::class)
|
||||
$builder
|
||||
->createOneToMany('roles', ApiKeyRole::class)
|
||||
->mappedBy('apiKey')
|
||||
->setIndexBy('role')
|
||||
->cascadePersist()
|
||||
|
||||
@@ -16,7 +16,8 @@ return static function (ClassMetadata $metadata): void {
|
||||
|
||||
$builder->setTable('api_key_roles');
|
||||
|
||||
$builder->createField('id', Types::BIGINT)
|
||||
$builder
|
||||
->createField('id', Types::BIGINT)
|
||||
->makePrimaryKey()
|
||||
->generatedValue('IDENTITY')
|
||||
->option('unsigned', true)
|
||||
@@ -26,17 +27,20 @@ return static function (ClassMetadata $metadata): void {
|
||||
'fieldName' => 'role',
|
||||
'type' => Types::STRING,
|
||||
'enumType' => Role::class,
|
||||
])->columnName('role_name')
|
||||
])
|
||||
->columnName('role_name')
|
||||
->length(255)
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder->createField('meta', Types::JSON)
|
||||
$builder
|
||||
->createField('meta', Types::JSON)
|
||||
->columnName('meta')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('apiKey', ApiKey::class)
|
||||
$builder
|
||||
->createManyToOne('apiKey', ApiKey::class)
|
||||
->addJoinColumn('api_key_id', 'id', false, false, 'CASCADE')
|
||||
->cascadePersist()
|
||||
->build();
|
||||
|
||||
@@ -20,7 +20,8 @@ class ApiKeyRepository extends EntitySpecificationRepository implements ApiKeyRe
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
return $em->wrapInTransaction(static function () use ($apiKey, $em): ApiKey|null {
|
||||
$firstResult = $em->createQueryBuilder()
|
||||
$firstResult = $em
|
||||
->createQueryBuilder()
|
||||
->select('a.id')
|
||||
->from(ApiKey::class, 'a')
|
||||
->setMaxResults(1)
|
||||
@@ -44,7 +45,8 @@ class ApiKeyRepository extends EntitySpecificationRepository implements ApiKeyRe
|
||||
public function nameExists(string $name): bool
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('a.id')
|
||||
$qb
|
||||
->select('a.id')
|
||||
->from(ApiKey::class, 'a')
|
||||
->setMaxResults(1);
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ readonly class DropDefaultDomainFromRequestMiddleware implements MiddlewareInter
|
||||
{
|
||||
/** @var array $body */
|
||||
$body = $request->getParsedBody() ?? [];
|
||||
$request = $request->withQueryParams($this->sanitizeDomainFromPayload($request->getQueryParams()))
|
||||
$request = $request
|
||||
->withQueryParams($this->sanitizeDomainFromPayload($request->getQueryParams()))
|
||||
->withParsedBody($this->sanitizeDomainFromPayload($body));
|
||||
|
||||
return $handler->handle($request);
|
||||
|
||||
@@ -46,7 +46,8 @@ class ResolveShortUrlActionTest extends TestCase
|
||||
)
|
||||
->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar'));
|
||||
|
||||
$request = new ServerRequest()->withAttribute('shortCode', $shortCode)
|
||||
$request = new ServerRequest()
|
||||
->withAttribute('shortCode', $shortCode)
|
||||
->withAttribute(ApiKey::class, $apiKey);
|
||||
$response = $this->action->handle($request);
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ class ShortUrlVisitsActionTest extends TestCase
|
||||
->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
$response = $this->action->handle(
|
||||
$this->requestWithApiKey()
|
||||
$this
|
||||
->requestWithApiKey()
|
||||
->withAttribute('shortCode', $shortCode)
|
||||
->withQueryParams([
|
||||
'endDate' => '2016-01-01 00:00:00',
|
||||
|
||||
@@ -68,10 +68,11 @@ class AuthenticationMiddlewareTest extends TestCase
|
||||
new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, HealthAction::class),
|
||||
),
|
||||
)];
|
||||
yield 'OPTIONS method' => [new ServerRequest()->withAttribute(
|
||||
RouteResult::class,
|
||||
RouteResult::fromRoute(new Route('bar', $dummyMiddleware), []),
|
||||
)
|
||||
yield 'OPTIONS method' => [new ServerRequest()
|
||||
->withAttribute(
|
||||
RouteResult::class,
|
||||
RouteResult::fromRoute(new Route('bar', $dummyMiddleware), []),
|
||||
)
|
||||
->withMethod(RequestMethodInterface::METHOD_OPTIONS)];
|
||||
}
|
||||
|
||||
|
||||
@@ -68,10 +68,12 @@ class BodyParserMiddlewareTest extends TestCase
|
||||
{
|
||||
$body = new Stream('php://temp', 'wr');
|
||||
$body->write('{"foo": "bar", "bar": ["one", 5]}');
|
||||
$request = new ServerRequest()->withMethod('PUT')
|
||||
$request = new ServerRequest()
|
||||
->withMethod('PUT')
|
||||
->withBody($body);
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
$handler->expects($this->once())
|
||||
$handler
|
||||
->expects($this->once())
|
||||
->method('handle')
|
||||
->with(
|
||||
$this->isInstanceOf(ServerRequestInterface::class),
|
||||
@@ -93,7 +95,8 @@ class BodyParserMiddlewareTest extends TestCase
|
||||
{
|
||||
$body = new Stream('php://temp', 'wr');
|
||||
$body->write('{"foo": "bar", "bar": ["one');
|
||||
$request = new ServerRequest()->withMethod('PUT')
|
||||
$request = new ServerRequest()
|
||||
->withMethod('PUT')
|
||||
->withBody($body);
|
||||
|
||||
$handler = $this->createMock(RequestHandlerInterface::class);
|
||||
|
||||
@@ -63,7 +63,8 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
public function optionsRequestIncludesMoreHeaders(): void
|
||||
{
|
||||
$originalResponse = new Response();
|
||||
$request = new ServerRequest()->withMethod('OPTIONS')
|
||||
$request = new ServerRequest()
|
||||
->withMethod('OPTIONS')
|
||||
->withHeader('Origin', 'local')
|
||||
->withHeader('Access-Control-Request-Headers', 'foo, bar, baz');
|
||||
$this->handler->expects($this->once())->method('handle')->willReturn($originalResponse);
|
||||
@@ -89,7 +90,8 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
if ($allowHeader !== null) {
|
||||
$originalResponse = $originalResponse->withHeader('Allow', $allowHeader);
|
||||
}
|
||||
$request = new ServerRequest()->withHeader('Origin', 'local')
|
||||
$request = new ServerRequest()
|
||||
->withHeader('Origin', 'local')
|
||||
->withMethod('OPTIONS');
|
||||
$this->handler->expects($this->once())->method('handle')->willReturn($originalResponse);
|
||||
|
||||
@@ -113,7 +115,8 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
int $expectedStatus,
|
||||
): void {
|
||||
$originalResponse = new Response()->withStatus($status);
|
||||
$request = new ServerRequest()->withMethod($method)
|
||||
$request = new ServerRequest()
|
||||
->withMethod($method)
|
||||
->withHeader('Origin', 'local');
|
||||
$this->handler->expects($this->once())->method('handle')->willReturn($originalResponse);
|
||||
|
||||
@@ -151,7 +154,8 @@ class CrossDomainMiddlewareTest extends TestCase
|
||||
public function credentialsAreAllowedIfConfiguredSo(bool $allowCredentials, string $method): void
|
||||
{
|
||||
$originalResponse = new Response();
|
||||
$request = new ServerRequest()->withMethod($method)
|
||||
$request = new ServerRequest()
|
||||
->withMethod($method)
|
||||
->withHeader('Origin', 'local');
|
||||
$this->handler->expects($this->once())->method('handle')->willReturn($originalResponse);
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Coding standard">
|
||||
<description>Coding standard</description>
|
||||
|
||||
<!-- display progress -->
|
||||
<arg value="p" />
|
||||
<arg name="colors" />
|
||||
|
||||
<!-- inherit rules from: -->
|
||||
<rule ref="Shlinkio" />
|
||||
|
||||
<!-- Paths to check -->
|
||||
<file>bin</file>
|
||||
<file>module</file>
|
||||
<file>config</file>
|
||||
<file>docker/config</file>
|
||||
<file>public/index.php</file>
|
||||
|
||||
<!-- Paths to exclude -->
|
||||
<exclude-pattern>config/params/*</exclude-pattern>
|
||||
</ruleset>
|
||||
Reference in New Issue
Block a user