├── .github ├── FUNDING.yml └── workflows │ ├── commitlint.yml │ ├── release-please.yml │ └── run-tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── config └── shareable-model.php ├── database └── migrations │ ├── 2017_05_21_232515_create_shareable_links_table.php │ ├── 2017_08_30_200213_add_should_notify_column_to_shareable_links_table.php │ └── 2017_10_02_200213_remove_hash_column_from_shareable_links_table.php ├── phpstan.neon ├── phpunit.xml ├── resources └── views │ └── password.blade.php ├── routes └── web.php ├── src ├── Events │ └── LinkWasVisited.php ├── Http │ ├── Controllers │ │ └── ShareableLinkPasswordController.php │ └── Middleware │ │ └── ValidateShareableLink.php ├── Shareable │ ├── Shareable.php │ ├── ShareableInterface.php │ ├── ShareableLink.php │ └── ShareableLinkBuilder.php └── ShareableLinkServiceProvider.php └── tests ├── Http ├── Controller │ └── ShareableLinkPasswordControllerTest.php └── Middleware │ └── ValidateShareableLinkTest.php ├── Models └── Upload.php ├── Shareable ├── ShareableLinkBuilderTest.php └── ShareableLinkTest.php ├── TestCase.php └── temp └── .gitignore /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ksassnowski 2 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Commit Messages 2 | on: [pull_request] 3 | 4 | jobs: 5 | commitlint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | - uses: wagoid/commitlint-github-action@v5 -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: google-github-actions/release-please-action@v3 17 | with: 18 | release-type: php 19 | package-name: roach-php/core -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | test: 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | php: [8.1, 8.2] 12 | stability: [prefer-lowest, prefer-stable] 13 | os: [ubuntu-latest] 14 | 15 | name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }} 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | 21 | - name: Cache dependencies 22 | uses: actions/cache@v3 23 | with: 24 | path: ~/.composer/cache/files 25 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 26 | 27 | - name: Setup PHP 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php }} 31 | extensions: pdo, sqlite, pdo_sqlite 32 | 33 | - name: Install dependencies 34 | run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction 35 | 36 | - name: Run PHPStan 37 | run: composer analyse 38 | 39 | - name: Execute tests 40 | run: composer test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .phpunit.cache/ 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [4.0.0](https://github.com/ksassnowski/laravel-shareable-models/compare/3.2.0...v4.0.0) (2023-03-17) 4 | 5 | 6 | ### ⚠ BREAKING CHANGES 7 | 8 | * support PHP 8.2, drop PHP 8.0 9 | 10 | ### Features 11 | 12 | * Laravel 10.x compatibility ([afe6254](https://github.com/ksassnowski/laravel-shareable-models/commit/afe62545d3719de1f63bd27d461e8f0cd2675daf)) 13 | 14 | 15 | ### Miscellaneous Chores 16 | 17 | * setup release pipeline ([8af85ab](https://github.com/ksassnowski/laravel-shareable-models/commit/8af85abd275cf91de74ce6b7823e651cc5ba9dd2)) 18 | * support PHP 8.2, drop PHP 8.0 ([a1351d1](https://github.com/ksassnowski/laravel-shareable-models/commit/a1351d187a95d268ed9bdf0bc51cb85ffcdc3037)) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Kai Sassnowski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Shareable Models 2 | 3 | Generate shareable links from your Eloquent models. 4 | 5 | ## Documentation 6 | 7 | You can find the full documentation [Here](https://ksassnowski.gitbooks.io/shareable-models/content/). 8 | 9 | ## License 10 | 11 | MIT 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sassnowski/laravel-shareable-models", 3 | "description": "Create shareable links from your eloquent models.", 4 | "license": "MIT", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "Kai Sassnowski", 9 | "email": "me@kai-sassnowki.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "~8.1.0 || ~8.2.0", 14 | "illuminate/database": "^10.0", 15 | "illuminate/http": "^10.0", 16 | "illuminate/support": "^10.0", 17 | "ramsey/uuid": "^4.7" 18 | }, 19 | "require-dev": { 20 | "doctrine/dbal": "^3.5", 21 | "ergebnis/composer-normalize": "^2.29", 22 | "mockery/mockery": "^1.4.4", 23 | "nunomaduro/larastan": "^2.5.1", 24 | "orchestra/testbench": "^8.0", 25 | "phpunit/phpunit": "^10.0", 26 | "roave/security-advisories": "dev-latest" 27 | }, 28 | "minimum-stability": "dev", 29 | "prefer-stable": true, 30 | "autoload": { 31 | "psr-4": { 32 | "Sassnowski\\LaravelShareableModel\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Sassnowski\\LaravelShareableModel\\Tests\\": "tests/" 38 | } 39 | }, 40 | "config": { 41 | "allow-plugins": { 42 | "ergebnis/composer-normalize": true 43 | } 44 | }, 45 | "extra": { 46 | "laravel": { 47 | "providers": [ 48 | "Sassnowski\\LaravelShareableModel\\ShareableLinkServiceProvider" 49 | ] 50 | } 51 | }, 52 | "scripts": { 53 | "post-install-cmd": [ 54 | "@composer normalize" 55 | ], 56 | "post-update-cmd": [ 57 | "@composer normalize" 58 | ], 59 | "analyse": [ 60 | "vendor/bin/phpstan analyse" 61 | ], 62 | "check": [ 63 | "@analyse", 64 | "@test" 65 | ], 66 | "test": [ 67 | "vendor/bin/phpunit" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /config/shareable-model.php: -------------------------------------------------------------------------------- 1 | '/shared', 6 | 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Redirection Routes 10 | |-------------------------------------------------------------------------- 11 | | 12 | | Here you can define the routes that the ValidateShareableLink 13 | | middleware will redirect to if one its checks fail. 14 | | 15 | | 'inactive' => The link is marked as inactive in the database. 16 | | 'expired' => The links expiration date has passed. 17 | | 'password_protected' => The user tried to access a password protected link. 18 | | 19 | */ 20 | 21 | 'redirect_routes' => [ 22 | 'inactive' => '/shared/inactive', 23 | 'expired' => '/shared/expired', 24 | 'password_protected' => '/shared/password', 25 | ], 26 | ]; 27 | -------------------------------------------------------------------------------- /database/migrations/2017_05_21_232515_create_shareable_links_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->boolean('active')->default(false); 19 | $table->unsignedInteger('shareable_id'); 20 | $table->string('shareable_type'); 21 | $table->string('url'); 22 | $table->uuid('uuid'); 23 | $table->string('hash'); 24 | $table->string('password')->nullable(); 25 | $table->dateTime('expires_at')->nullable(); 26 | 27 | $table->timestamps(); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down() 37 | { 38 | Schema::dropIfExists('shareable_links'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /database/migrations/2017_08_30_200213_add_should_notify_column_to_shareable_links_table.php: -------------------------------------------------------------------------------- 1 | boolean('should_notify')->default(false); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('shareable_links', function (Blueprint $table) { 29 | $table->dropColumn('should_notify'); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2017_10_02_200213_remove_hash_column_from_shareable_links_table.php: -------------------------------------------------------------------------------- 1 | dropColumn('hash'); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - src 8 | 9 | # The level 8 is the highest level 10 | level: 6 11 | 12 | ignoreErrors: [] 13 | 14 | excludes_analyse: [] 15 | 16 | checkMissingIterableValueType: false 17 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | 7 | 8 | 9 | 10 | ./tests/ 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /resources/views/password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Password Protected 5 | 6 | 7 | 100 | 101 | 102 | 103 |
104 |
105 | {{ csrf_field() }} 106 | 107 |
108 | 109 | 110 | 111 |
112 | 113 |
114 |

The resource you are trying to access is password protected. Please enter the password.

115 | 116 |
117 | 118 | 119 |
120 |
121 | 122 | 127 |
128 |
129 | 130 | 131 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | 'web'], function () { 4 | Route::get( 5 | '/shared/password/{shareable_link}', 6 | 'Sassnowski\LaravelShareableModel\Http\Controllers\ShareableLinkPasswordController@show' 7 | ); 8 | 9 | Route::post( 10 | '/shared/password/{shareable_link}', 11 | 'Sassnowski\LaravelShareableModel\Http\Controllers\ShareableLinkPasswordController@store' 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /src/Events/LinkWasVisited.php: -------------------------------------------------------------------------------- 1 | link = $link; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Http/Controllers/ShareableLinkPasswordController.php: -------------------------------------------------------------------------------- 1 | $shareableLink]); 15 | } 16 | 17 | public function store(Request $request, ShareableLink $shareableLink): RedirectResponse 18 | { 19 | if (password_verify($request->get('password'), $shareableLink->password)) { 20 | session([$shareableLink->uuid => true]); 21 | } 22 | 23 | return redirect($shareableLink->url); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Http/Middleware/ValidateShareableLink.php: -------------------------------------------------------------------------------- 1 | route('shareable_link'); 26 | 27 | if (!$link->isActive()) { 28 | return redirect(config('shareable-model.redirect_routes.inactive')); 29 | } 30 | 31 | if ($link->isExpired()) { 32 | return redirect(config('shareable-model.redirect_routes.expired')); 33 | } 34 | 35 | if ($link->requiresPassword() && !session($link->uuid)) { 36 | return redirect(url(config('shareable-model.redirect_routes.password_protected'), $link->uuid)); 37 | } 38 | 39 | $response = $next($request); 40 | 41 | if ($link->shouldNotify()) { 42 | event(new LinkWasVisited($link)); 43 | } 44 | 45 | return $response; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Shareable/Shareable.php: -------------------------------------------------------------------------------- 1 | morphMany(ShareableLink::class, 'shareable'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Shareable/ShareableInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | public function links(): MorphMany; 13 | } 14 | -------------------------------------------------------------------------------- /src/Shareable/ShareableLink.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | protected $guarded = []; 23 | 24 | /** 25 | * @var array 26 | */ 27 | protected $casts = [ 28 | 'should_notify' => 'bool', 29 | 'active' => 'bool', 30 | ]; 31 | 32 | public static function buildFor(ShareableInterface $entity): ShareableLinkBuilder 33 | { 34 | return new ShareableLinkBuilder($entity); 35 | } 36 | 37 | /** 38 | * @return MorphTo 39 | */ 40 | public function shareable(): MorphTo 41 | { 42 | return $this->morphTo(); 43 | } 44 | 45 | public function isActive(): bool 46 | { 47 | return (bool) $this->active; 48 | } 49 | 50 | public function isExpired(): bool 51 | { 52 | if ($this->expires_at === null) { 53 | return false; 54 | } 55 | 56 | return Carbon::now()->greaterThan(Carbon::parse($this->expires_at)); 57 | } 58 | 59 | public function requiresPassword(): bool 60 | { 61 | return !is_null($this->password); 62 | } 63 | 64 | public function shouldNotify(): bool 65 | { 66 | return $this->should_notify; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Shareable/ShareableLinkBuilder.php: -------------------------------------------------------------------------------- 1 | entity = $entity; 35 | $this->baseUrl = config('shareable-model.base_url'); 36 | } 37 | 38 | public function setPrefix(string $prefix): self 39 | { 40 | $this->prefix = $prefix; 41 | 42 | return $this; 43 | } 44 | 45 | public function setPassword(string $password): self 46 | { 47 | $this->password = $password; 48 | 49 | return $this; 50 | } 51 | 52 | public function setActive(): self 53 | { 54 | $this->active = true; 55 | 56 | return $this; 57 | } 58 | 59 | public function setExpirationDate(Carbon $date): self 60 | { 61 | $this->expirationDate = $date; 62 | 63 | return $this; 64 | } 65 | 66 | public function notifyOnVisit(): self 67 | { 68 | $this->shouldNotify = true; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * @return false|ShareableLink 75 | */ 76 | public function build() 77 | { 78 | $uuid = Uuid::uuid4()->getHex(); 79 | 80 | $link = new ShareableLink([ 81 | 'active' => $this->active, 82 | 'password' => $this->password ? bcrypt($this->password) : null, 83 | 'expires_at' => $this->expirationDate, 84 | 'uuid' => $uuid, 85 | 'url' => $this->buildUrl($uuid), 86 | 'should_notify' => $this->shouldNotify 87 | ]); 88 | 89 | return $this->entity->links()->save($link); 90 | } 91 | 92 | private function buildUrl(Hexadecimal $uuid): string 93 | { 94 | if (!$this->prefix) { 95 | return url($this->baseUrl, [$uuid]); 96 | } 97 | 98 | return url($this->baseUrl, [$this->prefix, $uuid]); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/ShareableLinkServiceProvider.php: -------------------------------------------------------------------------------- 1 | firstOrFail(); 21 | } catch (QueryException $e) { 22 | throw new ModelNotFoundException($e->getMessage()); 23 | } 24 | }); 25 | 26 | $this->publishes([ 27 | __DIR__ . '/../config/shareable-model.php' => config_path('shareable-model.php'), 28 | __DIR__ . '/../resources/views/password.blade.php' => resource_path('views/vendor/shareable-model'), 29 | ]); 30 | 31 | $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); 32 | } 33 | 34 | public function register(): void 35 | { 36 | $this->mergeConfigFrom(__DIR__ . '/../config/shareable-model.php', 'shareable-model'); 37 | $this->loadRoutesFrom(__DIR__ . '/../routes/web.php'); 38 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'shareable-model'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Http/Controller/ShareableLinkPasswordControllerTest.php: -------------------------------------------------------------------------------- 1 | '/path/to/file']); 15 | $link = ShareableLink::buildFor($entity) 16 | ->setActive() 17 | ->setPassword('super-secret') 18 | ->build(); 19 | 20 | $url = url(config('shareable-model.redirect_routes.password_protected'), $link->uuid); 21 | 22 | $response = $this->post($url, ['password' => 'super-secret']); 23 | 24 | $response->assertRedirect($link->url); 25 | $response->assertSessionHas($link->uuid->toString(), true); 26 | } 27 | 28 | /** @test */ 29 | public function entering_the_wrong_password_does_not_set_the_session_entry() 30 | { 31 | $entity = Upload::create(['path' => '/path/to/file']); 32 | $link = ShareableLink::buildFor($entity) 33 | ->setActive() 34 | ->setPassword('super-secret') 35 | ->build(); 36 | 37 | $url = url(config('shareable-model.redirect_routes.password_protected'), $link->uuid); 38 | 39 | $response = $this->post($url, ['password' => 'wrong-password']); 40 | 41 | $response->assertRedirect($link->url); 42 | $response->assertSessionMissing($link->uuid->toString()); 43 | } 44 | 45 | /** @test */ 46 | public function show_default_password_form_when_accessing_a_password_protected_route() 47 | { 48 | $entity = Upload::create(['path' => '/path/to/file']); 49 | $link = ShareableLink::buildFor($entity) 50 | ->setActive() 51 | ->setPassword('super-secret') 52 | ->build(); 53 | 54 | $url = url(config('shareable-model.redirect_routes.password_protected'), $link->uuid); 55 | $response = $this->get($url); 56 | 57 | $response->assertStatus(200); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Http/Middleware/ValidateShareableLinkTest.php: -------------------------------------------------------------------------------- 1 | entity = Upload::create(['path' => '/path/to/file']); 24 | } 25 | 26 | /** @test */ 27 | public function can_access_an_active_link() 28 | { 29 | $this->withoutExceptionHandling(); 30 | $link = ShareableLink::buildFor($this->entity) 31 | ->setActive() 32 | ->build(); 33 | 34 | $response = $this->get($link->url); 35 | 36 | $response->assertStatus(200); 37 | $response->assertSee($this->entity->path); 38 | } 39 | 40 | /** @test */ 41 | public function redirect_if_link_is_inactive() 42 | { 43 | $link = ShareableLink::buildFor($this->entity)->build(); 44 | 45 | $response = $this->get($link->url); 46 | 47 | $response->assertRedirect(config('shareable-model.redirect_routes.inactive')); 48 | } 49 | 50 | /** @test */ 51 | public function can_not_access_an_expired_link() 52 | { 53 | $link = ShareableLink::buildFor($this->entity) 54 | ->setActive() 55 | ->setExpirationDate(Carbon::now()->subDay()) 56 | ->build(); 57 | 58 | $response = $this->get($link->url); 59 | 60 | $response->assertRedirect(config('shareable-model.redirect_routes.expired')); 61 | } 62 | 63 | /** @test */ 64 | public function redirects_to_password_prompt_when_trying_to_access_a_password_protected_link() 65 | { 66 | $link = ShareableLink::buildFor($this->entity) 67 | ->setActive() 68 | ->setPassword('super-secret') 69 | ->build(); 70 | 71 | $response = $this->get($link->url); 72 | 73 | $expectedUrl = url(config('shareable-model.redirect_routes.password_protected'), $link->uuid); 74 | $response->assertRedirect($expectedUrl); 75 | } 76 | 77 | /** @test */ 78 | public function can_access_a_password_protected_link_if_the_correct_password_was_entered() 79 | { 80 | $link = ShareableLink::buildFor($this->entity) 81 | ->setActive() 82 | ->setPassword('super-secret') 83 | ->build(); 84 | 85 | session([$link->uuid->toString() => true]); 86 | 87 | $response = $this->get($link->url); 88 | 89 | $response->assertStatus(200); 90 | $response->assertSee($link->shareable->path); 91 | } 92 | 93 | /** @test */ 94 | public function it_triggers_an_event_when_a_link_is_visited_an_configured_to_do_so() 95 | { 96 | Event::fake(); 97 | 98 | $link = ShareableLink::buildFor($this->entity) 99 | ->setActive() 100 | ->notifyOnVisit() 101 | ->build(); 102 | 103 | $this->get($link->url); 104 | 105 | Event::assertDispatched(LinkWasVisited::class); 106 | } 107 | 108 | /** @test */ 109 | public function it_does_not_trigger_an_event_if_the_link_not_configured_to_do_so() 110 | { 111 | Event::fake(); 112 | 113 | $link = ShareableLink::buildFor($this->entity) 114 | ->setActive() 115 | ->build(); 116 | 117 | $this->get($link->url); 118 | 119 | Event::assertNotDispatched(LinkWasVisited::class); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /tests/Models/Upload.php: -------------------------------------------------------------------------------- 1 | entity = Upload::create(['path' => '/foo/bar']); 21 | } 22 | 23 | /** @test */ 24 | public function it_creates_an_inactive_link_by_default() 25 | { 26 | $link = (new ShareableLinkBuilder($this->entity))->build(); 27 | 28 | $this->assertFalse($link->isActive()); 29 | } 30 | 31 | /** @test */ 32 | public function creating_an_active_link() 33 | { 34 | $link = (new ShareableLinkBuilder($this->entity)) 35 | ->setActive() 36 | ->build(); 37 | 38 | $this->assertTrue($link->isActive()); 39 | } 40 | 41 | /** @test */ 42 | public function creates_a_link_which_is_not_password_protected_by_default() 43 | { 44 | $link = (new ShareableLinkBuilder($this->entity))->build(); 45 | 46 | $this->assertFalse($link->requiresPassword()); 47 | } 48 | 49 | /** @test */ 50 | public function creating_a_password_protected_link() 51 | { 52 | $link = (new ShareableLinkBuilder($this->entity)) 53 | ->setPassword('super-secret') 54 | ->build(); 55 | 56 | $this->assertTrue($link->requiresPassword()); 57 | } 58 | 59 | /** @test */ 60 | public function it_creates_a_link_without_expiration_date_by_default() 61 | { 62 | $link = (new ShareableLinkBuilder($this->entity))->build(); 63 | 64 | $this->assertNull($link->expires_at); 65 | } 66 | 67 | /** @test */ 68 | public function creating_a_link_with_an_expiration_date() 69 | { 70 | $expiresAt = Carbon::parse('2017-05-26 13:00:00'); 71 | 72 | $link = (new ShareableLinkBuilder($this->entity)) 73 | ->setExpirationDate($expiresAt) 74 | ->build(); 75 | 76 | $this->assertTrue($expiresAt->eq($link->expires_at)); 77 | } 78 | 79 | /** @test */ 80 | public function use_prefix_to_build_url() 81 | { 82 | $link = (new ShareableLinkBuilder($this->entity)) 83 | ->setPrefix('foo') 84 | ->build(); 85 | 86 | $this->assertStringContainsString('/shared/foo', $link->url); 87 | } 88 | 89 | /** @test */ 90 | public function it_does_not_notify_by_default() 91 | { 92 | $link = (new ShareableLinkBuilder($this->entity)) 93 | ->build(); 94 | 95 | $this->assertFalse($link->shouldNotify()); 96 | } 97 | 98 | /** @test */ 99 | public function build_a_link_that_notifies_on_visit() 100 | { 101 | $link = (new ShareableLinkBuilder($this->entity)) 102 | ->notifyOnVisit() 103 | ->build(); 104 | 105 | $this->assertTrue($link->shouldNotify()); 106 | } 107 | 108 | /** @test */ 109 | public function it_is_possible_to_override_the_base_url() 110 | { 111 | config(['shareable-model.base_url' => '/foobar']); 112 | 113 | $link = (new ShareableLinkBuilder($this->entity))->build(); 114 | 115 | $this->assertStringContainsString('/foobar', $link->url); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Shareable/ShareableLinkTest.php: -------------------------------------------------------------------------------- 1 | 'super-secret']); 15 | 16 | $this->assertTrue($link->requiresPassword()); 17 | } 18 | 19 | /** @test */ 20 | public function it_does_not_requires_a_password_if_it_has_been_left_empty() 21 | { 22 | $link = new ShareableLink(['password' => null]); 23 | 24 | $this->assertFalse($link->requiresPassword()); 25 | } 26 | 27 | /** @test */ 28 | public function it_is_expired_if_the_expiration_date_is_in_the_past() 29 | { 30 | Carbon::setTestNow(); 31 | 32 | $link = new ShareableLink(['expires_at' => Carbon::now()->subDay()]); 33 | 34 | $this->assertTrue($link->isExpired()); 35 | } 36 | 37 | /** @test */ 38 | public function it_is_not_expired_if_the_expiration_date_is_in_the_future() 39 | { 40 | Carbon::setTestNow(); 41 | 42 | $link = new ShareableLink(['expires_at' => Carbon::now()->addMinute()]); 43 | 44 | $this->assertFalse($link->isExpired()); 45 | } 46 | 47 | /** @test */ 48 | public function it_is_not_expired_if_no_expiration_date_was_set() 49 | { 50 | $link = new ShareableLink(['expires_at' => null]); 51 | 52 | $this->assertFalse($link->isExpired()); 53 | } 54 | 55 | /** @test */ 56 | public function link_is_active() 57 | { 58 | $link = new ShareableLink(['active' => true]); 59 | 60 | $this->assertTrue($link->isActive()); 61 | } 62 | 63 | /** @test */ 64 | public function link_is_inactive() 65 | { 66 | $link = new ShareableLink(['active' => false]); 67 | 68 | $this->assertFalse($link->isActive()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | setUpMiddleware(); 19 | 20 | $this->setUpRoutes(); 21 | 22 | $this->setUpDatabase(); 23 | } 24 | 25 | /** 26 | * @param \Illuminate\Foundation\Application $app 27 | * 28 | * @return array 29 | */ 30 | protected function getPackageProviders($app) 31 | { 32 | return [ 33 | ShareableLinkServiceProvider::class, 34 | ]; 35 | } 36 | 37 | /** 38 | * @param \Illuminate\Foundation\Application $app 39 | */ 40 | protected function getEnvironmentSetUp($app) 41 | { 42 | $app['config']->set('database.default', 'sqlite'); 43 | $app['config']->set('database.connections.sqlite', [ 44 | 'driver' => 'sqlite', 45 | 'database' => __DIR__.'/temp/database.sqlite', 46 | 'prefix' => '', 47 | ]); 48 | 49 | $app['config']->set('app.key', '6rE9Nz59bGRbeMATftriyQjrpF7DcOQm'); 50 | } 51 | 52 | private function setUpMiddleware() 53 | { 54 | $this->app[Router::class]->aliasMiddleware('shared', ValidateShareableLink::class); 55 | } 56 | 57 | private function setUpDatabase() 58 | { 59 | file_put_contents(__DIR__.'/temp/database.sqlite', null); 60 | 61 | $this->app['db']->connection()->getSchemaBuilder()->create('uploads', function (Blueprint $table) { 62 | $table->increments('id'); 63 | $table->string('path'); 64 | $table->timestamps(); 65 | }); 66 | 67 | $this->artisan('migrate'); 68 | } 69 | 70 | private function setUpRoutes() 71 | { 72 | // Since these routes don't get loaded as part of the regular application bootstrapping 73 | // we have to explicitly apply the `SubstituteBindings` middleware here. 74 | \Route::get('shared/{shareable_link}', ['middleware' => [SubstituteBindings::class, 'shared'], function ($shareableLink) { 75 | return $shareableLink->shareable->path; 76 | }]); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/temp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | --------------------------------------------------------------------------------