...
 
Commits (2)
......@@ -290,13 +290,21 @@ class boost implements Interfaces\Api
->setType(lcfirst($pages[0]))
->setPriority(false);
if ($manager->checkExisting($boost)) {
return Factory::response([
'status' => 'error',
'message' => "There's already an ongoing boost for this entity"
]);
}
if ($manager->checkExisting($boost)) {
return Factory::response([
'status' => 'error',
'message' => "There's already an ongoing boost for this entity"
]);
}
if ($manager->isBoostLimitExceededBy($boost)) {
$maxDaily = Di::_()->get('Config')->get('max_daily_boost_views') / 1000;
return Factory::response([
'status' => 'error',
'message' => "Exceeded maximum of ".$maxDaily." offchain tokens per day."
]);
}
// Pre-set GUID
if ($bidType == 'tokens' && isset($_POST['guid'])) {
......
......@@ -30,11 +30,13 @@ class ElasticRepository
'rating' => 3,
'token' => 0,
'offset' => null,
'order' => null,
'offchain' => null,
], $opts);
$must = [];
$must_not = [];
$sort = [ '@timestamp' => 'asc' ];
$sort = [ '@timestamp' => $opts['order'] ?? 'asc' ];
$must[] = [
'term' => [
......@@ -67,8 +69,16 @@ class ElasticRepository
if ($opts['entity_guid']) {
$must[] = [
'term' => [
'entity_guid' => $opts['entity_guid']
]
'entity_guid' => $opts['entity_guid'],
],
];
}
if ($opts['owner_guid']) {
$must[] = [
'term' => [
'owner_guid' => $opts['owner_guid'],
],
];
}
......@@ -87,6 +97,14 @@ class ElasticRepository
];
}
if ($opts['offchain']) {
$must[] = [
'term' => [
'token_method' => 'offchain',
],
];
}
if ($opts['state'] === 'review') {
$must_not[] = [
'exists' => [
......@@ -96,7 +114,7 @@ class ElasticRepository
$sort = ['@timestamp' => 'asc'];
}
if ($opts['state'] === 'approved' || $opts['state'] === 'review') {
if ($opts['state'] === 'approved' || $opts['state'] === 'review' || $opts['state'] === 'active') {
$must_not[] = [
'exists' => [
'field' => '@completed',
......
......@@ -25,16 +25,21 @@ class Manager
/** @var GuidBuilder $guidBuilder */
private $guidBuilder;
/** @var Config $config */
private $config;
public function __construct(
$repository = null,
$elasticRepository = null,
$entitiesBuilder = null,
$guidBuilder = null
$guidBuilder = null,
$config = null
) {
$this->repository = $repository ?: new Repository;
$this->elasticRepository = $elasticRepository ?: new ElasticRepository;
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->guidBuilder = $guidBuilder ?: new GuidBuilder;
$this->config = $config ?: Di::_()->get('Config');
}
/**
......@@ -50,14 +55,14 @@ class Manager
'state' => null,
], $opts);
if ($opts['state'] == 'review') {
if ($opts['state'] == 'review' || $opts['state'] == 'active') {
$opts['useElastic'] = true;
}
if ($opts['useElastic']) {
$response = $this->elasticRepository->getList($opts);
if ($opts['state'] === 'review') {
if ($opts['state'] === 'review' || $opts['state'] === 'active') {
$opts['guids'] = array_map(function ($boost) {
return $boost->getGuid();
}, $response->toArray());
......@@ -156,4 +161,52 @@ class Manager
return $existingBoost->count() > 0;
}
/**
* True if the boost is invalid due to the offchain boost limit being reached
*
* @param Boost $type the Boost object.
* @return boolean true if the boost limit has been reached.
*/
public function isBoostLimitExceededBy($boost)
{
//get offchain boosts
$offchain = $this->getOffchainBoosts($boost);
//filter to get todays offchain transactions
$offlineToday = array_filter($offchain->toArray(), function ($result) {
return $result->getCreatedTimestamp() > time() - (60 * 60 * 24);
});
//reduce the impressions to count the days boosts.
$acc = array_reduce($offlineToday, function ($carry, $_boost) {
$carry += $_boost->getImpressions();
return $carry;
}, 0);
$maxDaily = $this->config->get('max_daily_boost_views');
return $acc + $boost->getImpressions() > $maxDaily; //still allow 10k
}
/**
* Gets the users last offchain boosts, from the most recent boost backwards in time.
*
* @param string $type the type of the boost
* @param integer $limit default to 10.
* @return $existingBoosts
*/
public function getOffchainBoosts($boost, $limit = 10)
{
$existingBoosts = $this->getList([
'useElastic' => true,
'state' => 'active',
'type' => $boost->getType(),
'limit' => $limit,
'order' => 'desc',
'offchain' => true,
'owner_guid' => $boost->getOwnerGuid(),
]);
return $existingBoosts;
}
}
......@@ -13,6 +13,7 @@ use Minds\Entities\Activity;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\Core\Di\Di;
class ManagerSpec extends ObjectBehavior
{
......@@ -271,12 +272,12 @@ class ManagerSpec extends ObjectBehavior
public function it_should_check_if_the_entity_was_already_boosted(Boost $boost)
{
$this->elasticRepository->getList([
'hydrate' => true,
'useElastic' => true,
'state' => 'review',
'type' => 'newsfeed',
'entity_guid' => '123',
'limit' => 1,
'hydrate' => true,
'limit' => 1
])
->shouldBeCalled()
->willReturn(new Response([$boost], ''));
......@@ -295,4 +296,109 @@ class ManagerSpec extends ObjectBehavior
$this->checkExisting($boost)->shouldReturn(true);
}
public function it_should_request_offchain_boosts(Boost $boost)
{
$this->elasticRepository->getList([
"hydrate" => true,
"useElastic" => true,
"state" => "active",
"type" => "newsfeed",
"limit" => 10,
"order" => "desc",
"offchain" => true,
"owner_guid" => "123"
])
->shouldBeCalled()
->willReturn(new Response([$boost], ''));
$this->repository->getList(Argument::any())
->shouldBeCalled()
->willReturn(new Response([$boost]));
$boost->getType()
->shouldBeCalled()
->willReturn('newsfeed');
$boost->getOwnerGuid()
->shouldBeCalled()
->willReturn('123');
$this->getOffchainBoosts($boost)->shouldHaveType('Minds\Common\Repository\Response');
}
public function it_should_recognise_a_user_has_reached_the_offchain_boost_limit(Boost $boost)
{
$boostArray = [];
for ($i = 0; $i < 10; $i++) {
$newBoost = new Boost();
$newBoost->setCreatedTimestamp('9999999999999999');
$newBoost->setImpressions(1000);
array_push($boostArray, $newBoost);
}
Di::_()->get('Config')->set('max_daily_boost_views', 10000);
$this->runThroughGetList($boost, $boostArray);
$this->isBoostLimitExceededBy($boost)->shouldReturn(true);
}
public function it_should_recognise_a_user_has_NOT_reached_the_offchain_boost_limit(Boost $boost)
{
$boostArray = [];
for ($i = 0; $i < 9; $i++) {
$newBoost = new Boost();
$newBoost->setCreatedTimestamp('9999999999999999');
$newBoost->setImpressions(1000);
array_push($boostArray, $newBoost);
}
Di::_()->get('Config')->set('max_daily_boost_views', 10000);
$this->runThroughGetList($boost, $boostArray);
$this->isBoostLimitExceededBy($boost)->shouldReturn(false);
}
public function it_should_recognise_a_boost_would_take_user_above_offchain_limit(Boost $boost)
{
$boostArray = [];
for ($i = 0; $i < 2; $i++) {
$newBoost = new Boost();
$newBoost->setCreatedTimestamp('9999999999999999');
$newBoost->setImpressions(4501);
array_push($boostArray, $newBoost);
}
Di::_()->get('Config')->set('max_daily_boost_views', 10000);
$this->runThroughGetList($boost, $boostArray);
$this->isBoostLimitExceededBy($boost)->shouldReturn(true);
}
public function runThroughGetList($boost, $existingBoosts)
{
$this->elasticRepository->getList([
"hydrate" => true,
"useElastic" => true,
"state" => "active",
"type" => "newsfeed",
"limit" => 10,
"order" => "desc",
"offchain" => true,
"owner_guid" => "123"
])
->shouldBeCalled()
->willReturn(new Response($existingBoosts, ''));
$this->repository->getList(Argument::any())
->shouldBeCalled()
->willReturn(new Response($existingBoosts));
$boost->getType()
->shouldBeCalled()
->willReturn('newsfeed');
$boost->getOwnerGuid()
->shouldBeCalled()
->willReturn('123');
$boost->getImpressions()
->shouldBeCalled()
->willReturn(1000);
}
}
......@@ -269,6 +269,9 @@ $CONFIG->set('boost', [
],
]);
/* Maximum view per day */
$CONFIG->set('max_daily_boost_views', 10000);
$CONFIG->set('encryptionKeys', [
'email' => [
'private' => '{{email-private-key}}',
......