Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing code-less metadata updater and updating dependencies #15

Merged
merged 1 commit into from
Jan 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .do/deploy.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ spec:
- key: COLLECTION_HIDDEN_ASSET_EXTENSION
scope: RUN_TIME
value: gif
- key: METADATA_TEMPLATE
scope: RUN_TIME
value: null
- key: CACHE_EXPIRATION
scope: RUN_TIME
value: '600'
Expand Down
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ COLLECTION_WEBSITE=https://www.example.com/
COLLECTION_MAX_SUPPLY=10000
COLLECTION_ASSETS_EXTENSION=png
COLLECTION_HIDDEN_ASSET_EXTENSION=gif
METADATA_TEMPLATE=null

# Used for TotalSupplyProviers and HTTP headers (default: 10 minutes)
CACHE_EXPIRATION=600
Expand Down
1,438 changes: 719 additions & 719 deletions composer.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ services:
# [END] Safe NFT Metadata Provider configuration
#

App\MetadataUpdater\TemplatedMetadataUpdater:
arguments:
- '%env(json:METADATA_TEMPLATE)%'

# Collection manager service
App\Service\CollectionManager:
arguments:
Expand Down
2 changes: 1 addition & 1 deletion ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"\n",
array_map(
fn ($line) => trim($line, '/* '),
explode("\n", file_get_contents(__DIR__.'/license-header-template.txt')),
explode("\n", (string) file_get_contents(__DIR__.'/license-header-template.txt')),
),
),
),
Expand Down
7 changes: 7 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ parameters:
# Fix class not found errors for PhpCsFixer
- vendor/symplify/easy-coding-standard/vendor/autoload.php

ignoreErrors:
-
message: '#The command is missing \$this->setName\("\.\.\."\) in configure\(\) method#'
paths:
- src/Command/ExportMetadataCommand.php
- src/Command/ShuffleCollectionCommand.php

