Commit c64885d2 authored by Emiliano Balbuena's avatar Emiliano Balbuena

(feat): Lifecycle based on payment events

1 merge request!235WIP: Boost Campaigns (&24)
Pipeline #69998358 passed with stages
in 8 minutes and 30 seconds
......@@ -59,6 +59,9 @@ class BoostProvider extends Provider
$this->di->bind('Boost\Payment', function ($di) {
return new Payment();
}, ['useFactory' => true]);
$this->di->bind('Boost\Campaigns\Dispatcher', function ($di) {
return new Campaigns\Dispatcher();
}, ['useFactory' => true]);
$this->di->bind('Boost\Campaigns\Manager', function ($di) {
return new Campaigns\Manager();
}, ['useFactory' => true]);
......
......@@ -207,6 +207,31 @@ class Campaign implements JsonSerializable
return ($this->budget / $this->impressions) * 1000;
}
/**
* @param $now
* @return bool
*/
public function shouldBeStarted($now)
{
$isCreated = $this->getDeliveryStatus() === static::CREATED_STATUS;
$started = $now >= $this->getStart() && $now < $this->getEnd();
return $isCreated && $started;
}
/**
* @param $now
* @return bool
*/
public function shouldBeCompleted($now)
{
$isDelivering = $this->getDeliveryStatus() !== static::CREATED_STATUS;
$ended = $now >= $this->getEnd();
$fulfilled = $this->getImpressionsMet() >= $this->getImpressions();
return $isDelivering && ($ended || $fulfilled);
}
/**
* @return array
*/
......
......@@ -153,6 +153,35 @@ class PaymentsDelegate
return $campaign;
}
/**
* @param Campaign $campaign
* @param Payment $paymentRef
* @return Campaign
*/
public function onConfirm(Campaign $campaign, Payment $paymentRef)
{
// TODO: Check ALL other payments to ensure budget
$campaign
->setCreatedTimestamp(time() * 1000);
return $campaign;
}
/**
* @param Campaign $campaign
* @param Payment $paymentRef
* @return Campaign
*/
public function onFail(Campaign $campaign, Payment $paymentRef)
{
$campaign
->setCreatedTimestamp(time() * 1000)
->setRevokedTimestamp(time() * 1000);
return $campaign;
}
/**
* @param Campaign $campaign
* @return Campaign
......
<?php
/**
* Dispatcher
* @author edgebal
*/
namespace Minds\Core\Boost\Campaigns;
use Exception;
class Dispatcher
{
/** @var Manager */
protected $manager;
/**
* Dispatcher constructor.
* @param Manager $manager
* @throws Exception
*/
public function __construct(
$manager = null
)
{
$this->manager = $manager ?: new Manager();
}
/**
* @param Campaign $campaignRef
* @throws CampaignException
*/
public function onLifecycle(Campaign $campaignRef)
{
$now = time() * 1000;
$campaign = $this->manager->get($campaignRef->getUrn());
if ($campaign->shouldBeCompleted($now)) {
error_log("[BoostCampaignsDispatcher] Completing {$campaign->getUrn()}...");
$this->manager->complete($campaign);
} elseif ($campaign->shouldBeStarted($now)) {
error_log("[BoostCampaignsDispatcher] Starting {$campaign->getUrn()}...");
$this->manager->start($campaign);
}
}
}
......@@ -11,6 +11,8 @@ use Minds\Common\Repository\Response;
use Minds\Common\Urn;
use Minds\Core\Boost\Campaigns\Payments\Payment;
use Minds\Core\Boost\Campaigns\Payments\Repository as PaymentsRepository;
use Minds\Core\Queue\Client as QueueClient;
use Minds\Core\Queue\Interfaces\QueueClient as QueueClientInterface;
use Minds\Entities\User;
class Manager
......@@ -24,6 +26,9 @@ class Manager
/** @var PaymentsRepository */
protected $paymentsRepository;
/** @var QueueClientInterface */
protected $queueClient;
/** @var Delegates\CampaignUrnDelegate */
protected $campaignUrnDelegate;
......@@ -47,16 +52,19 @@ class Manager
* @param Repository $repository
* @param ElasticRepository $elasticRepository
* @param PaymentsRepository $paymentsRepository
* @param null $queueClient
* @param Delegates\CampaignUrnDelegate $campaignUrnDelegate
* @param Delegates\NormalizeDatesDelegate $normalizeDatesDelegate
* @param Delegates\NormalizeEntityUrnsDelegate $normalizeEntityUrnsDelegate
* @param Delegates\NormalizeHashtagsDelegate $normalizeHashtagsDelegate
* @param Delegates\PaymentsDelegate $paymentsDelegate
* @throws Exception
*/
public function __construct(
$repository = null,
$elasticRepository = null,
$paymentsRepository = null,
$queueClient = null,
$campaignUrnDelegate = null,
$normalizeDatesDelegate = null,
$normalizeEntityUrnsDelegate = null,
......@@ -67,6 +75,7 @@ class Manager
$this->repository = $repository ?: new Repository();
$this->elasticRepository = $elasticRepository ?: new ElasticRepository();
$this->paymentsRepository = $paymentsRepository ?: new PaymentsRepository();
$this->queueClient = $queueClient ?: QueueClient::build();
// Delegates
......@@ -172,7 +181,7 @@ class Manager
// Validate checksum
// TODO: Check checksum!
// TODO: Validate based on data
if (!$campaign->getChecksum()) {
throw new CampaignException('Invalid checksum value');
}
......@@ -199,9 +208,9 @@ class Manager
/**
* @param Campaign $campaignRef
* @param mixed $paymentPayload
* @return Campaign
* @throws CampaignException
* @throws Exception
*/
public function update(Campaign $campaignRef, $paymentPayload = null)
{
......@@ -250,6 +259,14 @@ class Manager
$this->repository->update($campaign);
$this->elasticRepository->update($campaign);
// Queue for lifecycle events
$this->queueClient
->setQueue('BoostCampaignDispatcher')
->send([
'campaign' => serialize($campaign),
]);
//
return $campaign;
......@@ -388,8 +405,6 @@ class Manager
return $campaign;
}
// TODO: Check for completion (on [analytics beacon | cron | delivery manager] -> queue to complete)
// TODO: (time() * 1000 >= $campaign->getEnd() || $campaign->getImpressionsMet() >= $campaign->getImpressions())
/**
* @param Campaign $campaignRef
* @return Campaign
......@@ -435,17 +450,39 @@ class Manager
/**
* @param Payment $paymentRef
* @throws Exception
*/
public function onPaymentSuccess(Payment $paymentRef)
{
$campaign = $this->get("urn:campaign:{$paymentRef->getCampaignGuid()}");
$campaign = $this->paymentsDelegate->onConfirm($campaign, $paymentRef);
// Write
$this->repository->update($campaign);
$this->elasticRepository->update($campaign);
// Queue for lifecycle events
$this->queueClient
->setQueue('BoostCampaignDispatcher')
->send([
'campaign' => serialize($campaign),
]);
}
/**
* @param Payment $paymentRef
* @throws Exception
*/
public function onPaymentFailed(Payment $paymentRef)
{
error_log(print_r(['FAIL!', $paymentRef], true));
$campaign = $this->get("urn:campaign:{$paymentRef->getCampaignGuid()}");
$campaign = $this->paymentsDelegate->onFail($campaign, $paymentRef);
// Write
$this->repository->update($campaign);
$this->elasticRepository->update($campaign);
}
}
......@@ -83,7 +83,7 @@ class Repository
->setCampaignGuid($row['campaign_guid']->toInt())
->setTx($row['tx'])
->setSource($row['source'])
->setAmount($row['amount'])
->setAmount($row['amount']->toDouble())
->setTimeCreated($row['time_created']->time());
$response[] = $payment;
......
<?php
namespace Minds\Core\Queue\Runners;
use Minds\Core\Boost\Campaigns\Manager;
use Minds\Core\Queue;
use Minds\Core\Queue\Interfaces;
use Minds\Core\Di\Di;
class BoostCampaignChecker implements Interfaces\QueueRunner
{
public function run()
{
$client = Queue\Client::Build();
$client->setQueue("BoostCampaignChecker")
->receive(function ($msg) {
$data = $msg->getData();
$campaignRef = $data['campaignRef'];
echo "Checking boost campaign {$campaignRef->getUrn()}\n";
/** @var Manager $manager */
$manager = Di::_()->get('Boost\Campaigns\Manager');
$manager->complete($campaignRef);
});
}
}
<?php
namespace Minds\Core\Queue\Runners;
use Minds\Core\Boost\Campaigns\Campaign;
use Minds\Core\Boost\Campaigns\Dispatcher;
use Minds\Core\Queue;
use Minds\Core\Queue\Interfaces;
use Minds\Core\Di\Di;
class BoostCampaignDispatcher implements Interfaces\QueueRunner
{
public function run()
{
$client = Queue\Client::build();
$client
->setQueue('BoostCampaignDispatcher')
->receive(function (Queue\Message $msg) {
$data = $msg->getData();
/** @var Campaign $campaignRef */
$campaignRef = is_string($data['campaign']) ? unserialize($data['campaign']) : $data['campaign'];
echo "Checking boost campaign {$campaignRef->getUrn()} status...\n";
/** @var Dispatcher $dispatcher */
$dispatcher = Di::_()->get('Boost\Campaigns\Dispatcher');
$dispatcher->onLifecycle($campaignRef);
});
}
}
......@@ -158,12 +158,12 @@ redirect_stderr=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
[program:minds-boost-campaing-checker]
[program:minds-boost-campaing-dispatcher]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/Minds/engine/cli.php QueueRunner run --runner=BoostCampaignChecker
command=php /var/www/Minds/engine/cli.php QueueRunner run --runner=BoostCampaignDispatcher
autostart=true
autorestart=true
numprocs=2
numprocs=1
redirect_stderr=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment