From 0a08a75e7a579bcf260014acbb166ef1a769bdec Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Thu, 2 May 2024 23:12:18 +0100 Subject: [PATCH] Tokens support (#277) Co-authored-by: Ryan Mitchell Co-authored-by: Jason Varga --- config/eloquent-driver.php | 5 ++ .../2024_03_07_100000_create_tokens_table.php | 25 ++++++++++ src/ServiceProvider.php | 23 ++++++++- src/Tokens/Token.php | 46 +++++++++++++++++ src/Tokens/TokenModel.php | 17 +++++++ src/Tokens/TokenRepository.php | 42 ++++++++++++++++ tests/Repositories/TokenRepositoryTest.php | 50 +++++++++++++++++++ 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2024_03_07_100000_create_tokens_table.php create mode 100644 src/Tokens/Token.php create mode 100644 src/Tokens/TokenModel.php create mode 100644 src/Tokens/TokenRepository.php create mode 100644 tests/Repositories/TokenRepositoryTest.php diff --git a/config/eloquent-driver.php b/config/eloquent-driver.php index b795c51b..33c0dbb0 100644 --- a/config/eloquent-driver.php +++ b/config/eloquent-driver.php @@ -86,4 +86,9 @@ 'driver' => 'file', 'model' => \Statamic\Eloquent\Taxonomies\TermModel::class, ], + + 'tokens' => [ + 'driver' => 'file', + 'model' => \Statamic\Eloquent\Tokens\TokenModel::class, + ], ]; diff --git a/database/migrations/2024_03_07_100000_create_tokens_table.php b/database/migrations/2024_03_07_100000_create_tokens_table.php new file mode 100644 index 00000000..6374e96a --- /dev/null +++ b/database/migrations/2024_03_07_100000_create_tokens_table.php @@ -0,0 +1,25 @@ +prefix('tokens'), function (Blueprint $table) { + $table->id(); + $table->string('token')->unique(); + $table->string('handler'); + $table->jsonb('data'); + $table->timestamp('expire_at'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists($this->prefix('tokens')); + } +}; diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 6b7e0d7d..87f448ba 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -18,6 +18,7 @@ use Statamic\Contracts\Structures\NavTreeRepository as NavTreeRepositoryContract; use Statamic\Contracts\Taxonomies\TaxonomyRepository as TaxonomyRepositoryContract; use Statamic\Contracts\Taxonomies\TermRepository as TermRepositoryContract; +use Statamic\Contracts\Tokens\TokenRepository as TokenRepositoryContract; use Statamic\Eloquent\Assets\AssetContainerContents as EloquentAssetContainerContents; use Statamic\Eloquent\Assets\AssetContainerRepository; use Statamic\Eloquent\Assets\AssetQueryBuilder; @@ -38,6 +39,7 @@ use Statamic\Eloquent\Taxonomies\TaxonomyRepository; use Statamic\Eloquent\Taxonomies\TermQueryBuilder; use Statamic\Eloquent\Taxonomies\TermRepository; +use Statamic\Eloquent\Tokens\TokenRepository; use Statamic\Events\CollectionTreeSaved; use Statamic\Providers\AddonServiceProvider; use Statamic\Statamic; @@ -158,6 +160,10 @@ private function publishMigrations(): void __DIR__.'/../database/migrations/2024_03_07_100000_create_revisions_table.php' => database_path('migrations/2024_03_07_100000_create_revisions_table.php'), ], 'statamic-eloquent-revision-migrations'); + $this->publishes($tokenMigrations = [ + __DIR__.'/../database/migrations/2024_03_07_100000_create_tokens_table.php' => database_path('migrations/2024_03_07_100000_create_tokens_table.php'), + ], 'statamic-eloquent-token-migrations'); + $this->publishes( array_merge( $taxonomyMigrations, @@ -172,7 +178,8 @@ private function publishMigrations(): void $formSubmissionMigrations, $assetContainerMigrations, $assetMigrations, - $revisionMigrations + $revisionMigrations, + $tokenMigrations ), 'migrations' ); @@ -203,6 +210,7 @@ public function register() $this->registerStructureTrees(); $this->registerTaxonomies(); $this->registerTerms(); + $this->registerTokens(); } private function registerAssetContainers() @@ -478,6 +486,19 @@ public function registerTerms() Statamic::repository(TermRepositoryContract::class, TermRepository::class); } + public function registerTokens() + { + if (config('statamic.eloquent-driver.tokens.driver', 'file') != 'eloquent') { + return; + } + + $this->app->bind('statamic.eloquent.tokens.model', function () { + return config('statamic.eloquent-driver.tokens.model'); + }); + + Statamic::repository(TokenRepositoryContract::class, TokenRepository::class); + } + protected function addAboutCommandInfo() { if (! class_exists(AboutCommand::class)) { diff --git a/src/Tokens/Token.php b/src/Tokens/Token.php new file mode 100644 index 00000000..e5482652 --- /dev/null +++ b/src/Tokens/Token.php @@ -0,0 +1,46 @@ +token, $model->handler, $model->data)) + ->expireAt($model->expire_at) + ->model($model); + } + + public function toModel() + { + return self::makeModelFromContract($this); + } + + public static function makeModelFromContract(Contract $source) + { + $class = app('statamic.eloquent.tokens.model'); + + return $class::firstOrNew(['token' => $source->token()])->fill([ + 'handler' => $source->handler(), + 'data' => $source->data(), + 'expire_at' => $source->expiry(), + ]); + } + + public function model($model = null) + { + if (func_num_args() === 0) { + return $this->model; + } + + $this->model = $model; + + return $this; + } +} diff --git a/src/Tokens/TokenModel.php b/src/Tokens/TokenModel.php new file mode 100644 index 00000000..82b70564 --- /dev/null +++ b/src/Tokens/TokenModel.php @@ -0,0 +1,17 @@ + 'json', + 'expire_at' => 'datetime', + ]; +} diff --git a/src/Tokens/TokenRepository.php b/src/Tokens/TokenRepository.php new file mode 100644 index 00000000..6a889e26 --- /dev/null +++ b/src/Tokens/TokenRepository.php @@ -0,0 +1,42 @@ +first(); + + if (! $model) { + return null; + } + + return Token::fromModel($model); + } + + public function save(TokenContract $token): bool + { + return $token->toModel()->save(); + } + + public function delete(TokenContract $token): bool + { + return $token->toModel()->delete(); + } + + public function collectGarbage(): void + { + app('statamic.eloquent.tokens.model')::where('expire_at', '<', now())->delete(); + } + + public static function bindings(): array + { + return [ + TokenContract::class => Token::class, + ]; + } +} diff --git a/tests/Repositories/TokenRepositoryTest.php b/tests/Repositories/TokenRepositoryTest.php new file mode 100644 index 00000000..22595b6a --- /dev/null +++ b/tests/Repositories/TokenRepositoryTest.php @@ -0,0 +1,50 @@ +repo = new TokenRepository(); + + $this->repo->make('abc', 'ExampleHandler', ['foo' => 'bar'])->save(); + } + + /** @test */ + public function it_gets_a_token() + { + tap($this->repo->find('abc'), function ($token) { + $this->assertInstanceOf(Token::class, $token); + $this->assertEquals('abc', $token->token()); + $this->assertEquals('ExampleHandler', $token->handler()); + $this->assertEquals(['foo' => 'bar'], $token->data()->all()); + $this->assertInstanceOf(Carbon::class, $token->expiry()); + }); + + $this->assertNull($this->repo->find('unknown')); + } + + /** @test */ + public function it_saves_a_token_to_the_database() + { + $token = $this->repo->make('new', 'ExampleHandler', ['foo' => 'bar']); + + $this->assertNull($this->repo->find('new')); + + $this->repo->save($token); + + $this->assertNotNull($item = $this->repo->find('new')); + $this->assertEquals(['foo' => 'bar'], $item->data()->all()); + } +}