diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83e871f..e65a569 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,20 +7,24 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: php-actions/composer@v6 - with: - php_version: "8.1" - - uses: php-actions/phpunit@v3 - with: - php_extensions: xdebug - php_version: "8.1" - args: --coverage-clover=coverage/clover-coverage.xml - env: + - uses: actions/checkout@v3 + + - uses: php-actions/composer@v6 + with: + php_version: "8.1" + + - uses: php-actions/phpunit@v3 + with: + version: 9 + php_extensions: bcmath xdebug gd mbstring imagick + php_version: "8.1" + args: --coverage-clover=coverage/clover-coverage.xml + env: XDEBUG_MODE: coverage - - name: Code Coverage Check - uses: themichaelhall/check-code-coverage@v2 - with: + - name: Code Coverage Check + uses: themichaelhall/check-code-coverage@v2 + if: github.event_name == 'pull_request' + with: report: coverage/clover-coverage.xml - required-percentage: 80 + required-percentage: 80 \ No newline at end of file diff --git a/README.md b/README.md index 4640904..360e780 100644 --- a/README.md +++ b/README.md @@ -1 +1,341 @@ -# TODO +# Laravel Error Tracker 📥 + +![Packagist Dependency Version](https://img.shields.io/packagist/v/dnj/laravel-error-tracker-server?style=flat-square) +![GitHub all releases](https://img.shields.io/packagist/dt/dnj/laravel-error-tracker-server?style=flat-square) +![GitHub](https://img.shields.io/github/license/dnj/laravel-error-tracker-server?style=flat-square) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dnj/laravel-error-tracker-server/ci.yml) + +## Introduction + +This package is specifically built for **laravel** error tracking. + +* Features include: + * Application Management + * Device Management + * Log Management +* Latest versions of PHP and PHPUnit and PHPCsFixer +* Best practices applied: + * [`README.md`](https://github.com/dnj/laravel-error-tracker-server/blob/master/README.md) (badges included) + * [`LICENSE`](https://github.com/dnj/laravel-error-tracker-server/blob/master/LICENSE) + * [`composer.json`](https://github.com/dnj/laravel-error-tracker-server/blob/master/composer.json) + * [`phpunit.xml`](https://github.com/dnj/laravel-error-tracker-server/blob/master/phpunit.xml) + * [`.gitignore`](https://github.com/dnj/laravel-error-tracker-server/blob/master/.gitignore) + * [`.php-cs-fixer.php`](https://github.com/dnj/laravel-error-tracker-server/blob/master/.php-cs-fixer.php) + +## Installation + +Require this package with composer. + +```shell +composer require dnj/laravel-error-tracker-server +``` + +Laravel uses Package Auto-Discovery, so doesn't require you to manually add the ServiceProvider. + +#### Copy the package config to your local config with the publish command: + +```shell +php artisan vendor:publish --provider="dnj\ErrorTracker\Laravel\Server\ServiceProvider" +``` + +#### Config file + +```php + \dnj\AAA\Models\User::class, + + 'routes' => [ + 'enable' => true, + 'prefix' => 'log', // example: log, device, etc ... , + ], +]; + +``` + +--- + +ℹ️ **Note** +> User activity logs are **disabled** by default, if you want to save them set `$userActivityLog` to true. + +Example : + +```php +use dnj\ErrorTracker\Contracts\IAppManager; +use dnj\ErrorTracker\Contracts\IDeviceManager; +use dnj\ErrorTracker\Contracts\ILogManager; +use dnj\ErrorTracker\Contracts\LogLevel; + +$appManager = app(IAppManager::class); + +$app = $appManager->store( + title: 'Android mobile app', + owner: 1, + meta: ['key' => 'value']), + userActivityLog: false, +); + +$deviceManager = app(IDeviceManager::class); + +$device = $deviceManager->store( + title: 'Nokia mobile', + owner: 1, + meta: ['serialNo' => 44514526985]), + userActivityLog: false, +); + + +$logManager = app(ILogManager::class); + +$log = $logManager->store( + app: $app, + device: $device, + level: LogLevel::INFO, + message: 'App just installed', + data: ['version' => "1.0.0"] +); +``` + +## Working With Application: + +* Search Applications: + +```php +use dnj\ErrorTracker\Contracts\IAppManager; + +$appManager = app(IAppManager::class); + +$apps = $appManager->search( + filters: [ + 'title' => 'mobile app' + 'owner' => 2 + ], +); +``` + +* Create new Application: + +```php +use dnj\ErrorTracker\Contracts\IAppManager; + +$appManager = app(IAppManager::class); + +$app = $appManager->store( + title: 'Android mobile app', + owner: 1, + meta: ['key' => 'value']), + userActivityLog: false, +); +``` + +* Update Application: + +```php +use dnj\ErrorTracker\Contracts\IAppManager; + +$appManager = app(IAppManager::class); + +$app = $appManager->update( + app: 1, + changes: [ + 'title' => 'new title', + 'owner' => 2, + ], + userActivityLog: true, +); +``` + +* Delete application: + +```php +use dnj\ErrorTracker\Contracts\IAppManager; + +$appManager = app(IAppManager::class); + +$appManager->destroy( + log: 1, + userActivityLog: false, +); + +``` + +*** + +## Working With Device: + +* Search Device: + +```php +use dnj\ErrorTracker\Contracts\IDeviceManager; + +$deviceManager = app(IDeviceManager::class); + +$devices = $deviceManager->search( + filters: [ + 'title' => 'Nokia Mobile' + 'owner' => 2 + ], +); +``` + +* Create new device: + +```php +use dnj\ErrorTracker\Contracts\IDeviceManager; + +$deviceManager = app(IDeviceManager::class); + +$device = $deviceManager->store( + title: 'Nokia mobile', + owner: 1, + meta: ['key' => 'value']), + userActivityLog: false, +); +``` + +* Update Device: + +```php +use dnj\ErrorTracker\Contracts\IDeviceManager; + +$deviceManager = app(IDeviceManager::class); + +$device = $deviceManager->update( + device: 3, + changes: [ + 'title' => 'My Nokia Mobile', + 'owner' => 2, + 'meta' => ['serialNo' => 55245252] + ], + userActivityLog: true, +); + +``` + +* Delete application: + +```php +use dnj\ErrorTracker\Contracts\IDeviceManager; + +$deviceManager = app(IDeviceManager::class); + +$deviceManager->destroy( + log: 3, + userActivityLog: false, +); +``` + +*** + +## Working With Log: + +* Search Device: + +```php +use dnj\ErrorTracker\Contracts\ILogManager; +use dnj\ErrorTracker\Contracts\LogLevel; + +$logManager = app(ILogManager::class); + +$logs = $logManager->search( + filters: [ + 'apps' => [1,2], + 'devices' => [1], + 'levels' => [LogLevel::DEBUG], + 'message' => 'important flag', + 'unread' => true, + ] +); +``` + +* Create new log: + +```php +use dnj\ErrorTracker\Contracts\ILogManager; +use dnj\ErrorTracker\Contracts\LogLevel; + +$logManager = app(ILogManager::class); + +$log = $logManager->store( + app: 1, + device: 1, + level: LogLevel::INFO, + message: 'App has been started', +); +``` + +* Mark as read log: + +```php +use dnj\ErrorTracker\Contracts\ILogManager; +use dnj\ErrorTracker\Contracts\LogLevel; + +$logManager = app(ILogManager::class); + +$log = $logManager->markAsRead( + log: 44, + user: 3 +); +``` +* Mark as unread log: + +```php +use dnj\ErrorTracker\Contracts\ILogManager; +use dnj\ErrorTracker\Contracts\LogLevel; + +$logManager = app(ILogManager::class); + +$log = $logManager->markAsUnread( + log: 44, +); +``` + +* Delete log: + +```php +use dnj\ErrorTracker\Contracts\ILogManager; + +$logManager = app(ILogManager::class); + +$logManager->destroy( + log: 44, + userActivityLog: true, +); +``` + +*** + +## Testing + +You can run unit tests with PHP Unit: + +```php +./vendor/bin/phpunit +``` + + +## Contribution + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any +contributions you make are greatly appreciated. + +If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also +simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again! + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +## Security + +If you discover any security-related issues, please email [security@dnj.co.ir](mailto:security@dnj.co.ir) instead of +using the issue tracker. + +## License + +The MIT License (MIT). Please +see [License File](https://github.com/dnj/laravel-error-tracker-server/blob/master/LICENSE) for more information. diff --git a/database/migrations/2023_02_27_000001_create_apps_table.php b/database/migrations/2023_02_27_000001_create_apps_table.php index 2df6074..745f553 100644 --- a/database/migrations/2023_02_27_000001_create_apps_table.php +++ b/database/migrations/2023_02_27_000001_create_apps_table.php @@ -12,7 +12,9 @@ public function up(): void $table->id(); $table->string('title'); $table->json('meta')->nullable(); - $table->foreignIdFor(User::class, 'owner_id')->nullable(); + $table->foreignId('owner_id') + ->nullable() + ->constrained((new User())->getTable(), 'id'); $table->timestamps(); }); } diff --git a/database/migrations/2023_02_27_000002_create_devices_table.php b/database/migrations/2023_02_27_000002_create_devices_table.php index 6743d13..98bdf0a 100644 --- a/database/migrations/2023_02_27_000002_create_devices_table.php +++ b/database/migrations/2023_02_27_000002_create_devices_table.php @@ -12,7 +12,9 @@ public function up(): void $table->id(); $table->string('title'); $table->json('meta')->nullable(); - $table->foreignIdFor(User::class, 'owner_id')->nullable(); + $table->foreignId('owner_id') + ->nullable() + ->constrained((new User())->getTable(), 'id'); $table->timestamps(); }); } diff --git a/database/migrations/2023_02_27_000003_create_logs_table.php b/database/migrations/2023_02_27_000003_create_logs_table.php index 56e9a11..30f6c76 100644 --- a/database/migrations/2023_02_27_000003_create_logs_table.php +++ b/database/migrations/2023_02_27_000003_create_logs_table.php @@ -2,8 +2,6 @@ use dnj\AAA\Models\User; use dnj\ErrorTracker\Contracts\LogLevel; -use dnj\ErrorTracker\Laravel\Server\Models\App; -use dnj\ErrorTracker\Laravel\Server\Models\Device; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -14,21 +12,21 @@ public function up(): void Schema::create('error_tracker_logs', function (Blueprint $table) { $table->id(); - $table->foreignIdFor(App::class, 'app_id') - ->references('id') + $table->foreignId('app_id') + ->constrained('error_tracker_apps', 'id') ->cascadeOnDelete(); - $table->foreignIdFor(Device::class, 'device_id') - ->references('id') + $table->foreignId('device_id') + ->constrained('error_tracker_devices', 'id') ->cascadeOnDelete(); - $table->foreignIdFor(User::class, 'reader_id') + $table->foreignId('reader_id') ->nullable() - ->references('id') + ->constrained((new User())->getTable(), 'id') ->cascadeOnDelete(); $table->timestamp('read_at')->nullable(); - $table->timestamp('created_at'); + $table->timestamp('created_at')->useCurrent(); $table->enum('level', array_column(LogLevel::cases(), 'name')); $table->string('message'); $table->json('data')->nullable(); diff --git a/src/Http/Requests/App/SearchRequest.php b/src/Http/Requests/App/SearchRequest.php new file mode 100644 index 0000000..728356a --- /dev/null +++ b/src/Http/Requests/App/SearchRequest.php @@ -0,0 +1,17 @@ + ['string', 'nullable'], + 'owner' => ['int', 'nullable'], + 'user' => ['int', 'nullable'], + ]; + } +} diff --git a/src/Http/Requests/App/StoreRequest.php b/src/Http/Requests/App/StoreRequest.php new file mode 100644 index 0000000..578a51c --- /dev/null +++ b/src/Http/Requests/App/StoreRequest.php @@ -0,0 +1,20 @@ + ['required'], + 'extra' => ['nullable', 'array'], + 'owner' => ['nullable', 'integer'], + ]; + } +} diff --git a/src/Http/Requests/App/UpdateRequest.php b/src/Http/Requests/App/UpdateRequest.php new file mode 100644 index 0000000..a769970 --- /dev/null +++ b/src/Http/Requests/App/UpdateRequest.php @@ -0,0 +1,20 @@ + ['string'], + 'extra' => ['array'], + 'owner' => ['integer'], + ]; + } +} diff --git a/src/Http/Requests/Device/SearchRequest.php b/src/Http/Requests/Device/SearchRequest.php new file mode 100644 index 0000000..c50059c --- /dev/null +++ b/src/Http/Requests/Device/SearchRequest.php @@ -0,0 +1,17 @@ + ['string', 'nullable'], + 'user' => ['int', 'nullable'], + 'owner' => ['int', 'nullable'], + ]; + } +} diff --git a/src/Http/Requests/Device/StoreRequest.php b/src/Http/Requests/Device/StoreRequest.php new file mode 100644 index 0000000..f365d0b --- /dev/null +++ b/src/Http/Requests/Device/StoreRequest.php @@ -0,0 +1,17 @@ + ['string', 'required'], + 'extra' => ['array', 'nullable'], + 'owner' => ['integer', 'nullable'], + ]; + } +} diff --git a/src/Http/Requests/Device/UpdateRequest.php b/src/Http/Requests/Device/UpdateRequest.php new file mode 100644 index 0000000..7a54edd --- /dev/null +++ b/src/Http/Requests/Device/UpdateRequest.php @@ -0,0 +1,17 @@ + ['string', 'required'], + 'extra' => ['array', 'nullable'], + 'owner' => ['integer', 'nullable'], + ]; + } +} diff --git a/src/Http/Requests/Log/MarkAsReadRequest.php b/src/Http/Requests/Log/MarkAsReadRequest.php new file mode 100644 index 0000000..8e56904 --- /dev/null +++ b/src/Http/Requests/Log/MarkAsReadRequest.php @@ -0,0 +1,16 @@ + ['nullable', 'integer'], + 'readAt' => ['nullable', 'date'], + ]; + } +} diff --git a/src/Http/Requests/Log/SearchRequest.php b/src/Http/Requests/Log/SearchRequest.php new file mode 100644 index 0000000..c876d8b --- /dev/null +++ b/src/Http/Requests/Log/SearchRequest.php @@ -0,0 +1,27 @@ + ['array', 'nullable'], + 'apps.*' => ['exists:apps,id'], + + 'devices' => ['array', 'nullable'], + 'devices.*' => ['exists:devices,id'], + + 'levels' => ['array', 'nullable'], + 'levels.*' => sprintf('in:%s', implode(',', getEnumValues(LogLevel::cases()))), + + 'message' => ['string', 'nullable'], + 'unread' => ['bool', 'nullable'], + 'user' => ['int', 'nullable'], + ]; + } +} diff --git a/src/Http/Requests/Log/StoreRequest.php b/src/Http/Requests/Log/StoreRequest.php new file mode 100644 index 0000000..3400355 --- /dev/null +++ b/src/Http/Requests/Log/StoreRequest.php @@ -0,0 +1,24 @@ + ['integer', 'required'], + 'device' => ['integer', 'required'], + 'level' => ['required', 'in:'.implode(',', getEnumValues(LogLevel::cases()))], + 'message' => ['string', 'required'], + 'data' => ['array', 'nullable'], + 'data.*' => ['array', 'nullable'], + 'data.readAt' => ['nullable', 'date'], + 'data.userId' => ['nullable', 'int'], + 'read' => ['array', 'nullable'], + ]; + } +} diff --git a/src/Http/Resources/App/AppResource.php b/src/Http/Resources/App/AppResource.php new file mode 100644 index 0000000..2a27709 --- /dev/null +++ b/src/Http/Resources/App/AppResource.php @@ -0,0 +1,9 @@ +app->singleton(IAppManager::class, AppManager::class); $this->app->singleton(IDeviceManager::class, DeviceManager::class); $this->app->singleton(ILogManager::class, LogManager::class); - // TODO } public function boot() @@ -28,8 +27,6 @@ public function boot() __DIR__.'/../config/error-tracker.php' => config_path('error-tracker.php'), ], 'config'); } - - // TODO } protected function loadRoutes()