From fb7bec4be41767d38b45d63d32c715ad2d87bcba Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 11 Feb 2025 10:55:56 -0800 Subject: [PATCH 1/8] adds expiring asset and license mail --- app/Console/Commands/SendExpirationAlerts.php | 13 ++-- app/Mail/ExpiringAssetsMail.php | 62 +++++++++++++++++++ app/Mail/ExpiringLicenseMail.php | 62 +++++++++++++++++++ 3 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 app/Mail/ExpiringAssetsMail.php create mode 100644 app/Mail/ExpiringLicenseMail.php diff --git a/app/Console/Commands/SendExpirationAlerts.php b/app/Console/Commands/SendExpirationAlerts.php index 025ba2ce23..20dd6c6742 100644 --- a/app/Console/Commands/SendExpirationAlerts.php +++ b/app/Console/Commands/SendExpirationAlerts.php @@ -2,6 +2,8 @@ namespace App\Console\Commands; +use App\Mail\ExpiringAssetsMail; +use App\Mail\ExpiringLicenseMail; use App\Models\Asset; use App\Models\License; use App\Models\Recipients\AlertRecipient; @@ -9,6 +11,7 @@ use App\Models\Setting; use App\Notifications\ExpiringAssetsNotification; use App\Notifications\ExpiringLicenseNotification; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Mail; class SendExpirationAlerts extends Command { @@ -47,22 +50,22 @@ class SendExpirationAlerts extends Command if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) { // Send a rollup to the admin, if settings dictate - $recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) { - return new AlertRecipient($item); - }); + $recipients = collect(explode(',', $settings->alert_email)) + ->map(fn($item) => trim($item)) // Trim each email + ->all(); // Expiring Assets $assets = Asset::getExpiringWarrantee($threshold); if ($assets->count() > 0) { $this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold])); - \Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold)); + Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $threshold)); } // Expiring licenses $licenses = License::getExpiringLicenses($threshold); if ($licenses->count() > 0) { $this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold])); - \Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold)); + Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $threshold)); } } else { if ($settings->alert_email == '') { diff --git a/app/Mail/ExpiringAssetsMail.php b/app/Mail/ExpiringAssetsMail.php new file mode 100644 index 0000000000..13d322f060 --- /dev/null +++ b/app/Mail/ExpiringAssetsMail.php @@ -0,0 +1,62 @@ +assets = $params; + $this->threshold = $threshold; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Expiring_Assets_Report'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'notifications.markdown.report-expiring-assets', + with: [ + 'assets' => $this->assets, + 'threshold' => $this->threshold, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/ExpiringLicenseMail.php b/app/Mail/ExpiringLicenseMail.php new file mode 100644 index 0000000000..77d94df637 --- /dev/null +++ b/app/Mail/ExpiringLicenseMail.php @@ -0,0 +1,62 @@ +licenses = $params; + $this->threshold = $threshold; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Expiring_Licenses_Report'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'notifications.markdown.report-expiring-licenses', + with: [ + 'licenses' => $this->licenses, + 'threshold' => $this->threshold, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} From 9b5b58687d74f924f860bb6c377851dd84fcc895 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Tue, 11 Feb 2025 11:02:25 -0800 Subject: [PATCH 2/8] removed unused --- app/Console/Commands/SendExpirationAlerts.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/Console/Commands/SendExpirationAlerts.php b/app/Console/Commands/SendExpirationAlerts.php index 20dd6c6742..31bb90de46 100644 --- a/app/Console/Commands/SendExpirationAlerts.php +++ b/app/Console/Commands/SendExpirationAlerts.php @@ -6,10 +6,7 @@ use App\Mail\ExpiringAssetsMail; use App\Mail\ExpiringLicenseMail; use App\Models\Asset; use App\Models\License; -use App\Models\Recipients\AlertRecipient; use App\Models\Setting; -use App\Notifications\ExpiringAssetsNotification; -use App\Notifications\ExpiringLicenseNotification; use Illuminate\Console\Command; use Illuminate\Support\Facades\Mail; From 6873244e7ee55d2327f2a51cb83d6ef20afef9e0 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 12 Feb 2025 11:28:36 -0800 Subject: [PATCH 3/8] adds test for expiring asset notifications --- app/Console/Commands/SendExpirationAlerts.php | 2 +- app/Providers/RouteServiceProvider.php | 1 + database/factories/AssetFactory.php | 2 +- routes/mobile.php | 39 ++++++++++++ .../Email/ExpiringAlertsNotificationTest.php | 63 +++++++++++++++++++ tests/Support/Settings.php | 17 ++++- 6 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 routes/mobile.php create mode 100644 tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php diff --git a/app/Console/Commands/SendExpirationAlerts.php b/app/Console/Commands/SendExpirationAlerts.php index 31bb90de46..1220e0cd9d 100644 --- a/app/Console/Commands/SendExpirationAlerts.php +++ b/app/Console/Commands/SendExpirationAlerts.php @@ -50,9 +50,9 @@ class SendExpirationAlerts extends Command $recipients = collect(explode(',', $settings->alert_email)) ->map(fn($item) => trim($item)) // Trim each email ->all(); - // Expiring Assets $assets = Asset::getExpiringWarrantee($threshold); + if ($assets->count() > 0) { $this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold])); Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $threshold)); diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index d5b10bde3a..d3ef733de3 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -25,6 +25,7 @@ class RouteServiceProvider extends ServiceProvider $this->mapWebRoutes(); require base_path('routes/scim.php'); + require base_path('routes/mobile.php'); }); } diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index 4d6d20651c..b6408ae690 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -48,7 +48,7 @@ class AssetFactory extends Factory 'assigned_type' => null, 'next_audit_date' => null, 'last_checkout' => null, - 'asset_eol_date' => null + 'asset_eol_date' => null, ]; } diff --git a/routes/mobile.php b/routes/mobile.php new file mode 100644 index 0000000000..3c9b2568c1 --- /dev/null +++ b/routes/mobile.php @@ -0,0 +1,39 @@ +only('username', 'password'); + + if (Auth::attempt($credentials)) { + // Authentication passed... + $user = Auth::user(); + $token = $user->createToken($request->device_name); + + return response()->json([ + 'token' => $token->accessToken, + 'token_id' => $token->token->id, + 'user' => $user->only('id', 'first_name', 'last_name', 'email') + ]); + } else { + return response()->json(['error' => 'Authentication Failed'], 401); + } +})->name('mobile.login'); + +Route::post('/mobile/logout', function (Request $request) { + $tokenId = $request->token_id; + $tokenRepository = app(TokenRepository::class); + $refreshTokenRepository = app(RefreshTokenRepository::class); + + // Revoke an access token... + $tokenRepository->revokeAccessToken($tokenId); + + // Revoke all of the token's refresh tokens... + $refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId); + return response()->json(['msg' => 'logged out'], 200); +})->name('mobile.logout'); \ No newline at end of file diff --git a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php new file mode 100644 index 0000000000..a6a6cf5561 --- /dev/null +++ b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php @@ -0,0 +1,63 @@ +markIncompleteIfSqlite(); + Mail::fake(); + + $this->settings->enableAlertEmail('admin@example.com'); + $this->settings->setAlertInterval(30); + + $alert_email = Setting::first()->alert_email; + + $expiringAsset = Asset::factory()->create([ + 'purchase_date' => now()->subMonths(11)->format('Y-m-d'), + 'warranty_months' => 12, + 'archived' => 0, + 'deleted_at' => null, + ]); +// dd($expiringAsset); + $expiredAsset = Asset::factory()->create([ + 'purchase_date' => now()->subMonths(13)->format('Y-m-d'), + 'warranty_months' => 12, + 'archived' => 0, + 'deleted_at' => null, + ]); + $notExpiringAsset = Asset::factory()->create([ + 'purchase_date' => now()->subMonths(10)->format('Y-m-d'), + 'warranty_months' => 12, + 'archived' => 0, + 'deleted_at' => null, + ]); + + $this->artisan('snipeit:expiring-alerts')->assertExitCode(0); + + Mail::assertSent(ExpiringAssetsMail::class, function($mail) use ($alert_email, $expiringAsset) { + return $mail->hasTo($alert_email) && $mail->assets->contains($expiringAsset); + }); + + Mail::assertNotSent(ExpiringAssetsMail::class, function($mail) use ($expiredAsset, $notExpiringAsset) { + return $mail->assets->contains($expiredAsset) || $mail->assets->contains($notExpiringAsset); + }); + } + + public function testExpiringLicensesEmailNotification() + { + Mail::fake(); + $user = User::factory()->create(); + $license = License::factory()->create(); + } +} \ No newline at end of file diff --git a/tests/Support/Settings.php b/tests/Support/Settings.php index e171c0ab95..09d40974c1 100644 --- a/tests/Support/Settings.php +++ b/tests/Support/Settings.php @@ -21,12 +21,23 @@ class Settings public function enableAlertEmail(string $email = 'notifications@afcrichmond.com'): Settings { - return $this->update(['alert_email' => $email]); + return $this->update([ + 'alert_email' => $email, + 'alerts_enabled' => 1, + ]); + } + public function setAlertInterval(int $days): Settings + { + return $this->update([ + 'alert_threshold' => $days, + ]); } - public function disableAlertEmail(): Settings { - return $this->update(['alert_email' => null]); + return $this->update([ + 'alert_email' => null, + 'alerts_enabled' => 0, + ]); } public function enableMultipleFullCompanySupport(): Settings From bd03d7093742a721becae41a154ac4050c12a229 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 12 Feb 2025 11:43:22 -0800 Subject: [PATCH 4/8] adds test for expiring licenses --- .../Email/ExpiringAlertsNotificationTest.php | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php index a6a6cf5561..2e15100c2b 100644 --- a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php +++ b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Notifications\Email; use App\Mail\ExpiringAssetsMail; +use App\Mail\ExpiringLicenseMail; use App\Models\Asset; use App\Models\License; use App\Models\Setting; @@ -57,7 +58,33 @@ class ExpiringAlertsNotificationTest extends TestCase public function testExpiringLicensesEmailNotification() { Mail::fake(); - $user = User::factory()->create(); - $license = License::factory()->create(); + $this->settings->enableAlertEmail('admin@example.com'); + $this->settings->setAlertInterval(60); + + $alert_email = Setting::first()->alert_email; + + $expiringLicense = License::factory()->create([ + 'expiration_date' => now()->addDays(30)->format('Y-m-d'), + 'deleted_at' => null, + ]); + + $expiredLicense = License::factory()->create([ + 'expiration_date' => now()->subDays(10)->format('Y-m-d'), + 'deleted_at' => null, + ]); + $notExpiringLicense = License::factory()->create([ + 'expiration_date' => now()->addMonths(3)->format('Y-m-d'), + 'deleted_at' => null, + ]); + + $this->artisan('snipeit:expiring-alerts')->assertExitCode(0); + + Mail::assertSent(ExpiringLicenseMail::class, function($mail) use ($alert_email, $expiringLicense) { + return $mail->hasTo($alert_email) && $mail->licenses->contains($expiringLicense); + }); + + Mail::assertNotSent(ExpiringLicenseMail::class, function($mail) use ($expiredLicense, $notExpiringLicense) { + return $mail->licenses->contains($expiredLicense) || $mail->licenses->contains($notExpiringLicense); + }); } } \ No newline at end of file From 4cbc751dad23708833c100e67617772c4dd2265b Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 12 Feb 2025 11:49:36 -0800 Subject: [PATCH 5/8] removed unnecessary changes --- routes/mobile.php | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 routes/mobile.php diff --git a/routes/mobile.php b/routes/mobile.php deleted file mode 100644 index 3c9b2568c1..0000000000 --- a/routes/mobile.php +++ /dev/null @@ -1,39 +0,0 @@ -only('username', 'password'); - - if (Auth::attempt($credentials)) { - // Authentication passed... - $user = Auth::user(); - $token = $user->createToken($request->device_name); - - return response()->json([ - 'token' => $token->accessToken, - 'token_id' => $token->token->id, - 'user' => $user->only('id', 'first_name', 'last_name', 'email') - ]); - } else { - return response()->json(['error' => 'Authentication Failed'], 401); - } -})->name('mobile.login'); - -Route::post('/mobile/logout', function (Request $request) { - $tokenId = $request->token_id; - $tokenRepository = app(TokenRepository::class); - $refreshTokenRepository = app(RefreshTokenRepository::class); - - // Revoke an access token... - $tokenRepository->revokeAccessToken($tokenId); - - // Revoke all of the token's refresh tokens... - $refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId); - return response()->json(['msg' => 'logged out'], 200); -})->name('mobile.logout'); \ No newline at end of file From c77a1faa69a867dc6083733f33625f1c6a5df94a Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 12 Feb 2025 11:54:34 -0800 Subject: [PATCH 6/8] moar unnecssary changes removed --- database/factories/AssetFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index b6408ae690..4d6d20651c 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -48,7 +48,7 @@ class AssetFactory extends Factory 'assigned_type' => null, 'next_audit_date' => null, 'last_checkout' => null, - 'asset_eol_date' => null, + 'asset_eol_date' => null ]; } From 242fd00e8a389ce33856cc00a29e91ff054f628b Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 12 Feb 2025 11:55:28 -0800 Subject: [PATCH 7/8] remove commented dd --- .../Notifications/Email/ExpiringAlertsNotificationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php index 2e15100c2b..0860d2efe7 100644 --- a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php +++ b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php @@ -30,7 +30,7 @@ class ExpiringAlertsNotificationTest extends TestCase 'archived' => 0, 'deleted_at' => null, ]); -// dd($expiringAsset); + $expiredAsset = Asset::factory()->create([ 'purchase_date' => now()->subMonths(13)->format('Y-m-d'), 'warranty_months' => 12, From e66b690c93a325fd65b4bb068d3be41623690142 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Wed, 12 Feb 2025 12:01:43 -0800 Subject: [PATCH 8/8] removed unnecessary base_path changes --- app/Providers/RouteServiceProvider.php | 1 - .../Notifications/Email/ExpiringAlertsNotificationTest.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index d3ef733de3..d5b10bde3a 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -25,7 +25,6 @@ class RouteServiceProvider extends ServiceProvider $this->mapWebRoutes(); require base_path('routes/scim.php'); - require base_path('routes/mobile.php'); }); } diff --git a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php index 0860d2efe7..220f6543c8 100644 --- a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php +++ b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php @@ -57,6 +57,7 @@ class ExpiringAlertsNotificationTest extends TestCase public function testExpiringLicensesEmailNotification() { + $this->markIncompleteIfSqlite(); Mail::fake(); $this->settings->enableAlertEmail('admin@example.com'); $this->settings->setAlertInterval(60);