includes:
##- vendor/symplify/phpstan-rules/config/array-rules.neon
#- vendor/symplify/phpstan-rules/config/code-complexity-rules.neon
Expand Down
14 changes: 12 additions & 2 deletions src/Command/ExportAssetsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@
* @author Marco Lipparini <developer@liarco.net>
*/
#[AsCommand(
name: 'nft:export-assets',
description: 'Exports a new assets folder with all the files shuffling them using the current mapping (if any)',
name: self::NAME,
description: self::DESCRIPTION,
)]
class ExportAssetsCommand extends Command
{
/**
* @var string
*/
final public const NAME = 'nft:export-assets';

/**
* @var string
*/
final public const DESCRIPTION = 'Exports a new assets folder with all the files shuffling them using the current mapping (if any)';

public function __construct(
private readonly CollectionManager $collectionManager,
string $name = null,
Expand Down
14 changes: 12 additions & 2 deletions src/Command/ExportMetadataCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@
* @author Marco Lipparini <developer@liarco.net>
*/
#[AsCommand(
name: 'nft:export-metadata',
description: 'Exports a new metadata folder with all the files updating and shuffling them using the current mapping (if any)',
name: self::NAME,
description: self::DESCRIPTION,
)]
class ExportMetadataCommand extends Command
{
/**
* @var string
*/
final public const NAME = 'nft:export-metadata';

/**
* @var string
*/
final public const DESCRIPTION = 'Exports a new metadata folder with all the files updating and shuffling them using the current mapping (if any)';

/**
* @var string
*/
Expand Down
14 changes: 12 additions & 2 deletions src/Command/ShuffleCollectionCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,21 @@
* @author Marco Lipparini <developer@liarco.net>
*/
#[AsCommand(
name: 'nft:shuffle-collection',
description: 'Generates a new shuffle mapping for all the tokens (or a given range)',
name: self::NAME,
description: self::DESCRIPTION,
)]
class ShuffleCollectionCommand extends Command
{
/**
* @var string
*/
final public const NAME = 'nft:shuffle-collection';

/**
* @var string
*/
final public const DESCRIPTION = 'Generates a new shuffle mapping for all the tokens (or a given range)';

/**
* @var string
*/
Expand Down
14 changes: 12 additions & 2 deletions src/Command/TotalSupplyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@
* @author Marco Lipparini <developer@liarco.net>
*/
#[AsCommand(
name: 'nft:total-supply',
description: 'Returns the current total supply',
name: self::NAME,
description: self::DESCRIPTION,
)]
class TotalSupplyCommand extends Command
{
/**
* @var string
*/
final public const NAME = 'nft:total-supply';

/**
* @var string
*/
final public const DESCRIPTION = 'Returns the current total supply';

public function __construct(
private readonly CachedTotalSupplyProvider $cachedTotalSupplyProvider,
string $name = null,
Expand Down
24 changes: 10 additions & 14 deletions src/FilesystemDriver/LocalFilesystemDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@
*/
final class LocalFilesystemDriver implements CollectionFilesystemDriverInterface
{
/**
* @var int[]
*/
private array $shuffleMapping = [];

public function __construct(
private readonly string $localCollectionPath,
private readonly string $assetsExtension,
Expand Down Expand Up @@ -126,20 +121,21 @@ public function getAbi(): array

public function getShuffleMapping(): ?array
{
if (empty($this->shuffleMapping)) {
$mappingPath = $this->localCollectionPath.self::MAPPING_PATH;
$mappingPath = $this->localCollectionPath.self::MAPPING_PATH;

if (! is_file($mappingPath)) {
return null;
}
if (! is_file($mappingPath)) {
return null;
}

/** @var int[] $shuffleMappingData */
$shuffleMappingData = Json::decode(FileSystem::read($mappingPath), Json::FORCE_ARRAY);
$shuffleMapping = Json::decode(FileSystem::read($mappingPath), Json::FORCE_ARRAY);

$this->shuffleMapping = $shuffleMappingData;
if (! is_array($shuffleMapping)) {
throw new LogicException('Unexpected shuffle mapping value (it must be an array).');
}

return $this->shuffleMapping;
/** @var int[] $shuffleMapping */

return $shuffleMapping;
}

public function storeNewShuffleMapping(array $newShuffleMapping): void
Expand Down
35 changes: 15 additions & 20 deletions src/FilesystemDriver/S3FilesystemDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ final class S3FilesystemDriver implements CollectionFilesystemDriverInterface

private readonly S3Client $s3Client;

/**
* @var int[]
*/
private array $shuffleMapping = [];

public function __construct(
readonly string $region,
readonly string $endpointUrl,
Expand Down Expand Up @@ -166,23 +161,23 @@ public function getAbi(): array

public function getShuffleMapping(): ?array
{
if (empty($this->shuffleMapping)) {
try {
/** @var int[] $shuffleMappingData */
$shuffleMappingData = Json::decode(
$this->getObject(self::MAPPING_PATH)->contents,
Json::FORCE_ARRAY,
);

$this->shuffleMapping = $shuffleMappingData;
} catch (S3Exception $s3Exception) {
if (self::KEY_NOT_FOUND_ERROR_CODE === $s3Exception->getAwsErrorCode()) {
return null;
}
try {
$shuffleMapping = Json::decode($this->getObject(self::MAPPING_PATH)->contents, Json::FORCE_ARRAY);

if (! is_array($shuffleMapping)) {
throw new LogicException('Unexpected shuffle mapping value (it must be an array).');
}
}

return $this->shuffleMapping;
/** @var int[] $shuffleMapping */

return $shuffleMapping;
} catch (S3Exception $s3Exception) {
if (self::KEY_NOT_FOUND_ERROR_CODE === $s3Exception->getAwsErrorCode()) {
return null;
}

throw $s3Exception;
}
}

public function storeNewShuffleMapping(array $newShuffleMapping): void
Expand Down
29 changes: 0 additions & 29 deletions src/MetadataUpdater/CustomMetadataUpdater.php

This file was deleted.

89 changes: 89 additions & 0 deletions src/MetadataUpdater/TemplatedMetadataUpdater.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

/*
* This file is part of the Safe NFT Metadata Provider package.
*
* (c) Marco Lipparini <developer@liarco.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace App\MetadataUpdater;

use App\Contract\MetadataUpdaterInterface;
use RuntimeException;

/**
* This metadata updater replaces each metadata key with the values found inside the given JSON template.
* Any key which is not found in the template is left as it is.
*
* Each template value also supports the replacement of the following placeholders:
* - {TOKEN_ID}
* - {INT_TOKEN_ID} (a value matching this string exactly will be replaced with the token ID as an integer value)
* - {ASSET_URI} (please remember that the "image" key is already replaced by default!)
*
* Limitations: this updater supports first-level keys only.
*
* Template example:
* {
* "name": "My awesome token #{TOKEN_ID}"
* }
*
* @author Marco Lipparini <developer@liarco.net>
*/
final class TemplatedMetadataUpdater implements MetadataUpdaterInterface
{
/**
* @var string
*/
private const TOKEN_ID_PLACEHOLDER = '{TOKEN_ID}';

/**
* @var string
*/
private const INT_TOKEN_ID_PLACEHOLDER = '{INT_TOKEN_ID}';

/**
* @var string
*/
private const ASSET_URI_PLACEHOLDER = '{ASSET_URI}';

/**
* @param array<string, string> $template
*/
public function __construct(
private readonly ?array $template,
) {
}

public function updateMetadata(array &$metadata, int $tokenId, string $assetUri): void
{
if (null === $this->template) {
return;
}

foreach ($this->template as $key => $value) {
if (! is_string($value) || (isset($metadata[$key]) && ! is_string($metadata[$key]))) {
throw new RuntimeException('Deep level replacement is not supported in METADATA_TEMPLATE.');
}

$metadata[$key] = $this->replacePlaceholders($value, $tokenId, $assetUri);
}
}

private function replacePlaceholders(string $value, int $tokenId, string $assetUri): string|int
{
if (self::INT_TOKEN_ID_PLACEHOLDER === $value) {
return $tokenId;
}

return str_replace(
[self::TOKEN_ID_PLACEHOLDER, self::ASSET_URI_PLACEHOLDER],
[(string) $tokenId, $assetUri],
$value,
);
}
}