Set cron job dynamically base from different user timezone - laravel

In laravel 7, I setup cronjobs that runs base from different users specific timezone.
Here's the exact code:
foreach (User::role('admin')->get() as $user) {
/* with queue job */
$schedule->command('weekly-survey:send')->timezone($user->timezone)->weekly()->wednesdays()->at('8:00');
$schedule->command('update:surveys-completed')->timezone($user->timezone)->daily();
$schedule->command('trial:reminder')->timezone($user->timezone)->dailyAt('7:45');
$schedule->command('trial:ends')->timezone($user->timezone)->dailyAt('23:00');
$schedule->command('subscribe:customer')->timezone($user->timezone)->dailyAt('23:45');
$schedule->command('update:user-invoice')->timezone($user->timezone)->everyMinute();
$schedule->command('employees:update-status')->timezone($user->timezone)->everyMinute();
$schedule->command('subscription:update-quantity')->timezone($user->timezone)->daily();
$schedule->command('update:freeze-account')->timezone($user->timezone)->dailyAt('22:45');
$schedule->command('send:upcoming-survey-notification')->timezone($user->timezone)->weeklyOn(1, '8:00');
$schedule->command('surveys:end-soon')->timezone($user->timezone)->dailyAt('8:00');
/* end with queue job */
/* without queue */
$schedule->command('amazon:get-send-qouta')->timezone($user->timezone)->dailyAt('23:55');
$schedule->command('amazon:get-statistics')->timezone($user->timezone)->dailyAt('23:55');
$schedule->command('update:customer-success-table')->timezone($user->timezone)->everyMinute();
$schedule->command('csm:prev-month-active')->timezone($user->timezone)->lastDayOfMonth('23:59');
$schedule->command('update:monthly-earning')->timezone($user->timezone)->lastDayOfMonth('23:59');
$schedule->command('update:subscription-status')->timezone($user->timezone)->everyMinute();
$schedule->command('retrieve:past-due-subscription')->timezone($user->timezone)->dailyAt('23:59');
$user = new User();
$user->accountNotificationsSchedule($schedule);
$schedule->command('horizon:snapshot')->timezone($user->timezone)->everyFiveMinutes();
/* end without queue */
\Log::info("Cron entry successfully executed!");
}
Do this code safe to run? First I loop through all users where has admin role and then pass user time zone to each of the command. I'm sure that this code will infinitely run for as long as the server is alive. I just want to have an alternative safe approach.

Here's what I implemented and it works.
public function handle(TimezoneRepository $timezoneRepository)
{
// get user's unique timezone
$timezones = $timezoneRepository->getUniqueTimezones();
foreach ($timezones as $timezone) {
$date = Carbon::now($timezone);
// send only on Wednesday at 8:00 am
if ($date->isWednesday() && $date->hour == 8 && $date->minute == 0) {
Survey::SendSurvey();
}
}
}

Related

Laravel Console Command Failing to Complete Execution

I have the script below which is used to send messages (sms, email & whatsapp) to some 300k customers. Things where working fine before but for some days now the script is bugging (it freezes at some point in the loop. I can see this with the progress bar which doesn't move further). I have checked laravel.log and there is no particular error message. The script itself does not output any error message.
Below is the code:
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
AccountsCstm::where('authentification_c', 1)->chunk(10000, function ($customer_details) {
$bar = $this->output->createProgressBar(count($customer_details));
$bar->start();
foreach ($customer_details as $customer_detail) {
// Get customer bills and send through the appropriate channel. He could have multiple bills
if (isset($customer_detail->account->sic_code)) {
$bills = TbBillsInfos::where('contract_number', $customer_detail->account->sic_code)->where('bill_status', 'UNPAID')->get();
if (isset($bills)){
foreach ($bills as $bill) {
// Send messages.
if ($customer_detail->is_whatsapp_c) {
$message = $this->bill_message($customer_detail, $bill, 'WhatsApp');
// Send the message
$this->send_whatsapp_message($customer_detail->phone_4_c, $message, $customer_detail->contract_number_c);
// Record the message in database
$this->save_message('WhatsApp', $customer_detail->phone_4_c, null, $message, $customer_detail->account->id);
} elseif ($customer_detail->is_sms_c) {
$message = $this->bill_message($customer_detail, $bill, 'SMS');
// Send the message
$this->send_sms($customer_detail->phone_4_c, $message);
// Record the message in database
$this->save_message('SMS', $customer_detail->phone_4_c, null, $message, $customer_detail->account->id);
} elseif ($customer_detail->is_mail_c) {
$message = $this->bill_message($customer_detail, $bill, 'Email');
// Send the message
$this->send_email($customer_detail, $bill, $message);
// Record the message in database
$this->save_message('Email', null, $customer_detail->mail_c, $message, $customer_detail->account->id);
}
}
}
}
$bar->advance();
}
$bar->finish();
});
// Delete all record from the tb_bills_infos table
// Set the auto-increment value to 0
TbBillsInfos::truncate();
Log::info('send:unpaid-bill-messages: Command executed successfully');
}
Please can someone point out what can be the issue with the above script? Why is it bugging and not completing execution ? Why is it freezing at some point in the loop?
See for example the command since April 21, where as before it will complete execution in about 15 minutes or less
I have equally checked RAM usage on the server and only 1GB out of 8GB is being used. So the server is stable

