From eb3b080ee895f4c0be9c63a124d513ecaed0b981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Duli=C3=A7i?= Date: Wed, 14 Sep 2022 11:53:22 +0300 Subject: [PATCH] improved email throttle --- app/Exceptions/Common/TooManyEmailsSent.php | 10 ++++ app/Http/Controllers/Modals/InvoiceEmails.php | 24 +++----- .../Controllers/Modals/TransactionEmails.php | 30 ++++------ app/Providers/Route.php | 5 +- app/Traits/Emails.php | 55 +++++++++++++++++++ config/app.php | 5 +- routes/admin.php | 4 +- 7 files changed, 92 insertions(+), 41 deletions(-) create mode 100644 app/Exceptions/Common/TooManyEmailsSent.php create mode 100644 app/Traits/Emails.php diff --git a/app/Exceptions/Common/TooManyEmailsSent.php b/app/Exceptions/Common/TooManyEmailsSent.php new file mode 100644 index 000000000..2831835f5 --- /dev/null +++ b/app/Exceptions/Common/TooManyEmailsSent.php @@ -0,0 +1,10 @@ +middleware('permission:delete-sales-invoices')->only('destroy'); } - /** - * Show the form for creating a new resource. - * - * @param Document $invoice - * - * @return Response - */ - public function create(Document $invoice) + public function create(Document $invoice): JsonResponse { $notification = new Notification($invoice, 'invoice_new_customer', true); @@ -58,16 +55,9 @@ class InvoiceEmails extends Controller ]); } - /** - * Store a newly created resource in storage. - * - * @param Request $request - * - * @return Response - */ - public function store(Request $request) + public function store(Request $request): JsonResponse { - $response = $this->ajaxDispatch(new SendDocumentAsCustomMail($request, 'invoice_new_customer')); + $response = $this->sendEmail(new SendDocumentAsCustomMail($request, 'invoice_new_customer')); if ($response['success']) { $invoice = Document::find($request->get('document_id')); diff --git a/app/Http/Controllers/Modals/TransactionEmails.php b/app/Http/Controllers/Modals/TransactionEmails.php index b40eb6e3c..e4672906b 100644 --- a/app/Http/Controllers/Modals/TransactionEmails.php +++ b/app/Http/Controllers/Modals/TransactionEmails.php @@ -8,9 +8,13 @@ use App\Notifications\Portal\PaymentReceived as PaymentReceivedNotification; use App\Notifications\Banking\Transaction as TransactionNotification; use App\Jobs\Banking\SendTransactionAsCustomMail; use App\Http\Requests\Common\CustomMail as Request; +use Illuminate\Http\JsonResponse; +use App\Traits\Emails; class TransactionEmails extends Controller { + use Emails; + /** * Instantiate a new controller instance. */ @@ -23,17 +27,10 @@ class TransactionEmails extends Controller $this->middleware('permission:delete-banking-transactions')->only('destroy'); } - /** - * Show the form for creating a new resource. - * - * @param Transaction $transaction - * - * @return Response - */ - public function create(Transaction $transaction) - { + public function create(Transaction $transaction): JsonResponse + { $email_template = config('type.transaction.' . $transaction->type . '.email_template'); - + if (request()->get('email_template')) { $email_template = request()->get('email_template'); } @@ -42,7 +39,7 @@ class TransactionEmails extends Controller case 'invoice_payment_customer': $notification = new PaymentReceivedNotification($transaction->document, $transaction, $email_template, true); break; - + default: $notification = new TransactionNotification($transaction, $email_template, true); break; @@ -73,20 +70,13 @@ class TransactionEmails extends Controller ]); } - /** - * Store a newly created resource in storage. - * - * @param Request $request - * - * @return Response - */ - public function store(Request $request) + public function store(Request $request): JsonResponse { $transaction = Transaction::find($request->get('transaction_id')); $email_template = config('type.transaction.' . $transaction->type . '.email_template'); - $response = $this->ajaxDispatch(new SendTransactionAsCustomMail($request, $email_template)); + $response = $this->sendEmail(new SendTransactionAsCustomMail($request, $email_template)); if ($response['success']) { $route = config('type.transaction.' . $transaction->type . '.route.prefix'); diff --git a/app/Providers/Route.php b/app/Providers/Route.php index ada05b003..69034ef4c 100644 --- a/app/Providers/Route.php +++ b/app/Providers/Route.php @@ -291,7 +291,10 @@ class Route extends Provider }); RateLimiter::for('email', function (Request $request) { - return Limit::perMinute(config('app.throttles.email')); + return [ + Limit::perDay(config('app.throttles.email.month'), 30), + Limit::perMinute(config('app.throttles.email.minute')), + ]; }); } } diff --git a/app/Traits/Emails.php b/app/Traits/Emails.php new file mode 100644 index 000000000..ce8388a80 --- /dev/null +++ b/app/Traits/Emails.php @@ -0,0 +1,55 @@ +id; + $limit_per_month = config('app.throttles.email.month'); + $decay_per_month = 60 * 60 * 24 * 30; + + $can_send = RateLimiter::attempt($key_per_month, $limit_per_month, fn() => '', $decay_per_month); + + if ($can_send) { + // Check if the user has reached the limit of emails per minute + $key_per_minute = 'email-minute:' . user()->id; + $limit_per_minute = config('app.throttles.email.minute'); + + $can_send = RateLimiter::attempt($key_per_minute, $limit_per_minute, fn() => ''); + } + + if ($can_send) { + $this->dispatch($job); + + $response = [ + 'success' => true, + 'error' => false, + 'data' => '', + 'message' => '', + ]; + + return $response; + } + + $response = [ + 'success' => false, + 'error' => true, + 'data' => null, + 'message' => 'Too many emails sent!', + ]; + + report(new TooManyEmailsSent('Too many emails sent!')); + + return $response; + } +} diff --git a/config/app.php b/config/app.php index 7bb8c325a..a01930e41 100644 --- a/config/app.php +++ b/config/app.php @@ -25,7 +25,10 @@ return [ 'throttles' => [ 'api' => env('APP_THROTTLES_API', '60'), 'import' => env('APP_THROTTLES_IMPORT', '1'), - 'email' => env('APP_THROTTLES_EMAIL', '1'), + 'email' => [ + 'minute' => env('APP_THROTTLES_EMAIL_MINUTE', '3'), + 'month' => env('APP_THROTTLES_EMAIL_MONTH', '500'), + ], ], /* diff --git a/routes/admin.php b/routes/admin.php index 0b95b1a03..f81e103e1 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -260,11 +260,11 @@ Route::group(['as' => 'modals.', 'prefix' => 'modals'], function () { ]); Route::get('invoices/{invoice}/emails/create', 'Modals\InvoiceEmails@create')->name('invoices.emails.create'); - Route::post('invoices/{invoice}/emails', 'Modals\InvoiceEmails@store')->middleware('email')->name('invoices.emails.store'); + Route::post('invoices/{invoice}/emails', 'Modals\InvoiceEmails@store')->name('invoices.emails.store'); Route::get('invoices/{invoice}/share/create', 'Modals\InvoiceShare@create')->name('invoices.share.create'); Route::get('transactions/{transaction}/emails/create', 'Modals\TransactionEmails@create')->name('transactions.emails.create'); - Route::post('transactions/{transaction}/emails', 'Modals\TransactionEmails@store')->middleware('email')->name('transactions.emails.store'); + Route::post('transactions/{transaction}/emails', 'Modals\TransactionEmails@store')->name('transactions.emails.store'); Route::get('transactions/{transaction}/share/create', 'Modals\TransactionShare@create')->name('transactions.share.create'); Route::resource('taxes', 'Modals\Taxes');