diff --git a/src/GraphQL/Mutations/AddTokensMutation.php b/src/GraphQL/Mutations/AddTokensMutation.php index 3b5e3cc..8c89dfa 100644 --- a/src/GraphQL/Mutations/AddTokensMutation.php +++ b/src/GraphQL/Mutations/AddTokensMutation.php @@ -3,31 +3,21 @@ namespace Enjin\Platform\Beam\GraphQL\Mutations; use Closure; -use Enjin\Platform\Beam\Enums\BeamType; use Enjin\Platform\Beam\GraphQL\Traits\HasBeamCommonFields; +use Enjin\Platform\Beam\GraphQL\Traits\HasTokenInputRules; use Enjin\Platform\Beam\Models\Beam; use Enjin\Platform\Beam\Rules\BeamExists; -use Enjin\Platform\Beam\Rules\MaxTokenCount; -use Enjin\Platform\Beam\Rules\MaxTokenSupply; -use Enjin\Platform\Beam\Rules\TokensDoNotExistInBeam; -use Enjin\Platform\Beam\Rules\TokensDoNotExistInCollection; -use Enjin\Platform\Beam\Rules\TokensExistInCollection; -use Enjin\Platform\Beam\Rules\TokenUploadExistInCollection; -use Enjin\Platform\Beam\Rules\TokenUploadNotExistInBeam; -use Enjin\Platform\Beam\Rules\TokenUploadNotExistInCollection; -use Enjin\Platform\Beam\Rules\UniqueTokenIds; use Enjin\Platform\Beam\Services\BeamService; -use Enjin\Platform\Rules\DistinctAttributes; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; -use Illuminate\Validation\Rule; use Rebing\GraphQL\Support\Facades\GraphQL; class AddTokensMutation extends Mutation { use HasBeamCommonFields; + use HasTokenInputRules; /** * Get the mutation's attributes. @@ -59,9 +49,13 @@ public function args(): array 'description' => __('enjin-platform-beam::mutation.claim_beam.args.code'), ], 'tokens' => [ - 'type' => GraphQL::type('[ClaimToken!]!'), + 'type' => GraphQL::type('[ClaimToken!]'), 'description' => __('enjin-platform-beam::input_type.claim_token.description'), ], + 'packs' => [ + 'type' => GraphQL::type('[BeamPackInput!]'), + 'description' => __('enjin-platform-beam::input_type.beam_pack.description'), + ], ]; } @@ -76,7 +70,13 @@ public function resolve( Closure $getSelectFields, BeamService $beam ) { - return DB::transaction(fn () => $beam->addTokens($args['code'], $args['tokens'])); + return DB::transaction( + fn () => $beam->addTokens( + $args['code'], + Arr::get($args, 'tokens', []), + Arr::get($args, 'packs', []) + ) + ); } /** @@ -92,61 +92,11 @@ protected function rules(array $args = []): array 'max:1024', new BeamExists(), ], - 'tokens' => ['bail', 'array', 'min:1', new UniqueTokenIds()], - 'tokens.*.attributes' => Rule::forEach(function ($value, $attribute) use ($args) { - if (empty($value)) { - return []; - } - - return [ - 'nullable', - 'bail', - 'array', - 'min:1', - 'max:10', - new DistinctAttributes(), - Rule::prohibitedIf(BeamType::getEnumCase(Arr::get($args, str_replace('attributes', 'type', $attribute))) == BeamType::TRANSFER_TOKEN), - ]; - }), - 'tokens.*.attributes.*.key' => 'max:255', - 'tokens.*.attributes.*.value' => 'max:1000', - 'tokens.*.tokenIds' => Rule::forEach(function ($value, $attribute) use ($args, $beam) { - return [ - 'bail', - 'required_without:tokens.*.tokenIdDataUpload', - 'prohibits:tokens.*.tokenIdDataUpload', - 'distinct', - BeamType::getEnumCase(Arr::get($args, str_replace('tokenIds', 'type', $attribute))) == BeamType::TRANSFER_TOKEN - ? new TokensExistInCollection($beam?->collection_chain_id) - : new TokensDoNotExistInCollection($beam?->collection_chain_id), - new TokensDoNotExistInBeam($beam), - ]; - }), - 'tokens.*.tokenIdDataUpload' => Rule::forEach(function ($value, $attribute) use ($args, $beam) { - return [ - 'bail', - 'required_without:tokens.*.tokenIds', - 'prohibits:tokens.*.tokenIds', - BeamType::getEnumCase(Arr::get($args, str_replace('tokenIdDataUpload', 'type', $attribute))) == BeamType::TRANSFER_TOKEN - ? new TokenUploadExistInCollection($beam?->collection_chain_id) - : new TokenUploadNotExistInCollection($beam?->collection_chain_id), - new TokenUploadNotExistInBeam($beam), - ]; - }), - 'tokens.*.tokenQuantityPerClaim' => [ - 'bail', - 'filled', - 'integer', - 'min:1', - new MaxTokenSupply($beam?->collection_chain_id), - ], - 'tokens.*.claimQuantity' => [ - 'bail', - 'filled', - 'integer', - 'min:1', - new MaxTokenCount($beam?->collection_chain_id), - ], + ...match (true) { + !$beam => [], + !$beam?->is_pack => $this->tokenRules($args, $beam?->collection_chain_id), + default => $this->packTokenRules($args, $beam?->collection_chain_id), + }, ]; } } diff --git a/src/GraphQL/Mutations/CreateBeamMutation.php b/src/GraphQL/Mutations/CreateBeamMutation.php index 439ca0f..0f35b10 100644 --- a/src/GraphQL/Mutations/CreateBeamMutation.php +++ b/src/GraphQL/Mutations/CreateBeamMutation.php @@ -104,8 +104,8 @@ function (string $attribute, mixed $value, Closure $fail) { new IsCollectionOwnerOrApproved(), ], 'flags.*.flag' => ['required', 'distinct'], - ...$this->tokenRules($args, $args['collectionId']), - ...$this->packTokenRules($args, $args['collectionId']), + ...$this->tokenRules($args, $args['collectionId'], true), + ...$this->packTokenRules($args, $args['collectionId'], true), ]; } } diff --git a/src/GraphQL/Mutations/UpdateBeamMutation.php b/src/GraphQL/Mutations/UpdateBeamMutation.php index 74a4a6c..a08ca6f 100644 --- a/src/GraphQL/Mutations/UpdateBeamMutation.php +++ b/src/GraphQL/Mutations/UpdateBeamMutation.php @@ -107,8 +107,8 @@ protected function rules(array $args = []): array 'end' => ['filled', 'date', new IsEndDateValid()], ...match (true) { !$beam => [], - !$beam?->is_pack => $this->tokenRules($args, $beam?->collection_chain_id, false), - default => $this->packTokenRules($args, $beam?->collection_chain_id, false), + !$beam?->is_pack => $this->tokenRules($args, $beam?->collection_chain_id), + default => $this->packTokenRules($args, $beam?->collection_chain_id), }, ]; } diff --git a/src/GraphQL/Traits/HasTokenInputRules.php b/src/GraphQL/Traits/HasTokenInputRules.php index 68de02d..82b4f41 100644 --- a/src/GraphQL/Traits/HasTokenInputRules.php +++ b/src/GraphQL/Traits/HasTokenInputRules.php @@ -17,7 +17,7 @@ trait HasTokenInputRules { - public function tokenRules(array $args, ?string $collectionId = null, bool $withPacks = true): array + public function tokenRules(array $args, ?string $collectionId = null, bool $withPacks = false): array { return [ 'tokens' => [ @@ -85,7 +85,7 @@ public function tokenRules(array $args, ?string $collectionId = null, bool $with ]; } - public function packTokenRules(array $args, ?string $collectionId = null, bool $withTokens = true): array + public function packTokenRules(array $args, ?string $collectionId = null, bool $withTokens = false): array { return [ 'packs' => [ diff --git a/src/Services/BeamService.php b/src/Services/BeamService.php index 34d37a6..ca95b8f 100644 --- a/src/Services/BeamService.php +++ b/src/Services/BeamService.php @@ -186,7 +186,7 @@ public function updateByCode(string $code, array $values): Model if ($beam->fill($values)->save()) { if ($beam->is_pack && ($packs = Arr::get($values, 'packs', []))) { - $this->createPackClaims($beam, $packs, false); + $this->createPackClaims($beam, $packs); } elseif ($tokens = Arr::get($values, 'tokens', [])) { Cache::increment( self::key($beam->code), @@ -205,14 +205,19 @@ public function updateByCode(string $code, array $values): Model /** * Update beam by code. */ - public function addTokens(string $code, array $tokens): bool + public function addTokens(string $code, ?array $tokens = [], ?array $packs = []): bool { $beam = Beam::whereCode($code)->firstOrFail(); - Cache::increment( - self::key($beam->code), - $this->createClaims($beam, $tokens) - ); - TokensAdded::safeBroadcast(event: ['beamCode' => $beam->code, 'code' => $code, 'tokenIds' => collect($tokens)->pluck('tokenIds')->all()]); + + if ($beam->is_pack && $packs) { + $this->createPackClaims($beam, $packs); + } elseif ($tokens) { + Cache::increment( + self::key($beam->code), + $this->createClaims($beam, $tokens) + ); + TokensAdded::safeBroadcast(event: ['beamCode' => $beam->code, 'code' => $code, 'tokenIds' => collect($tokens)->pluck('tokenIds')->all()]); + } return true; } diff --git a/tests/Feature/GraphQL/Mutations/AddTokensTest.php b/tests/Feature/GraphQL/Mutations/AddTokensTest.php index 13a6706..739f858 100644 --- a/tests/Feature/GraphQL/Mutations/AddTokensTest.php +++ b/tests/Feature/GraphQL/Mutations/AddTokensTest.php @@ -44,6 +44,17 @@ public function test_it_add_tokens(): void ); $this->assertTrue($response); Event::assertDispatched(TokensAdded::class); + + $this->seedBeamPack(); + $response = $this->graphql( + $this->method, + [ + 'code' => $this->beam->code, + 'packs' => [['tokens' => [['tokenIds' => ['1..5'], 'type' => BeamType::MINT_ON_DEMAND->name]]]], + ] + ); + $this->assertTrue($response); + Event::assertDispatched(TokensAdded::class); } public function test_it_can_add_token_with_attributes(): void @@ -67,10 +78,31 @@ public function test_it_can_add_token_with_attributes(): void ); $this->assertTrue($response); Event::assertDispatched(TokensAdded::class); + + $this->seedBeamPack(); + $response = $this->graphql( + $this->method, + [ + 'code' => $this->beam->code, + 'packs' => [['tokens' => [ + [ + 'tokenIds' => ['1..5'], + 'type' => BeamType::MINT_ON_DEMAND->name, + 'attributes' => [ + ['key' => 'test', 'value' => 'test'], + ['key' => 'test2', 'value' => 'test2'], + ], + ], + ], + ]]], + ); + $this->assertTrue($response); + Event::assertDispatched(TokensAdded::class); } public function test_it_can_update_beam_with_file_upload(): void { + Event::fake(); $file = UploadedFile::fake()->createWithContent('tokens.txt', "1\n2..10"); $response = $this->graphql( $this->method, @@ -92,6 +124,31 @@ public function test_it_can_update_beam_with_file_upload(): void ); $this->assertTrue($response); Event::assertDispatched(TokensAdded::class); + + $this->seedBeamPack(); + $file = UploadedFile::fake()->createWithContent('tokens.txt', "1\n2..10"); + $response = $this->graphql( + $this->method, + [ + 'code' => $this->beam->code, + 'packs' => [[ + 'tokens' => [['tokenIdDataUpload' => $file, 'type' => BeamType::MINT_ON_DEMAND->name]], + ]], + ] + ); + $this->assertTrue($response); + Event::assertDispatched(TokensAdded::class); + + $file = UploadedFile::fake()->createWithContent('tokens.txt', "{$this->token->token_chain_id}\n{$this->token->token_chain_id}..{$this->token->token_chain_id}"); + $response = $this->graphql( + $this->method, + [ + 'code' => $this->beam->code, + 'packs' => [['tokens' => [['tokenIdDataUpload' => $file]]]], + ] + ); + $this->assertTrue($response); + Event::assertDispatched(TokensAdded::class); } public function test_it_will_fail_with_token_exist_in_beam(): void @@ -124,38 +181,44 @@ public function test_it_will_fail_with_token_exist_in_beam(): void $this->assertArraySubset([ 'tokens.0.tokenIdDataUpload' => ['The tokens.0.tokenIdDataUpload already exist in beam.'], ], $response['error']); - } - public function test_it_will_fail_to_create_beam_with_invalid_file_upload(): void - { - $file = UploadedFile::fake()->createWithContent('tokens.txt', $this->token->token_chain_id); + + $this->seedBeamPack(); + $claim = $this->claims->first(); + $claim->forceFill(['token_chain_id' => $this->token->token_chain_id])->save(); $response = $this->graphql( $this->method, [ 'code' => $this->beam->code, - 'tokens' => [['tokenIdDataUpload' => $file, 'type' => BeamType::MINT_ON_DEMAND->name]], + 'packs' => [[ + 'tokens' => [['tokenIds' => [$claim->token_chain_id], 'type' => BeamType::TRANSFER_TOKEN->name]], + ]], ], true ); $this->assertArraySubset([ - 'tokens.0.tokenIdDataUpload' => ['The tokens.0.tokenIdDataUpload exists in the specified collection.'], + 'packs.0.tokens.0.tokenIds' => ['The packs.0.tokens.0.tokenIds already exist in beam.'], ], $response['error']); - Event::assertNotDispatched(BeamUpdated::class); - $file = UploadedFile::fake()->createWithContent('tokens.txt', "{$this->token->token_chain_id}..{$this->token->token_chain_id}"); + $file = UploadedFile::fake()->createWithContent('tokens.txt', $this->token->token_chain_id); $response = $this->graphql( $this->method, [ 'code' => $this->beam->code, - 'tokens' => [['tokenIdDataUpload' => $file, 'type' => BeamType::MINT_ON_DEMAND->name]], + 'packs' => [[ + 'tokens' => [['tokenIdDataUpload' => $file, 'type' => BeamType::TRANSFER_TOKEN->name]], + ]], ], true ); $this->assertArraySubset([ - 'tokens.0.tokenIdDataUpload' => ['The tokens.0.tokenIdDataUpload exists in the specified collection.'], + 'packs.0.tokens.0.tokenIdDataUpload' => ['The packs.0.tokens.0.tokenIdDataUpload already exist in beam.'], ], $response['error']); - Event::assertNotDispatched(BeamUpdated::class); + } + + public function test_it_will_fail_to_create_beam_with_invalid_file_upload(): void + { $file = UploadedFile::fake()->createWithContent('tokens.txt', '1'); $response = $this->graphql( $this->method, @@ -168,7 +231,6 @@ public function test_it_will_fail_to_create_beam_with_invalid_file_upload(): voi $this->assertArraySubset([ 'tokens.0.tokenIdDataUpload' => ['The tokens.0.tokenIdDataUpload does not exist in the specified collection.'], ], $response['error']); - Event::assertNotDispatched(BeamUpdated::class); $file = UploadedFile::fake()->createWithContent('tokens.txt', '1..10'); $response = $this->graphql( @@ -182,7 +244,33 @@ public function test_it_will_fail_to_create_beam_with_invalid_file_upload(): voi $this->assertArraySubset([ 'tokens.0.tokenIdDataUpload' => ['The tokens.0.tokenIdDataUpload does not exist in the specified collection.'], ], $response['error']); - Event::assertNotDispatched(BeamUpdated::class); + + $this->seedBeamPack(); + $file = UploadedFile::fake()->createWithContent('tokens.txt', '1'); + $response = $this->graphql( + $this->method, + [ + 'code' => $this->beam->code, + 'packs' => [['tokens' => [['tokenIdDataUpload' => $file]]]], + ], + true + ); + $this->assertArraySubset([ + 'packs.0.tokens.0.tokenIdDataUpload' => ['The packs.0.tokens.0.tokenIdDataUpload does not exist in the specified collection.'], + ], $response['error']); + + $file = UploadedFile::fake()->createWithContent('tokens.txt', '1..10'); + $response = $this->graphql( + $this->method, + [ + 'code' => $this->beam->code, + 'packs' => [['tokens' => [['tokenIdDataUpload' => $file]]]], + ], + true + ); + $this->assertArraySubset([ + 'packs.0.tokens.0.tokenIdDataUpload' => ['The packs.0.tokens.0.tokenIdDataUpload does not exist in the specified collection.'], + ], $response['error']); } /** @@ -200,28 +288,30 @@ public function test_it_will_fail_with_invalid_parameters(): void ); $this->assertArraySubset([ 'code' => ['The selected code is invalid.'], - 'tokens' => ['The tokens field must have at least 1 items.'], ], $response['error']); + $response = $this->graphql( $this->method, [ - 'code' => null, - 'tokens' => null, + 'code' => $this->beam->code, + 'tokens' => [], ], true ); - $this->assertEquals($response['error'], 'Variable "$code" of non-null type "String!" must not be null.'); + $this->assertArraySubset([ + 'tokens' => ['The tokens field must have at least 1 items.'], + ], $response['error']); $response = $this->graphql( $this->method, [ - 'code' => $this->beam->code, + 'code' => null, 'tokens' => null, ], true ); - $this->assertEquals($response['error'], 'Variable "$tokens" of non-null type "[ClaimToken!]!" must not be null.'); + $this->assertEquals($response['error'], 'Variable "$code" of non-null type "String!" must not be null.'); $response = $this->graphql($this->method, [ 'code' => Str::random(1500), @@ -231,5 +321,18 @@ public function test_it_will_fail_with_invalid_parameters(): void ['code' => ['The code field must not be greater than 1024 characters.']], $response['error'] ); + + $this->seedBeamPack(); + $response = $this->graphql( + $this->method, + [ + 'code' => $this->beam->code, + 'packs' => [], + ], + true + ); + $this->assertArraySubset([ + 'packs' => ['The packs field must have at least 1 items.'], + ], $response['error']); } } diff --git a/tests/Feature/GraphQL/Resources/AddTokens.graphql b/tests/Feature/GraphQL/Resources/AddTokens.graphql index 3d0b5c5..fffeb22 100644 --- a/tests/Feature/GraphQL/Resources/AddTokens.graphql +++ b/tests/Feature/GraphQL/Resources/AddTokens.graphql @@ -1,9 +1,7 @@ mutation AddTokens( $code: String! - $tokens: [ClaimToken!]! + $tokens: [ClaimToken!] + $packs: [BeamPackInput!] ) { - AddTokens( - code: $code - tokens: $tokens - ) + AddTokens(code: $code, tokens: $tokens, packs: $packs) }