Laravel notify user with results when batched jobs have finished

I want to email a potentially large number of clients, so I am using Batches and pushing each email send as a batched job, like this:
public function __construct(RunReport $runReport, User $run_by) {
$this->runReport = $runReport;
$this->run_by = $run_by;
}
public function handle()
{
$company_detail = CompanyDetail::first();
$jobs = $this->runReport->runReportReportees
->map(fn(RunReportReportee $runReportReportee) => new EmailStatementJob($runReportReportee, $company_detail))
->toArray();
$batch = Bus
::batch($jobs)
->then(function(Batch $batch) {
// All completed
$completed = ($batch->totalJobs - $batch->failedJobs);
$message = "foo";
$type = "bar";
$this->run_by->notify(new GenericNotification($message, $type, 'envelope'));
})
->allowFailures()
->name('Batch name here trust me')
->dispatch();
return $batch->id;
}
However the notify line causes an error Serialization of 'Doctrine\DBAL\Driver\PDOConnection' is not allowed.
How can I notify the user that initiated the batch when the batch is finished, with the results of the included email send attempts? Alternatively, how else should I email a few hundred clients and notify the user of the results?
I figured it out eventually with help - I was using $this in the ->then() callback which was my first mistake. Then instead of the notification in the job, I have instead stored the user ID and passed it to the then() callback like this
->then(function(Batch $batch) use ($run_by_id) { ... }
In the callback instead of notifying the user, I call an event
event(new HcpStatementsBatchFinishedEvent($batch, $id));
The event simply stores the information
public function __construct(Batch $batch, int $run_by_sysid)
{
$this->run_by = User::find($run_by_id);
$this->batch = $batch;
}
And the listener for the event builds the message and notifies the user.

How to prevent additional page requests after response sent

I have configured a listener on kernel.request which sets a new response with redirect when the session time has reached a certain value. The listener works fine and redirects to a certain page, on the next request, after the session has ended. But my problem is on the page I have many links and if I press multiple times the same link, the initial request with the redirect is cancelled/stopped and a new request is made with the last link pressed and so it passes my redirect even though the session has ended and is destroyed. So, my question is how to prevent additional requests/link presses after the firs request is made?
Here is my code:
public function onKernelRequestSession(GetResponseEvent $event)
{
$request = $event->getRequest();
$route = $request->get('_route');
$session = $request->getSession();
if ((false === strpos($route, '_wdt')) && ($route != null)) {
$session->start();
$time = time() - $session->getMetadataBag()->getCreated();
if ($route != 'main_route_for_idle_page') {
if (!$session->get("active") && $route == 'main_route_for_site_pages') {
$session->invalidate();
$session->set("active", "1");
} else {
if ($time >= $this->sessionTime) {
$session->clear();
$session->invalidate();
$event->setResponse(new RedirectResponse($this->router->generate('main_route_for_idle_page')));
}
}
} else {
if ($session->get("activ")) {
$session->clear();
$session->invalidate();
}
}
}
}
Thak you.
Idea #1: Simple incremental counter
Each request sends sequence number as param which is being verified as expected at the server.
Server increments the number and sends it back via response
the new number is used in future requests
Basically, if server expects the SEQUENCE number to be 2 and client sends 1 the request is to be rejected.
Idea #2: Unique hash each time
Similar to the idea above, but uses unique hashes to eliminate predictive nature of incremental sequence.
I resolved the issue using JQuery: when a link was pressed I disabled the other ones and so only one request is made from the page:
var isClicked = false;
$(".menu-link").click(function(e) {
if(!isClicked) {
isClicked = true;
} else {
e.preventDefault();
}
});
Thanks.

Destroying the session if the user is idle

My goal is to destroy the logged-in used session and force him to log in again if he was idle for 20 minutes. Here is my way of doing it:
In each controller, I do this check:
if(reach_idle_limit()) {
redirect('logout');
}
And reach_idle_limit() is a helper method in one of my helper classes:
function reach_idle_limit() {
$idle_period = 1200; //20 mins
$CI =& get_instance();
$last_activity = $CI->session->userdata('last_activity');
$now_time = time();
//If $last_activity is not set, don't force a logout
if($last_activity == False || $last_activity == 0){
return false;
}
//If idle period exceeded: destroy the session and return true
else if($now_time - $last_activity > $idle_period){
$CI->session->sess_destroy();
return true;
}
//else, update session's last_activity to current time, return false
else{
$CI->session->set_userdata('last_activity', $now_time);
return false;
}
}
This works fine when I give $idle_period a small value, like 60 sec. But when I give it the value I seek, 20 min, it doesn't work!
FYI:
I'm using ag_auth library with Codeigniter (for the authentication part).
My config's variable sess_expiration is set to 0.
In this case, why not use session.gc_maxlifetime?
session.gc_maxlifetime specifies the number of seconds after which data will be seen as 'garbage' and potentially cleaned up. Garbage collection may occur during session start (depending on session.gc_probability and session.gc_divisor).
just use:
ini_set('session.gc_maxlifetime',20);

How can I solve the warning "Warning: array_key_exists."?

I'm using Hybridauth social login, and upon a user authenticating with Facebook, I receive the following error:
Warning: array_key_exists() [function.array-key-exists]: The second
argument should be either an array or an object in
/hybridauth/Hybrid/thirdparty/Facebook/base_facebook.php on line 1328
My guess (probably wrong) to why this may be happening is because the parameters used to pass to Hybridauth come from the browser URL, and I have two - page=register & connected_with=facebook. Hybridauth only requires the second one...
It actually authenticates, but I want rid of this error. Why does this warning occur? Is there a way to hide it?
This is the bit that errors:
/**
* Get the base domain used for the cookie.
*/
protected function getBaseDomain() {
// The base domain is stored in the metadata cookie
// if not we fallback to the current hostname
$metadata = $this->getMetadataCookie();
if (array_key_exists('base_domain', $metadata) &&
!empty($metadata['base_domain'])) {
return trim($metadata['base_domain'], '.');
}
return $this->getHttpHost();
}
It's this code the warning comes from:
/**
* Destroy the current session
*/
public function destroySession() {
$this->accessToken = null;
$this->signedRequest = null;
$this->user = null;
$this->clearAllPersistentData();
// JavaScript sets a cookie that will be used in getSignedRequest
// that we need to clear if we can
$cookie_name = $this->getSignedRequestCookieName();
if (array_key_exists($cookie_name, $_COOKIE)) {
unset($_COOKIE[$cookie_name]);
if (!headers_sent()) {
$base_domain = $this->getBaseDomain();
setcookie($cookie_name, '', 1, '/', '.'.$base_domain);
} else {
// #codeCoverageIgnoreStart
self::errorLog(
'There exists a cookie that we wanted to clear that we couldn\'t '.
'clear because headers was already sent. Make sure to do the first '.
'API call before outputting anything.'
);
// #codeCoverageIgnoreEnd
}
}
}
It looks like getMetadataCookie() does not always return an array, possibly because the cookie has not yet been set. You may want to check that it's actually an array before using it as such;
if (is_array($metadata) && array_key_exists('base_domain', $metadata) &&
For the added code, the same would apply to array_key_exists() in the new code. If you're unsure if it's actually set to an array if the cookie is not set, check first.

Resources