diff --git a/.env.example b/.env.example index 8db744c11..2275fa9cb 100644 --- a/.env.example +++ b/.env.example @@ -47,6 +47,7 @@ GITHUB_REDIRECT_URL=REDIRECT_URL # Disable Chrome's Sandbox feature # More information: https://github.com/stefanzweifel/screeenly/issues/174#issuecomment-423438612 SCREEENLY_DISABLE_SANDBOX=false +SCREEENLY_DISK=public ## Misc Configuration diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..48aab522a --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: vendor/bin/heroku-php-apache2 public/ diff --git a/app.json b/app.json new file mode 100644 index 000000000..dd782ccb1 --- /dev/null +++ b/app.json @@ -0,0 +1,34 @@ +{ + "name": "screeenly", + "description": "Create screenshots of websites through a simple API", + "keywords": [ + "screeenly", + "Laravel", + "PHP", + "screenshots", + "puppeteer" + ], + "website": "http://screeenly.com/", + "repository": "https://github.com/stefanzweifel/screeenly", + "success_url": "/", + "addons": [ + "scheduler", + { + "plan": "heroku-postgresql", + "options": { + "version": "13" + } + } + ], + "buildpacks": [ + { + "url": "heroku/php" + }, + { + "url": "heroku/nodejs" + }, + { + "url": "https://github.com/jontewks/puppeteer-heroku-buildpack" + } + ] +} diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 14befceb0..d04f4a9f4 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -12,12 +12,12 @@ class TrustProxies extends Middleware * * @var array|string|null */ - protected $proxies; + protected $proxies = '*'; /** * The headers that should be used to detect proxies. * * @var int */ - protected $headers = Request::HEADER_X_FORWARDED_ALL; + protected $headers = Request::HEADER_X_FORWARDED_AWS_ELB; } diff --git a/composer.json b/composer.json index c275a78ba..dfa5e9780 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "laravel/socialite": "^5.0", "laravel/tinker": "^2.5", "laravel/ui": "^3.0", + "league/flysystem-aws-s3-v3": "^1.0", "sentry/sentry-laravel": "^2.1", "spatie/browsershot": "^3.40" }, diff --git a/composer.lock b/composer.lock index d796d1940..a968b5ea8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3bad33094f75448fa917e4b817067e7d", + "content-hash": "421390706d1c7b0312d5713174a7b70f", "packages": [ { "name": "asm89/stack-cors", @@ -62,6 +62,96 @@ }, "time": "2020-10-29T16:03:21+00:00" }, + { + "name": "aws/aws-sdk-php", + "version": "3.171.20", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "02aaf7007c5678a6358ea924cd85531300aa1747" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/02aaf7007c5678a6358ea924cd85531300aa1747", + "reference": "02aaf7007c5678a6358ea924cd85531300aa1747", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "^2.5", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.171.20" + }, + "time": "2021-01-19T19:13:08+00:00" + }, { "name": "brick/math", "version": "0.9.2", @@ -2077,6 +2167,57 @@ ], "time": "2020-08-23T07:39:11+00:00" }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.29", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "4e25cc0582a36a786c31115e419c6e40498f6972" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4e25cc0582a36a786c31115e419c6e40498f6972", + "reference": "4e25cc0582a36a786c31115e419c6e40498f6972", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.20.0", + "league/flysystem": "^1.0.40", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.29" + }, + "time": "2020-10-08T18:58:37+00:00" + }, { "name": "league/glide", "version": "1.7.0", @@ -2369,6 +2510,67 @@ ], "time": "2020-12-14T13:15:25+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/42dae2cbd13154083ca6d70099692fef8ca84bfb", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.0" + }, + "time": "2020-07-31T21:01:56+00:00" + }, { "name": "nesbot/carbon", "version": "2.44.0", diff --git a/config/filesystems.php b/config/filesystems.php index 6c39a0162..323674fa0 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -55,6 +55,7 @@ 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), + 'visibility' => 'public', ], ], diff --git a/config/logging.php b/config/logging.php index 6e536a80f..0b30dc72d 100644 --- a/config/logging.php +++ b/config/logging.php @@ -37,7 +37,7 @@ 'channels' => [ 'stack' => [ 'driver' => 'stack', - 'channels' => ['daily'], + 'channels' => ['daily', 'errorlog'], 'ignore_exceptions' => false, ], diff --git a/config/screeenly.php b/config/screeenly.php index 39d4c5e7b..1a6e6d104 100644 --- a/config/screeenly.php +++ b/config/screeenly.php @@ -8,4 +8,9 @@ */ 'disable_sandbox' => env('SCREEENLY_DISABLE_SANDBOX', false), + /** + * The Filesystem disk where screenshots are being stored + */ + 'filesystem_disk' => env('SCREEENLY_DISK', 'public'), + ]; diff --git a/database/migrations/2021_01_20_193343_create_sessions_table.php b/database/migrations/2021_01_20_193343_create_sessions_table.php new file mode 100644 index 000000000..88b4a316e --- /dev/null +++ b/database/migrations/2021_01_20_193343_create_sessions_table.php @@ -0,0 +1,35 @@ +string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->text('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('sessions'); + } +} diff --git a/modules/Screeenly/Entities/Screenshot.php b/modules/Screeenly/Entities/Screenshot.php index baa947f07..181cb5408 100644 --- a/modules/Screeenly/Entities/Screenshot.php +++ b/modules/Screeenly/Entities/Screenshot.php @@ -27,13 +27,18 @@ class Screenshot */ protected $publicUrl; + /** + * Screenshot constructor. + * @param $absolutePath + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ public function __construct($absolutePath) { $this->doesScreenshotExist($absolutePath); $this->path = $absolutePath; $this->filename = basename($absolutePath); - $this->publicUrl = asset(Storage::url($this->filename)); - $this->base64 = base64_encode(Storage::disk('public')->get($this->filename)); + $this->publicUrl = asset(Storage::disk(config('screeenly.filesystem_disk'))->url($this->filename)); + $this->base64 = base64_encode(Storage::disk(config('screeenly.filesystem_disk'))->get($this->filename)); } /** @@ -54,6 +59,9 @@ public function getFilename() return $this->filename; } + /** + * @return string + */ public function getPath() { return $this->path; @@ -70,13 +78,20 @@ public function getPublicUrl() /** * Test if a file is available. - * @param string $absolutePath + * @param string $absolutePath * @return void + * @throws Exception */ - protected function doesScreenshotExist($absolutePath) + protected function doesScreenshotExist(string $absolutePath) { - if (file_exists($absolutePath) == false) { - throw new Exception("Screenshot can't be generated for given URL"); + if (config('screeenly.filesystem_disk') == 'public') { + if (file_exists($absolutePath) == false) { + throw new Exception("Screenshot can't be generated for given URL"); + } + } else { + if (Storage::disk(config('screeenly.filesystem_disk'))->exists($absolutePath) == false) { + throw new Exception("Screenshot can't be generated for given URL"); + } } } @@ -86,6 +101,6 @@ protected function doesScreenshotExist($absolutePath) */ public function delete() { - return Storage::disk('public')->delete($this->filename); + return Storage::disk(config('screeenly.filesystem_disk'))->delete($this->filename); } } diff --git a/modules/Screeenly/Http/routes/console.php b/modules/Screeenly/Http/routes/console.php index 816fd7be9..5950557a5 100644 --- a/modules/Screeenly/Http/routes/console.php +++ b/modules/Screeenly/Http/routes/console.php @@ -4,12 +4,17 @@ use Screeenly\Models\ApiLog; Artisan::command('screeenly:cleanup', function () { - ApiLog::where('created_at', '<', Carbon::now()->subHours(1))->get()->each(function ($log) { - try { - $log->screenshot()->delete(); - $log->delete(); - } catch (Exception $e) { - $log->delete(); - } - }); + ApiLog::query() + ->where('created_at', '<', Carbon::now()->subHours(1)) + ->get() + ->each(function (ApiLog $log) { + $this->info("Delete Log #{$log->id}"); + + try { + $log->screenshot()->delete(); + $log->delete(); + } catch (Exception $e) { + // $log->delete(); + } + }); })->describe('Delete Screenshot Files older than 1 hour.'); diff --git a/modules/Screeenly/Services/ChromeBrowser.php b/modules/Screeenly/Services/ChromeBrowser.php index fba9654b4..3263c6312 100644 --- a/modules/Screeenly/Services/ChromeBrowser.php +++ b/modules/Screeenly/Services/ChromeBrowser.php @@ -28,9 +28,9 @@ public function capture(Url $url, $filename) $browser->fullPage(); } - Storage::disk('public')->put($filename, $browser->screenshot()); + Storage::disk(config('screeenly.filesystem_disk'))->put($filename, $browser->screenshot()); - $path = Storage::disk('public')->path($filename); + $path = Storage::disk(config('screeenly.filesystem_disk'))->path($filename); return new Screenshot($path); } diff --git a/phpunit.xml b/phpunit.xml index f3e7b9577..cab30f2c3 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -25,5 +25,6 @@ + diff --git a/readme.md b/readme.md index f7867688f..8c8ffb44c 100644 --- a/readme.md +++ b/readme.md @@ -33,17 +33,30 @@ The repository itself will soon get updates for Laravel 7 and Laravel 8. --- - - - ## Documentation and more The [wiki](https://github.com/stefanzweifel/screeenly/wiki) holds the documentation. - [API specification](https://github.com/stefanzweifel/screeenly/wiki/Use-the-API) -- [Self Hosting Guide](https://github.com/stefanzweifel/screeenly/wiki/Requirements-and-Install) - [Read about the code structure](https://github.com/stefanzweifel/screeenly/wiki/Read-the-Code) + +## Self Hosting + +screeenly is quite a simple PHP app. Therefore, it's quite easy to self host the application on your own server. + +### Self Hosting on your own Server + +If you're comfortable running your own server follow your self-hosting guide [here](https://github.com/stefanzweifel/screeenly/wiki/Requirements-and-Install). + +### Deploy to Heroku + +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/stefanzweifel/screeenly/tree/master) + + +If managing servers is not your thing, we've also written [a guide](https://github.com/stefanzweifel/screeenly/wiki/Deploy-to-Heroku) on how to deploy the app to Heroku. +By using Heroku, you can run your own version of screeenly basically for free. + ## Docker Images If you're interested in a Docker version of screeenly, you can use the daily built images created by [Jacek Szafarkiewicz](https://github.com/hadogenes). diff --git a/tests/modules/Screeenly/integration/api/v1/ApiV1ScreenshotTest.php b/tests/modules/Screeenly/integration/api/v1/ApiV1ScreenshotTest.php index 24fe05f04..b6376124f 100644 --- a/tests/modules/Screeenly/integration/api/v1/ApiV1ScreenshotTest.php +++ b/tests/modules/Screeenly/integration/api/v1/ApiV1ScreenshotTest.php @@ -132,9 +132,9 @@ public function it_returns_an_errof_if_url_has_no_protocol_prefix() /** @test */ public function it_returns_path_and_base64_representation_of_to_image_on_successful_request() { - Storage::fake('public'); + Storage::fake(config('screeenly.filesystem_disk')); - Storage::disk('public') + Storage::disk(config('screeenly.filesystem_disk')) ->put( 'test-screenshot.jpg', file_get_contents(storage_path('testing/test-screenshot.jpg')) diff --git a/tests/modules/Screeenly/integration/api/v2/ApiV2ScreenshotTest.php b/tests/modules/Screeenly/integration/api/v2/ApiV2ScreenshotTest.php index 3c3bb5e9d..fd93dae08 100644 --- a/tests/modules/Screeenly/integration/api/v2/ApiV2ScreenshotTest.php +++ b/tests/modules/Screeenly/integration/api/v2/ApiV2ScreenshotTest.php @@ -50,9 +50,9 @@ public function it_shows_error_if_not_a_url_is_passed() /** @test */ public function it_returns_base64_representation_of_screenshot() { - Storage::fake('public'); + Storage::fake(config('screeenly.filesystem_disk')); - Storage::disk('public') + Storage::disk(config('screeenly.filesystem_disk')) ->put( 'test-screenshot.jpg', file_get_contents(storage_path('testing/test-screenshot.jpg')) diff --git a/tests/modules/Screeenly/unit/Entities/ScreenshotTest.php b/tests/modules/Screeenly/unit/Entities/ScreenshotTest.php index 3b08ebb14..cafd7f7c2 100644 --- a/tests/modules/Screeenly/unit/Entities/ScreenshotTest.php +++ b/tests/modules/Screeenly/unit/Entities/ScreenshotTest.php @@ -8,7 +8,7 @@ protected function setUp(): void { parent::setUp(); - Storage::disk('public')->put('test-screenshot.jpg', file_get_contents(storage_path('testing/test-screenshot.jpg'))); + Storage::disk(config('screeenly.filesystem_disk'))->put('test-screenshot.jpg', file_get_contents(storage_path('testing/test-screenshot.jpg'))); } /** @test */ @@ -46,7 +46,7 @@ public function it_throws_exception_if_screenshot_is_not_available() /** @test */ public function it_deletes_screenshot_from_disk() { - Storage::disk('public')->delete('test-screenshot.jpg'); + Storage::disk(config('screeenly.filesystem_disk'))->delete('test-screenshot.jpg'); Storage::put( 'test-screenshot-to-delete.jpg', @@ -58,6 +58,6 @@ public function it_deletes_screenshot_from_disk() $this->assertTrue($screenshot->delete()); - Storage::disk('public')->assertMissing('test-screenshot-to-delete.jpg'); + Storage::disk(config('screeenly.filesystem_disk'))->assertMissing('test-screenshot-to-delete.jpg'); } } diff --git a/tests/modules/Screeenly/unit/Services/CaptureServiceTest.php b/tests/modules/Screeenly/unit/Services/CaptureServiceTest.php index 95574158f..2c2f67bb0 100644 --- a/tests/modules/Screeenly/unit/Services/CaptureServiceTest.php +++ b/tests/modules/Screeenly/unit/Services/CaptureServiceTest.php @@ -54,9 +54,9 @@ public function it_lets_you_define_a_delay() /** @test */ public function it_captures_screenshot_and_returns_screenshot_instance() { - Storage::fake('public'); + Storage::fake(config('screeenly.filesystem_disk')); - Storage::disk('public') + Storage::disk(config('screeenly.filesystem_disk')) ->put( 'test-screenshot.jpg', file_get_contents(storage_path('testing/test-screenshot.jpg'))