├── .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 |
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 |
--------------------------------------------------------------------------------