Skip to content

Commit

Permalink
Add idempotency key to ClaimBeamMutation
Browse files Browse the repository at this point in the history
  • Loading branch information
enjinabner committed Feb 26, 2024
1 parent d76e71f commit 5a941ec
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('beam_claims', function (Blueprint $table) {
$table->string('idempotency_key', 255)->nullable()->unique();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('beam_claims', function (Blueprint $table) {
$table->dropColumn('idempotency_key');
});
}
};
1 change: 1 addition & 0 deletions src/BeamServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function configurePackage(Package $package): void
->hasMigration('create_beam_scans_table')
->hasMigration('update_beams_table')
->hasMigration('add_collection_chain_id_to_beam_batches_table')
->hasMigration('add_idempotency_key_to_beam_claims_table')
->hasRoute('enjin-platform-beam')
->hasTranslations();
}
Expand Down
13 changes: 12 additions & 1 deletion src/GraphQL/Mutations/ClaimBeamMutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Enjin\Platform\Beam\GraphQL\Traits\HasBeamClaimConditions;
use Enjin\Platform\Beam\GraphQL\Traits\HasBeamCommonFields;
use Enjin\Platform\Beam\Models\BeamClaim;
use Enjin\Platform\Beam\Rules\CanClaim;
use Enjin\Platform\Beam\Rules\NotExpired;
use Enjin\Platform\Beam\Rules\NotOwner;
Expand All @@ -13,17 +14,20 @@
use Enjin\Platform\Beam\Rules\VerifySignedMessage;
use Enjin\Platform\Beam\Services\BeamService;
use Enjin\Platform\Enums\Substrate\CryptoSignatureType;
use Enjin\Platform\GraphQL\Types\Input\Substrate\Traits\HasIdempotencyField;
use Enjin\Platform\Interfaces\PlatformPublicGraphQlOperation;
use Enjin\Platform\Rules\ValidSubstrateAccount;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Rebing\GraphQL\Support\Facades\GraphQL;

