From bc2791fb4968581b61db4f3f70967976e1b71301 Mon Sep 17 00:00:00 2001 From: Austin Kregel <5355937+austinkregel@users.noreply.github.com> Date: Fri, 31 May 2024 21:38:24 -0400 Subject: [PATCH 01/36] wip --- .../Documents/PdfParserServiceContract.php | 11 + .../Spork/LocalAdminController.php | 10 +- app/Http/Middleware/HandleInertiaRequests.php | 4 +- .../BuildSummaryNotificationJob.php | 1 + app/Jobs/SyncMailboxIfCredentialsAreSet.php | 2 + app/Models/ExternalRssFeed.php | 2 +- app/Models/Finance/Account.php | 6 + app/Models/Finance/Transaction.php | 5 +- app/Models/Tag.php | 2 + .../Daily/SummaryNotification.php | 2 + app/Providers/AppServiceProvider.php | 3 + app/Services/ConditionService.php | 14 +- .../Development/DescribeTableService.php | 12 +- app/Services/Documents/MenuCardParser.php | 178 +++++++++++ app/Services/Documents/PdfParserService.php | 68 ++++ app/Services/Documents/PdfReaderService.php | 43 +++ composer.json | 2 + composer.lock | 293 +++++++++++++++--- ...0807_add_priority_to_dns_records_table.php | 28 ++ docker/base/Dockerfile | 2 +- resources/js/Components/Modal.vue | 18 +- .../Spork/Atoms/NotificationMessage.vue | 34 ++ .../Spork/Atoms/NotificationToast.vue | 32 ++ .../Spork/Atoms/NotificationWithAction.vue | 35 +++ resources/js/Components/Spork/CrudView.vue | 121 ++++---- .../Spork/Molecules/ApplyTagModal.vue | 105 +++++++ .../Notifications/SummaryNotification.vue | 6 + .../js/Components/Spork/SporkDynamicInput.vue | 4 +- resources/js/Components/Spork/SporkTable.vue | 12 +- resources/js/Layouts/AppLayout.vue | 12 +- resources/js/Layouts/Manage.vue | 1 + resources/js/Pages/Manage/Index.vue | 55 +++- resources/js/Pages/Notifications.vue | 67 ++++ resources/js/Pages/Postal/Inbox.vue | 23 +- resources/js/Pages/Postal/Index.vue | 2 +- resources/js/app.js | 10 + routes/crud.php | 1 + routes/pages/spork.php | 3 + routes/web.php | 1 + 39 files changed, 1060 insertions(+), 170 deletions(-) create mode 100644 app/Contracts/Services/Documents/PdfParserServiceContract.php create mode 100644 app/Services/Documents/MenuCardParser.php create mode 100644 app/Services/Documents/PdfParserService.php create mode 100644 app/Services/Documents/PdfReaderService.php create mode 100644 database/migrations/2024_05_24_040807_add_priority_to_dns_records_table.php create mode 100644 resources/js/Components/Spork/Atoms/NotificationMessage.vue create mode 100644 resources/js/Components/Spork/Atoms/NotificationToast.vue create mode 100644 resources/js/Components/Spork/Atoms/NotificationWithAction.vue create mode 100644 resources/js/Components/Spork/Molecules/ApplyTagModal.vue create mode 100644 resources/js/Components/Spork/Molecules/Notifications/SummaryNotification.vue create mode 100644 resources/js/Pages/Notifications.vue diff --git a/app/Contracts/Services/Documents/PdfParserServiceContract.php b/app/Contracts/Services/Documents/PdfParserServiceContract.php new file mode 100644 index 00000000..a4882d03 --- /dev/null +++ b/app/Contracts/Services/Documents/PdfParserServiceContract.php @@ -0,0 +1,11 @@ +getModel($request); + $abstractEloquentModel = $modelClass::findOrFail($abstractEloquentModel); + $abstractEloquentModel->delete(); + + return response('', 204); + } public function forceDestroy(ForceDeleteRequest $request, $abstractEloquentModel) { @@ -154,7 +162,7 @@ public function forceDestroy(ForceDeleteRequest $request, $abstractEloquentModel return; } - $abstractEloquentModel->forceDelete(); + $abstractEldoquentModel->forceDelete(); return response('', 204); } diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 6395e622..1ba50905 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -40,9 +40,9 @@ public function share(Request $request): array if (auth()->check()) { auth()->user()->setRelation('person', auth()->user()->person()); } - + $navigation = (new ConditionService)->navigation(); return array_merge(parent::share($request), [ - 'navigation' => $navigation = (new ConditionService)->navigation(), + 'navigation' => $navigation, 'current_navigation' => $navigation->where('current', true)->first(), 'conversations' => auth()->check() ? Thread::query() ->with(['messages', 'messages.fromPerson', 'messages.toPerson']) diff --git a/app/Jobs/Notifications/BuildSummaryNotificationJob.php b/app/Jobs/Notifications/BuildSummaryNotificationJob.php index e68dd936..7d5abee9 100644 --- a/app/Jobs/Notifications/BuildSummaryNotificationJob.php +++ b/app/Jobs/Notifications/BuildSummaryNotificationJob.php @@ -61,6 +61,7 @@ public function handle( $user->notify(new SummaryNotification( $headlines->toArray(), + $weather )); } } diff --git a/app/Jobs/SyncMailboxIfCredentialsAreSet.php b/app/Jobs/SyncMailboxIfCredentialsAreSet.php index 18af23f8..9f99a76b 100644 --- a/app/Jobs/SyncMailboxIfCredentialsAreSet.php +++ b/app/Jobs/SyncMailboxIfCredentialsAreSet.php @@ -6,6 +6,7 @@ use App\Models\Credential; use App\Models\Person; +use App\Models\User; use App\Services\Messaging\ImapFactoryService; use Carbon\Carbon; use Illuminate\Bus\Batchable; @@ -124,6 +125,7 @@ protected function getPersonFromEmail(array $message) $person = Person::create([ 'name' => $fromName, 'emails' => [$fromEmail], + 'user_id' => User::firstWhere('email', env('SPORK_ADMIN_EMAILS'))->id, ]); } $emails = array_values(array_unique(array_filter(array_merge($person->emails, [$message['from']['email']]), fn ($val) => ! empty($val)))); diff --git a/app/Models/ExternalRssFeed.php b/app/Models/ExternalRssFeed.php index eec8b421..3fd4d1d9 100644 --- a/app/Models/ExternalRssFeed.php +++ b/app/Models/ExternalRssFeed.php @@ -4,7 +4,7 @@ namespace App\Models; -use App\Actions\Spork\Domains\ApplyTagToModelAction; +use App\Actions\Spork\ApplyTagToModelAction; use App\Events\Models\ExternalRssFeed\ExternalRssFeedCreated; use App\Events\Models\ExternalRssFeed\ExternalRssFeedCreating; use App\Events\Models\ExternalRssFeed\ExternalRssFeedDeleted; diff --git a/app/Models/Finance/Account.php b/app/Models/Finance/Account.php index 243b3fd2..acd0a052 100644 --- a/app/Models/Finance/Account.php +++ b/app/Models/Finance/Account.php @@ -17,6 +17,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; class Account extends Model implements Crud { @@ -49,4 +50,9 @@ public function credential(): BelongsTo { return $this->belongsTo(Credential::class); } + + public function transactions(): HasMany + { + return $this->hasMany(Transaction::class); + } } diff --git a/app/Models/Finance/Transaction.php b/app/Models/Finance/Transaction.php index 17774a8a..82c41bb6 100644 --- a/app/Models/Finance/Transaction.php +++ b/app/Models/Finance/Transaction.php @@ -11,6 +11,7 @@ use App\Events\Models\Transaction\TransactionDeleting; use App\Events\Models\Transaction\TransactionUpdated; use App\Events\Models\Transaction\TransactionUpdating; +use App\Models\Credential; use App\Models\Crud; use App\Models\Taggable; use App\Models\Traits\ScopeQSearch; @@ -69,8 +70,8 @@ public function account(): BelongsTo return $this->belongsTo(Account::class, 'account_id', 'account_id'); } - public function user(): HasManyThrough + public function credential(): HasManyThrough { - return $this->hasManyThrough(User::class, Account::class); + return $this->hasManyThrough(Credential::class, Account::class, 'credential_id', 'id', 'account_id', 'account_id'); } } diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 275a6db0..b697c9c4 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -33,6 +33,8 @@ class Tag extends \Spatie\Tags\Tag implements Conditionable, Crud, ModelQuery public $fillable = ['name', 'slug', 'must_all_conditions_pass', 'type', 'order_column']; + public $casts = ['name' => 'json']; + public $dispatchesEvents = [ 'created' => TagCreated::class, 'creating' => TagCreating::class, diff --git a/app/Notifications/Daily/SummaryNotification.php b/app/Notifications/Daily/SummaryNotification.php index a9facb67..763b431d 100644 --- a/app/Notifications/Daily/SummaryNotification.php +++ b/app/Notifications/Daily/SummaryNotification.php @@ -19,6 +19,7 @@ public function __construct( // Transactions from a period, // domain expirations public ?array $articles, + public ?Forecast $forecast, ) { // } @@ -35,6 +36,7 @@ public function toMail(object $notifiable): MailMessage 'emails.daily.summary', [ 'articles' => $this->articles, + 'weather' => $this->forecast, ] ); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ae03868a..fc48bbed 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,7 @@ namespace App\Providers; use App\Contracts\Repositories\CredentialRepositoryContract; +use App\Contracts\Services\Documents\PdfParserServiceContract; use App\Contracts\Services\ImapServiceContract; use App\Contracts\Services\JiraServiceContract; use App\Contracts\Services\NamecheapServiceContract; @@ -27,6 +28,7 @@ use App\Operations\Operator; use App\Repositories\CredentialRepository; use App\Services\Code; +use App\Services\Documents\PdfParserService; use App\Services\Finance\PlaidService; use App\Services\JiraService; use App\Services\Messaging\ImapCredentialService; @@ -83,6 +85,7 @@ public function register(): void $this->app->bind(WeatherServiceContract::class, OpenWeatherService::class); $this->app->bind(JiraServiceContract::class, JiraService::class); $this->app->alias(Operator::class, 'operator'); + $this->app->bind(PdfParserServiceContract::class, PdfParserService::class); } /** diff --git a/app/Services/ConditionService.php b/app/Services/ConditionService.php index 89b62b5f..99e962a7 100644 --- a/app/Services/ConditionService.php +++ b/app/Services/ConditionService.php @@ -52,6 +52,8 @@ class ConditionService public function navigation() { + $parsedUrl = parse_url(request()->getRequestUri()); + // So we want to filter out any nav items $navItems = Navigation::query() ->with('conditions', 'children') @@ -59,12 +61,18 @@ public function navigation() ->whereNull('parent_id') ->orderBy('order') ->get() - ->map(function (Navigation $item) { - $item->current = $item->href === request()->getRequestUri() || ($item->children->isNotEmpty() && $item->children->filter(fn ($item) => $item->href === request()->getRequestUri())->count() > 0); - + ->map(function (Navigation $item) use ($parsedUrl) { + $item->current = $item->href === request()->getRequestUri() || ( + $item->children->isNotEmpty() && + // We don't want to use the query param in the comparison + $item->children->filter(fn ($item) => $item->href === $parsedUrl['path']) + ->count() > 0 + ); return $item; }); + ; + return $navItems->filter(fn (Navigation $item) => $this->process($item)); } diff --git a/app/Services/Development/DescribeTableService.php b/app/Services/Development/DescribeTableService.php index 93633c5d..c8e7589e 100644 --- a/app/Services/Development/DescribeTableService.php +++ b/app/Services/Development/DescribeTableService.php @@ -103,7 +103,7 @@ public function describe(Model $model): array foreach ($notedInstances as $class) { $instance = app($class); if (in_array($model::class, $instance->models)) { - $arrayedInstance = (array) $instance; + $arrayedInstance = (array)$instance; if (method_exists($instance, 'fields')) { $arrayedInstance['fields'] = $instance->fields(); @@ -156,7 +156,15 @@ public function describe(Model $model): array 'required' => $mapField(array_filter($description, fn ($query) => $query->Null === 'NO' && $query->Extra !== 'auto_increment')), ], $model instanceof Taggable ? [ 'tags' => Tag::query()->whereNull('type')->orWhere('type', Str::singular($model->getTable()))->get(), - ] : []); + ] : [], + [ + 'permissions' => [ + 'create' => auth()->user()->can('create_'.Str::singular($model->getTable())) || auth()->user()->hasRole('developer'), + 'update' => auth()->user()->can('update_'.Str::singular($model->getTable())) || auth()->user()->hasRole('developer'), + 'delete' => auth()->user()->can('delete_'.Str::singular($model->getTable())) || auth()->user()->hasRole('developer'), + 'delete_any' => auth()->user()->can('delete_any_'.Str::singular($model->getTable())) || auth()->user()->hasRole('developer'), + ] + ]); } public function describeTable(string $table): array diff --git a/app/Services/Documents/MenuCardParser.php b/app/Services/Documents/MenuCardParser.php new file mode 100644 index 00000000..b68f45ff --- /dev/null +++ b/app/Services/Documents/MenuCardParser.php @@ -0,0 +1,178 @@ +parserService = $parserService; + } + + public function getAllIdentifiers(string $filename): array + { + $pdfContents = $this->parseAndHandleEncryptedPdf($filename); + + dd($pdfContents); + $replacements = [ + " " => " ", + "\n" => " ", + "\t" => " ", + " - " => "-", + "- " => "-", + " -" => "-", + ]; + + foreach ($replacements as $search => $replace) { + $pdfContents = str_replace($search, $replace, $pdfContents); + } + + $lines = array_values(array_filter(explode("\n", $pdfContents))); + $matches = collect([]); + + foreach ($lines as $line) { + if (preg_match_all('/(1[\s]?A[\s]?4[\s]?.+?[^A-Z\d])|([A-Z-]{1,3}-[A-Z-0-9]{1,3}.+?[^A-Z\d])/m', $line, $allMatches)) { + $matches = collect($allMatches) + ->flatten() + ->map(fn ($str) => preg_replace('/[^[:print:]]/', '', trim(str_replace(' ', '', $str), " \t\n\r\0\x0B:)("))) + ->filter() + // The MRA email gets caught in this regex, so let's filter out anything starting with MRA + ->filter(fn ($str) => !str_starts_with($str, 'MRA') && !str_starts_with($str, 'AGE') && !str_starts_with($str, 'LLC')) + ->merge($matches) + ->unique(); + } + } + + return $matches->toArray(); + } + + public function getRecallsFromPdfFile(string $filename): array + { + $pdfContents = $this->parseAndHandleEncryptedPdf($filename); + $lines = array_values(array_filter(explode("\n", $pdfContents))); + return $this->parsePdf($lines); + } + + public function getPackageIdsFromPdfFile(string $filename): array + { + $pdfContents = $this->parseAndHandleEncryptedPdf($filename); + $lines = array_values(array_filter(explode("\n", $pdfContents))); + return $this->parsePackagesFromPdf($lines); + } + + protected function parseAndHandleEncryptedPdf(string $filename, int $recursionCounter = 0): string + { + try { + $pdfText = $this->parserService->getPdfTextFromFile($filename); + + if (empty($pdfText)) { + throw new \Exception('Secured pdf file are currently not supported.'); + } + return $pdfText; + } catch (\Exception $e) { + if ($e->getMessage() === 'Secured pdf file are currently not supported.' && $recursionCounter < 3) { + // We can actually use pdftk to decrypt the file and then re-run the extraction. + $pdf = new Pdf(); + $pdf->addFile($filename, null, '')->saveAs($filename); + return $this->parserService->getPdfTextFromFile($filename); + } + throw $e; + } + } + + protected function parsePackagesFromPdf(array $lines): array + { + $packages = []; + foreach ($lines as $line) { + if ($package = $this->isPackageBlock($line)) { + $packages[] = $package; + } + } + + return $packages; + } + + protected function parsePdf(array $lines): array + { + $recalls = []; + + $currentStore = null; + $currentPackage = null; + + foreach ($lines as $line) { + $isStoreBlock = $this->isStoreBlock($line); + $isPackageBlock = $this->isPackageBlock($line); + + if (!empty($isStoreBlock)) { + // New store has been discovered, set package info and reset + $currentStore = $isStoreBlock; + $currentPackage = null; + continue; + } + + if (!empty($isPackageBlock)) { + // New package discovered + $currentPackage = $isPackageBlock; + continue; + } + + if (empty($currentPackage)) continue; + + if (empty($recalls[$currentStore])) { + $recalls[$currentStore] = []; + } + + if (empty($recalls[$currentStore][$currentPackage])) { + $recalls[$currentStore][$currentPackage] = ''; + } + + $recalls[$currentStore][$currentPackage] .= ' ' . str_replace(array_keys($recalls), '', $this->trim($line)); + } + + /** + * Depending on how a package recall block is structured there may be another store name trailing at the end + * of the recall info. Here we will just filter out store names from the recall info based on the store names + * that are in our recalls array. + */ + $stores = array_keys($recalls); + foreach ($recalls as $store => $packages) { + foreach ($packages as $package => $recallInfo) { + $recalls[$store][$package] = $this->trim(str_replace($stores, '', $recallInfo)); + } + } + return $recalls; + } + + protected function isStoreBlock(string $line): ?string + { + $storeNameRegex = '/This recall affects.*from ([a-zA-Z0-9\-_ &]*).*/i'; + preg_match_all($storeNameRegex, $line, $matches); + if (!empty($matches[1])) { + return $this->trim($matches[1][0]); + } + + return null; + } + + protected function isPackageBlock(string $line): ?string + { + $packageRegex = '/[Package]? ?#? ?(1A[A-Z0-9]*)/i'; + preg_match_all($packageRegex, $line, $matches); + if (!empty($matches[1])) { + return $this->trim($matches[1][0]); + } + + return null; + } + + protected function trim(string $value): string + { + return trim($value, "\t\r\n "); + } +} diff --git a/app/Services/Documents/PdfParserService.php b/app/Services/Documents/PdfParserService.php new file mode 100644 index 00000000..dddefe91 --- /dev/null +++ b/app/Services/Documents/PdfParserService.php @@ -0,0 +1,68 @@ +parser->parseFile($filename); + + $menuData = []; + + foreach ($pdf->getPages() as $page) { + $dataChunks = $page->getDataTm(); + + foreach ($dataChunks as $index => $chunk) { + if ($index < 18 ) { + continue; + } + if (!isset($item)) { + $item = []; + } + + $text = trim(match(count($chunk)) { + 2 => $chunk[1], + }); + + if (empty($text)) { + continue; + } + + $item[] = $text; + + preg_match('/\d{1,4}\.\d{2}/', $text, $matches); + + if (empty($matches)) { + continue; + } + + $menuData[$item[0]] = $item; + unset($item); + } + } + + return json_encode($menuData); + } + + public function getPdfTextFromRawContent(string $contents): string + { + $pdf = $this->parser->parseContent($contents); + return $this->readPdfText($pdf); + } + + protected function readPdfText(Document $pdf): string + { + return $pdf->getText(); + } +} diff --git a/app/Services/Documents/PdfReaderService.php b/app/Services/Documents/PdfReaderService.php new file mode 100644 index 00000000..d65736cf --- /dev/null +++ b/app/Services/Documents/PdfReaderService.php @@ -0,0 +1,43 @@ +rawDataParser->parseData($content); + if (isset($xref['trailer']['encrypt'])) { + $this->stripEncryption($xref); + } + + if (empty($data)) { + throw new \Exception('Object list not found. Possible secured file.'); + } + + // Create destination object. + $document = new Document(); + $this->objects = []; + + foreach ($data as $id => $structure) { + $this->parseObject($id, $structure, $document); + unset($data[$id]); + } + + $document->setTrailer($this->parseTrailer($xref['trailer'], $document)); + $document->setObjects($this->objects); + + return $document; + } + + protected function stripEncryption(array &$xref): void + { + if (is_string($xref['trailer']['size'])) { + $xref['trailer']['encrypt'] = null; + } + } +} diff --git a/composer.json b/composer.json index ac713f6a..5b773bee 100644 --- a/composer.json +++ b/composer.json @@ -32,11 +32,13 @@ "league/flysystem-ftp": "^3.0", "lesstif/php-jira-rest-client": "^5.7", "meilisearch/meilisearch-php": "^1.6", + "mikehaertl/php-pdftk": "^0.13.1", "mustache/mustache": "^2.14", "nativephp/electron": "^0.5.0", "nette/php-generator": "*", "php-imap/php-imap": "^5.0", "pusher/pusher-php-server": "^7.0", + "smalot/pdfparser": "^2.10", "spatie/laravel-activitylog": "^4.8", "spatie/laravel-feed": "^4.4", "spatie/laravel-ignition": "^2.4", diff --git a/composer.lock b/composer.lock index 2b73ef2b..d32c1579 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": "41377b7b0be5ee3d276aa3f1fe5b3577", + "content-hash": "81f5969e34e14bf81318c209517cdcf9", "packages": [ { "name": "bacon/bacon-qr-code", @@ -2198,22 +2198,23 @@ }, { "name": "inertiajs/inertia-laravel", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/inertiajs/inertia-laravel.git", - "reference": "576fba4da6f2ba6348ddf57a750c73231904d598" + "reference": "5675663d9619528544cc2dca60e0f8b9603980ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/576fba4da6f2ba6348ddf57a750c73231904d598", - "reference": "576fba4da6f2ba6348ddf57a750c73231904d598", + "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/5675663d9619528544cc2dca60e0f8b9603980ae", + "reference": "5675663d9619528544cc2dca60e0f8b9603980ae", "shasum": "" }, "require": { "ext-json": "*", "laravel/framework": "^8.74|^9.0|^10.0|^11.0", - "php": "^7.3|~8.0.0|~8.1.0|~8.2.0|~8.3.0" + "php": "^7.3|~8.0.0|~8.1.0|~8.2.0|~8.3.0", + "symfony/console": "^5.3|^6.0|^7.0" }, "require-dev": { "mockery/mockery": "^1.3.3", @@ -2261,7 +2262,7 @@ ], "support": { "issues": "https://github.com/inertiajs/inertia-laravel/issues", - "source": "https://github.com/inertiajs/inertia-laravel/tree/v1.1.0" + "source": "https://github.com/inertiajs/inertia-laravel/tree/v1.2.0" }, "funding": [ { @@ -2269,7 +2270,7 @@ "type": "github" } ], - "time": "2024-05-16T01:41:06+00:00" + "time": "2024-05-17T22:12:22+00:00" }, { "name": "justinrainbow/json-schema", @@ -2466,16 +2467,16 @@ }, { "name": "laravel/fortify", - "version": "v1.21.2", + "version": "v1.21.3", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "cb122ceec7f8d0231985c1dde8161b3c561bfe90" + "reference": "a725684d17959c4750f3b441ff2e94ecde7793a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/cb122ceec7f8d0231985c1dde8161b3c561bfe90", - "reference": "cb122ceec7f8d0231985c1dde8161b3c561bfe90", + "url": "https://api.github.com/repos/laravel/fortify/zipball/a725684d17959c4750f3b441ff2e94ecde7793a1", + "reference": "a725684d17959c4750f3b441ff2e94ecde7793a1", "shasum": "" }, "require": { @@ -2527,20 +2528,20 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2024-04-25T14:17:43+00:00" + "time": "2024-05-08T18:07:38+00:00" }, { "name": "laravel/framework", - "version": "v11.7.0", + "version": "v11.8.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "e5ac72f513f635f208024aa76b8a04efc1b47f93" + "reference": "ceb892a25817c888ef3df4d1a2af9cac53978300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/e5ac72f513f635f208024aa76b8a04efc1b47f93", - "reference": "e5ac72f513f635f208024aa76b8a04efc1b47f93", + "url": "https://api.github.com/repos/laravel/framework/zipball/ceb892a25817c888ef3df4d1a2af9cac53978300", + "reference": "ceb892a25817c888ef3df4d1a2af9cac53978300", "shasum": "" }, "require": { @@ -2665,7 +2666,7 @@ "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", - "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", @@ -2732,7 +2733,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-05-07T13:41:51+00:00" + "time": "2024-05-21T17:57:45+00:00" }, { "name": "laravel/horizon", @@ -2815,16 +2816,16 @@ }, { "name": "laravel/jetstream", - "version": "v5.1.0", + "version": "v5.1.1", "source": { "type": "git", "url": "https://github.com/laravel/jetstream.git", - "reference": "8bffd5336bfac4d6f0098b50ee7c3e3559714ddc" + "reference": "30bde139ca1c41d10a58c67f8daa2e8cd3a0be93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/jetstream/zipball/8bffd5336bfac4d6f0098b50ee7c3e3559714ddc", - "reference": "8bffd5336bfac4d6f0098b50ee7c3e3559714ddc", + "url": "https://api.github.com/repos/laravel/jetstream/zipball/30bde139ca1c41d10a58c67f8daa2e8cd3a0be93", + "reference": "30bde139ca1c41d10a58c67f8daa2e8cd3a0be93", "shasum": "" }, "require": { @@ -2878,20 +2879,20 @@ "issues": "https://github.com/laravel/jetstream/issues", "source": "https://github.com/laravel/jetstream" }, - "time": "2024-05-06T17:10:37+00:00" + "time": "2024-05-13T17:01:27+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.21", + "version": "v0.1.22", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "23ea808e8a145653e0ab29e30d4385e49f40a920" + "reference": "37f94de71758dbfbccc9d299b0e5eb76e02a40f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/23ea808e8a145653e0ab29e30d4385e49f40a920", - "reference": "23ea808e8a145653e0ab29e30d4385e49f40a920", + "url": "https://api.github.com/repos/laravel/prompts/zipball/37f94de71758dbfbccc9d299b0e5eb76e02a40f5", + "reference": "37f94de71758dbfbccc9d299b0e5eb76e02a40f5", "shasum": "" }, "require": { @@ -2934,22 +2935,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.21" + "source": "https://github.com/laravel/prompts/tree/v0.1.22" }, - "time": "2024-04-30T12:46:16+00:00" + "time": "2024-05-10T19:22:18+00:00" }, { "name": "laravel/reverb", - "version": "v1.0.0-beta10", + "version": "v1.0.0-beta11", "source": { "type": "git", "url": "https://github.com/laravel/reverb.git", - "reference": "78056b18086a5dcd0c50db8398e9be313e22624b" + "reference": "f0cee423dce0ed8c8d65dbfdfc531b188627a50d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/reverb/zipball/78056b18086a5dcd0c50db8398e9be313e22624b", - "reference": "78056b18086a5dcd0c50db8398e9be313e22624b", + "url": "https://api.github.com/repos/laravel/reverb/zipball/f0cee423dce0ed8c8d65dbfdfc531b188627a50d", + "reference": "f0cee423dce0ed8c8d65dbfdfc531b188627a50d", "shasum": "" }, "require": { @@ -3016,9 +3017,9 @@ ], "support": { "issues": "https://github.com/laravel/reverb/issues", - "source": "https://github.com/laravel/reverb/tree/v1.0.0-beta10" + "source": "https://github.com/laravel/reverb/tree/v1.0.0-beta11" }, - "time": "2024-05-02T14:46:35+00:00" + "time": "2024-05-09T17:23:01+00:00" }, { "name": "laravel/sanctum", @@ -3876,6 +3877,145 @@ }, "time": "2024-05-06T13:58:08+00:00" }, + { + "name": "mikehaertl/php-pdftk", + "version": "0.13.1", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-pdftk.git", + "reference": "3851b08c1027489e48387d7c14c27bc295d98239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-pdftk/zipball/3851b08c1027489e48387d7c14c27bc295d98239", + "reference": "3851b08c1027489e48387d7c14c27bc295d98239", + "shasum": "" + }, + "require": { + "mikehaertl/php-shellcommand": "^1.6.3", + "mikehaertl/php-tmpfile": "^1.1.0", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">4.0 <9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\pdftk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Haertl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "A PDF conversion and form utility based on pdftk.", + "keywords": [ + "pdf", + "pdftk" + ], + "support": { + "issues": "https://github.com/mikehaertl/php-pdftk/issues", + "source": "https://github.com/mikehaertl/php-pdftk/tree/0.13.1" + }, + "time": "2023-11-03T16:06:08+00:00" + }, + { + "name": "mikehaertl/php-shellcommand", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-shellcommand.git", + "reference": "e79ea528be155ffdec6f3bf1a4a46307bb49e545" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-shellcommand/zipball/e79ea528be155ffdec6f3bf1a4a46307bb49e545", + "reference": "e79ea528be155ffdec6f3bf1a4a46307bb49e545", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">4.0 <=9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\shellcommand\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Härtl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "An object oriented interface to shell commands", + "keywords": [ + "shell" + ], + "support": { + "issues": "https://github.com/mikehaertl/php-shellcommand/issues", + "source": "https://github.com/mikehaertl/php-shellcommand/tree/1.7.0" + }, + "time": "2023-04-19T08:25:22+00:00" + }, + { + "name": "mikehaertl/php-tmpfile", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/mikehaertl/php-tmpfile.git", + "reference": "70a5b70b17bc0d9666388e6a551ecc93d0b40a10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikehaertl/php-tmpfile/zipball/70a5b70b17bc0d9666388e6a551ecc93d0b40a10", + "reference": "70a5b70b17bc0d9666388e6a551ecc93d0b40a10", + "shasum": "" + }, + "require-dev": { + "php": ">=5.3.0", + "phpunit/phpunit": ">4.0 <=9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikehaertl\\tmp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Härtl", + "email": "haertl.mike@gmail.com" + } + ], + "description": "A convenience class for temporary files", + "keywords": [ + "files" + ], + "support": { + "issues": "https://github.com/mikehaertl/php-tmpfile/issues", + "source": "https://github.com/mikehaertl/php-tmpfile/tree/1.2.1" + }, + "time": "2021-03-01T18:26:25+00:00" + }, { "name": "mobiledetect/mobiledetectlib", "version": "4.8.06", @@ -7077,6 +7217,57 @@ }, "time": "2023-09-03T09:24:00+00:00" }, + { + "name": "smalot/pdfparser", + "version": "v2.10.0", + "source": { + "type": "git", + "url": "https://github.com/smalot/pdfparser.git", + "reference": "14adf318f8620a6195c0b00d51c6a507837b9ff4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/smalot/pdfparser/zipball/14adf318f8620a6195c0b00d51c6a507837b9ff4", + "reference": "14adf318f8620a6195c0b00d51c6a507837b9ff4", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-zlib": "*", + "php": ">=7.1", + "symfony/polyfill-mbstring": "^1.18" + }, + "type": "library", + "autoload": { + "psr-0": { + "Smalot\\PdfParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Sebastien MALOT", + "email": "sebastien@malot.fr" + } + ], + "description": "Pdf parser library. Can read and extract information from pdf file.", + "homepage": "https://www.pdfparser.org", + "keywords": [ + "extract", + "parse", + "parser", + "pdf", + "text" + ], + "support": { + "issues": "https://github.com/smalot/pdfparser/issues", + "source": "https://github.com/smalot/pdfparser/tree/v2.10.0" + }, + "time": "2024-04-29T06:36:50+00:00" + }, { "name": "spatie/backtrace", "version": "1.6.1", @@ -11322,16 +11513,16 @@ }, { "name": "laravel/pint", - "version": "v1.15.3", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "3600b5d17aff52f6100ea4921849deacbbeb8656" + "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/3600b5d17aff52f6100ea4921849deacbbeb8656", - "reference": "3600b5d17aff52f6100ea4921849deacbbeb8656", + "url": "https://api.github.com/repos/laravel/pint/zipball/1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98", + "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98", "shasum": "" }, "require": { @@ -11342,11 +11533,11 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.54.0", - "illuminate/view": "^10.48.8", - "larastan/larastan": "^2.9.5", - "laravel-zero/framework": "^10.3.0", - "mockery/mockery": "^1.6.11", + "friendsofphp/php-cs-fixer": "^3.57.1", + "illuminate/view": "^10.48.10", + "larastan/larastan": "^2.9.6", + "laravel-zero/framework": "^10.4.0", + "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.15.1", "pestphp/pest": "^2.34.7" }, @@ -11384,20 +11575,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-04-30T15:02:26+00:00" + "time": "2024-05-21T18:08:25+00:00" }, { "name": "laravel/sail", - "version": "v1.29.1", + "version": "v1.29.2", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "8be4a31150eab3b46af11a2e7b2c4632eefaad7e" + "reference": "a8e4e749735ba2f091856eafeb3f99db8cd6b621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/8be4a31150eab3b46af11a2e7b2c4632eefaad7e", - "reference": "8be4a31150eab3b46af11a2e7b2c4632eefaad7e", + "url": "https://api.github.com/repos/laravel/sail/zipball/a8e4e749735ba2f091856eafeb3f99db8cd6b621", + "reference": "a8e4e749735ba2f091856eafeb3f99db8cd6b621", "shasum": "" }, "require": { @@ -11447,7 +11638,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2024-03-20T20:09:31+00:00" + "time": "2024-05-16T21:39:11+00:00" }, { "name": "mockery/mockery", diff --git a/database/migrations/2024_05_24_040807_add_priority_to_dns_records_table.php b/database/migrations/2024_05_24_040807_add_priority_to_dns_records_table.php new file mode 100644 index 00000000..2af2670b --- /dev/null +++ b/database/migrations/2024_05_24_040807_add_priority_to_dns_records_table.php @@ -0,0 +1,28 @@ +integer('priority')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('dns_records', function (Blueprint $table) { + $table->dropColumn('priority'); + }); + } +}; diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 968b5cc0..f2adff32 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -18,7 +18,7 @@ RUN apt-get update \ && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ && apt-get update \ - && apt-get install -y php8.2-cli php8.2-dev \ + && apt-get install -y pdftk php8.2-cli php8.2-dev \ php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \ php8.2-curl \ php8.2-imap php8.2-mysql php8.2-mbstring \ diff --git a/resources/js/Components/Modal.vue b/resources/js/Components/Modal.vue index 93b74f30..d143bf20 100644 --- a/resources/js/Components/Modal.vue +++ b/resources/js/Components/Modal.vue @@ -68,20 +68,24 @@ const maxWidthClass = computed(() => { leave-from-class="opacity-100" leave-to-class="opacity-0" > -
-
+
+
-
+
diff --git a/resources/js/Components/Spork/Atoms/NotificationMessage.vue b/resources/js/Components/Spork/Atoms/NotificationMessage.vue new file mode 100644 index 00000000..1ac0cdbc --- /dev/null +++ b/resources/js/Components/Spork/Atoms/NotificationMessage.vue @@ -0,0 +1,34 @@ + + + diff --git a/resources/js/Components/Spork/Atoms/NotificationToast.vue b/resources/js/Components/Spork/Atoms/NotificationToast.vue new file mode 100644 index 00000000..99469dbc --- /dev/null +++ b/resources/js/Components/Spork/Atoms/NotificationToast.vue @@ -0,0 +1,32 @@ + + + diff --git a/resources/js/Components/Spork/Atoms/NotificationWithAction.vue b/resources/js/Components/Spork/Atoms/NotificationWithAction.vue new file mode 100644 index 00000000..53802584 --- /dev/null +++ b/resources/js/Components/Spork/Atoms/NotificationWithAction.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/js/Components/Spork/CrudView.vue b/resources/js/Components/Spork/CrudView.vue index 5eac4b83..8fbe7c68 100644 --- a/resources/js/Components/Spork/CrudView.vue +++ b/resources/js/Components/Spork/CrudView.vue @@ -1,12 +1,11 @@ -