class ClaimBeamMutation extends Mutation implements PlatformPublicGraphQlOperation
{
use HasBeamCommonFields;
use HasBeamClaimConditions;
use HasIdempotencyField;

/**
* Get the mutation's attributes.
Expand Down Expand Up @@ -67,6 +71,7 @@ public function args(): array
'description' => __('enjin-platform-beam::mutation.claim_beam.args.cryptoSignatureType'),
'defaultValue' => CryptoSignatureType::SR25519->name,
],
...$this->getIdempotencyField(),
];
}

Expand All @@ -81,9 +86,15 @@ public function resolve(
Closure $getSelectFields,
BeamService $beam
) {
$idempotencyKey = Arr::get($args, 'idempotencyKey');
if ($idempotencyKey && BeamClaim::where('idempotency_key', $idempotencyKey)->exists()) {
return true;
}

return DB::transaction(fn () => $beam->claim(
$args['code'],
$args['account']
$args['account'],
$idempotencyKey,
));
}

Expand Down
5 changes: 5 additions & 0 deletions src/GraphQL/Types/BeamClaimType.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public function fields(): array
'description' => __('enjin-platform::type.transaction.description'),
'is_relation' => true,
],
'idempotencyKey' => [
'type' => GraphQL::type('String'),
'description' => __('enjin-platform::type.transaction.field.idempotencyKey'),
'alias' => 'idempotency_key',
],
];
}
}
8 changes: 7 additions & 1 deletion src/Jobs/ClaimBeam.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,13 @@ protected function buildBeamClaimAttributes(BatchService $batchService, Model $c
protected function buildRequiredClaimAttributes(BatchService $batchService, Model $claim): array
{
return [
...Arr::only($this->data, ['wallet_public_key', 'claimed_at', 'state', 'ip_address']),
...Arr::only($this->data, [
'wallet_public_key',
'claimed_at',
'state',
'ip_address',
'idempotency_key',
]),
'beam_batch_id' => $batchService->getNextBatchId(
BeamType::getEnumCase($claim->type),
$claim->beam->collection_chain_id
Expand Down
1 change: 1 addition & 0 deletions src/Models/Laravel/BeamClaim.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class BeamClaim extends BaseModel
'ip_address',
'code',
'nonce',
'idempotency_key',
];

/**
Expand Down
24 changes: 16 additions & 8 deletions src/Services/BeamService.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public function scanByCode(string $code, ?string $wallet = null): Model|null
/**
* Claim a beam.
*/
public function claim(string $code, string $wallet): bool
public function claim(string $code, string $wallet, ?string $idempotencyKey = null): bool
{
$singleUseCode = null;
$singleUse = static::isSingleUse($code);
Expand Down Expand Up @@ -211,7 +211,7 @@ public function claim(string $code, string $wallet): bool
throw new BeamException(__('enjin-platform-beam::error.no_more_claims'));
}

ClaimBeam::dispatch($claim = $this->buildClaimBeamData($wallet, $beam, $singleUseCode));
ClaimBeam::dispatch($claim = $this->buildClaimBeamData($wallet, $beam, $singleUseCode, $idempotencyKey));
event(new BeamClaimPending($claim));
Cache::decrement($key);
Log::info("Claim beam: {$code}, Remaining: " . Cache::get($key), $claim);
Expand Down Expand Up @@ -428,19 +428,27 @@ protected function createClaims(array $tokens, Model $beam): int
/**
* Build claim payload.
*/
protected function buildClaimBeamData(string $wallet, Model $beam, ?string $singleUseCode = null): array
{
protected function buildClaimBeamData(
string $wallet,
Model $beam,
?string $singleUseCode = null,
?string $idempotencyKey = null
): array {
return array_merge(
$this->buildRequiredClaimBeamData($wallet, $beam, $singleUseCode),
$this->buildRequiredClaimBeamData($wallet, $beam, $singleUseCode, $idempotencyKey),
['extras' => $this->buildExtrasClaimBeamData($wallet, $beam)]
);
}

/**
* Build claim default data.
*/
protected function buildRequiredClaimBeamData(string $wallet, Model $beam, ?string $singleUseCode = null): array
{
protected function buildRequiredClaimBeamData(
string $wallet,
Model $beam,
?string $singleUseCode = null,
?string $idempotencyKey = null
): array {
return [
'wallet_public_key' => SS58Address::getPublicKey($wallet),
'claimed_at' => now(),
Expand All @@ -449,7 +457,7 @@ protected function buildRequiredClaimBeamData(string $wallet, Model $beam, ?stri
'beam_id' => $beam->id,
'ip_address' => request()->getClientIp(),
'code' => $singleUseCode,
'idempotency_key' => Str::uuid()->toString(),
'idempotency_key' => $idempotencyKey ?: Str::uuid()->toString(),
];
}

Expand Down
23 changes: 23 additions & 0 deletions tests/Feature/GraphQL/Mutations/ClaimBeamTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
use Enjin\Platform\Beam\Jobs\ClaimBeam;
use Enjin\Platform\Beam\Models\BeamClaim;
use Enjin\Platform\Beam\Rules\PassesClaimConditions;
use Enjin\Platform\Beam\Services\BatchService;
use Enjin\Platform\Beam\Tests\Feature\GraphQL\TestCaseGraphQL;
use Enjin\Platform\Beam\Tests\Feature\Traits\CreateBeamData;
use Enjin\Platform\Beam\Tests\Feature\Traits\SeedBeamData;
use Enjin\Platform\Enums\Substrate\CryptoSignatureType;
use Enjin\Platform\Providers\Faker\SubstrateProvider;
use Enjin\Platform\Services\Database\WalletService;
use Enjin\Platform\Support\Account;
use Enjin\Platform\Support\SS58Address;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -104,6 +106,27 @@ public function test_it_can_claim_beam_with_ed25519(): void
$this->genericClaimTest(CryptoSignatureType::ED25519);
}

public function test_it_can_claim_beam_job_with_idempotency_key(): void
{
$data = [
'wallet_public_key' => $this->wallet->public_key,
'claimed_at' => Carbon::now()->toDateTimeString(),
'state' => 'PENDING',
'ip_address' => fake()->ipv4(),
'idempotency_key' => $uuid = fake()->uuid(),
'beam' => $this->beam->toArray(),
'beam_id' => $this->beam->id,
'code' => '',
];

(new ClaimBeam($data))->handle(
resolve(BatchService::class),
resolve(WalletService::class)
);

$this->assertTrue(BeamClaim::where('idempotency_key', $uuid)->exists());
}

/**
* Test it can remove a condition from the rule.
*/
Expand Down
1 change: 1 addition & 0 deletions tests/Feature/GraphQL/Resources/GetClaims.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ query GetClaims(
claimStatus
quantity
identifierCode
idempotencyKey
attributes {
key
value
Expand Down
1 change: 1 addition & 0 deletions tests/Feature/GraphQL/Resources/GetPendingClaims.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ query GetPendingClaims(
claimStatus
quantity
identifierCode
idempotencyKey
attributes {
key
value
Expand Down

0 comments on commit 5a941ec

Please sign in to comment.