Commit cd72577e authored by Guy Thouret's avatar Guy Thouret

Refactor Boost Code - #1149

parent ab9311d8
No related merge requests found
Pipeline #99972820 failed with stages
in 2 minutes and 42 seconds
......@@ -171,16 +171,17 @@ class Controller
/**
* Gets the list of publically available commands and filters out the system ones.
* @param array $additionalExcludes Additional methods to exclude from command list (optional)
*/
public function getCommands()
public function getCommands(array $additionalExcludes = [])
{
$excludedMethods = ['__construct', 'help', 'out', 'setArgs', 'setApp', 'getApp', 'getExecCommand', 'getOpt', 'getOpts', 'getAllOpts', 'getCommands', 'displayCommandHelp'];
$excludedMethods = ['__construct', 'help', 'out', 'setArgs', 'setApp', 'getApp', 'getExecCommand', 'getOpt', 'getOpts', 'getAllOpts', 'getCommands', 'displayCommandHelp', 'exec', 'gatekeeper'];
$commands = [];
foreach ((new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$commands[] = $method->getName();
}
return array_diff($commands, $excludedMethods);
return array_diff($commands, $excludedMethods, $additionalExcludes);
}
public function displayCommandHelp()
......
<?php
/**
* Minds Admin: Boost Analytics
*
* @version 1
* @author Emi Balbuena
*
*/
namespace Minds\Controllers\api\v1\admin\boosts;
use Minds\Controllers\api\v1\newsfeed\preview;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Helpers;
use Minds\Entities;
use Minds\Interfaces;
use Minds\Api\Factory;
class analytics implements Interfaces\Api, Interfaces\ApiAdminPam
{
/**
* GET
*/
public function get($pages)
{
$response = [];
$type = isset($pages[0]) ? $pages[0] : 'newsfeed';
/** @var Core\Boost\Network\Review $review */
$review = Di::_()->get('Boost\Network\Review');
$review->setType($type);
/** @var Core\Boost\Network\Metrics $metrics */
$metrics = Di::_()->get('Boost\Network\Metrics');
$metrics->setType($type);
$cache = Di::_()->get('Cache');
$cacheKey = "admin:boosts:analytics:{$type}";
if ($cached = $cache->get($cacheKey)) {
return Factory::response($cached);
}
$reviewQueue = $review->getReviewQueueCount();
$backlog = $metrics->getBacklogCount();
$priorityBacklog = $metrics->getPriorityBacklogCount();
$impressions = $metrics->getBacklogImpressionsSum();
$avgApprovalTime = $metrics->getAvgApprovalTime();
$avgImpressions = round($impressions / ($backlog ?: 1));
$timestamp = time();
$response = compact(
'reviewQueue',
'backlog',
'priorityBacklog',
'impressions',
'avgApprovalTime',
'avgImpressions',
'timestamp'
);
$cache->set($cacheKey, $response, 15 * 60 /* 15min cache */);
return Factory::response($response);
}
/**
* POST
*/
public function post($pages)
{
return Factory::response([]);
}
/**
* PUT
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* DELETE
*/
public function delete($pages)
{
return Factory::response([]);
}
}
<?php
/**
* Minds Boost Api endpoint
*
* @version 1
* @author Mark Harding
*
*/
namespace Minds\Controllers\api\v1\boost;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities;
use Minds\Entities\Entity;
use Minds\Helpers\Counters;
use Minds\Interfaces;
use Minds\Core\Boost;
class fetch implements Interfaces\Api
{
......@@ -28,14 +22,16 @@ class fetch implements Interfaces\Api
$user = Core\Session::getLoggedinUser();
if (!$user) {
return Factory::response([
Factory::response([
'status' => 'error',
'message' => 'You must be loggedin to view boosts',
]);
return;
}
if ($user->disabled_boost && $user->isPlus()) {
return Factory::response([]);
Factory::response([]);
return;
}
$limit = isset($_GET['limit']) ? (int) $_GET['limit'] : 2;
......@@ -45,35 +41,33 @@ class fetch implements Interfaces\Api
// options specific to newly created users (<=1 hour) and iOS users
if (time() - $user->getTimeCreated() <= 3600) {
$rating = 1; // they can only see safe content
$rating = Boost\Network\Boost::RATING_SAFE;
$quality = 75;
}
if ($platform === 'ios') {
$rating = 1; // they can only see safe content
$rating = Boost\Network\Boost::RATING_SAFE;
$quality = 90;
}
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
/** @var $iterator */
$iterator = new Core\Boost\Network\Iterator();
$iterator->setLimit($limit)
->setRating($rating)
->setQuality($quality)
->setOffset($_GET['offset'])
->setType($pages[0])
->setPriority(true);
->setUserGuid($user->getGUID());
if (isset($_GET['rating']) && $pages[0] == 'newsfeed') {
if (isset($_GET['rating']) && $pages[0] == Boost\Network\Boost::TYPE_NEWSFEED) {
$cacher = Core\Data\cache\factory::build('Redis');
$offset = $cacher->get(Core\Session::getLoggedinUser()->guid . ':boost-offset:newsfeed');
$iterator->setOffset($offset);
}
switch ($pages[0]) {
case 'content':
//$iterator->setOffset('');
$iterator->setIncrement(true);
case Boost\Network\Boost::TYPE_CONTENT:
/** @var $entity Entity */
foreach ($iterator as $guid => $entity) {
$response['boosts'][] = array_merge($entity->export(), [
'boosted_guid' => (string) $guid,
......@@ -87,7 +81,7 @@ class fetch implements Interfaces\Api
if (!$response['boosts']) {
$result = Di::_()->get('Trending\Repository')->getList([
'type' => 'images',
'rating' => isset($rating) ? (int) $rating : 1,
'rating' => isset($rating) ? (int) $rating : Boost\Network\Boost::RATING_SAFE,
'limit' => $limit,
]);
......@@ -97,7 +91,7 @@ class fetch implements Interfaces\Api
}
}
break;
case 'newsfeed':
case Boost\Network\Boost::TYPE_NEWSFEED:
foreach ($iterator as $guid => $entity) {
$response['boosts'][] = array_merge($entity->export(), [
'boosted' => true,
......@@ -109,70 +103,25 @@ class fetch implements Interfaces\Api
if (isset($_GET['rating']) && $pages[0] == 'newsfeed') {
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset:newsfeed', $iterator->getOffset(), (3600 / 2));
}
if (!$iterator->list && false) {
$cacher = Core\Data\cache\factory::build('apcu');
$offset = (int) $cacher->get(Core\Session::getLoggedinUser()->guid . ":newsfeed-fallover-boost-offset") ?: 0;
$posts = $this->getSuggestedPosts([
'offset' => $offset,
'limit' => $limit,
'rating' => $rating,
]);
foreach ($posts as $entity) {
$entity->boosted = true;
$response['boosts'][] = array_merge($entity->export(), [ 'boosted' => true ]);
}
if (!$response['boosts'] || count($response['boosts']) < 5) {
$cacher->destroy(Core\Session::getLoggedinUser()->guid . ":newsfeed-fallover-boost-offset");
} else {
$cacher->set(Core\Session::getLoggedinUser()->guid . ":newsfeed-fallover-boost-offset", ((int) $offset) + count($posts));
}
}
break;
}
return Factory::response($response);
Factory::response($response);
}
/**
*/
public function post($pages)
{
/* Not Implemented */
}
/**
* @param array $pages
*/
public function put($pages)
{
$expire = Core\Di\Di::_()->get('Boost\Network\Expire');
$metrics = Core\Di\Di::_()->get('Boost\Network\Metrics');
$boost = Core\Boost\Factory::build($pages[0])->getBoostEntity($pages[1]);
if (!$boost) {
return Factory::response([
'status' => 'error',
'message' => 'Boost not found'
]);
}
$count = $metrics->incrementViews($boost);
if ($count > $boost->getImpressions()) {
$expire->setBoost($boost);
$expire->expire();
}
Counters::increment($boost->getEntity()->guid, "impression");
Counters::increment($boost->getEntity()->owner_guid, "impression");
return Factory::response([]);
/* Not Implemented */
}
/**
*/
public function delete($pages)
{
/* Not Implemented */
}
private function getSuggestedPosts($opts = [])
......
......@@ -181,24 +181,18 @@ class newsfeed implements Interfaces\Api
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator->setPriority(!get_input('offset', ''))
->setType('newsfeed')
$iterator->setType('newsfeed')
->setLimit($limit)
->setOffset($offset)
//->setRating(0)
->setQuality(0)
->setIncrement(false);
->setUserGuid(Core\Session::getLoggedinUserGuid())
->setRating((int) Core\Session::getLoggedinUser()->getBoostRating());
foreach ($iterator as $guid => $boost) {
$boost->boosted = true;
$boost->boosted_guid = (string) $guid;
array_unshift($activity, $boost);
//if (get_input('offset')) {
//bug: sometimes views weren't being calculated on scroll down
//Counters::increment($boost->guid, "impression");
//Counters::increment($boost->owner_guid, "impression");
//}
}
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset:newsfeed', $iterator->getOffset(), (3600 / 2));
} catch (\Exception $e) {
......
......@@ -71,10 +71,11 @@ class views implements Interfaces\Api
$entity = Entities\Factory::build($pages[1]);
if (!$entity) {
return Factory::response([
Factory::response([
'status' => 'error',
'message' => 'Could not the entity'
]);
return;
}
if ($entity->type === 'activity') {
......@@ -123,16 +124,16 @@ class views implements Interfaces\Api
break;
}
return Factory::response([]);
Factory::response([]);
}
public function put($pages)
{
return Factory::response([]);
Factory::response([]);
}
public function delete($pages)
{
return Factory::response([]);
Factory::response([]);
}
}
This diff is collapsed.
<?php
/**
* Boost Fetch
*
* @version 2
* @author emi
*
*/
namespace Minds\Controllers\api\v2\boost;
use Minds\Api\Exportable;
use Minds\Common\Urn;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Helpers;
use Minds\Entities;
use Minds\Entities\User;
use Minds\Interfaces;
use Minds\Api\Factory;
class feed implements Interfaces\Api
{
/** @var User */
protected $currentUser;
/** @var array */
protected $boosts = [];
protected $next;
protected $type;
protected $limit;
protected $offset;
protected $rating;
protected $platform;
protected $quality = 0;
protected $isBoostFeed;
/**
* Equivalent to HTTP GET method
* @param array $pages
......@@ -30,133 +34,84 @@ class feed implements Interfaces\Api
{
Factory::isLoggedIn();
/** @var Entities\User $currentUser */
$currentUser = Core\Session::getLoggedinUser();
$this->currentUser = Core\Session::getLoggedinUser();
if ($currentUser->disabled_boost && $currentUser->isPlus()) {
return Factory::response([
'boosts' => [],
]);
if (!$this->parseAndValidateParams() || !$this->validBoostUser()) {
$this->sendResponse();
return;
}
// Parse parameters
$type = $pages[0] ?? 'newsfeed';
$limit = abs(intval($_GET['limit'] ?? 2));
$offset = $_GET['offset'] ?? null;
$rating = intval($_GET['rating'] ?? $currentUser->getBoostRating());
$platform = $_GET['platform'] ?? 'other';
$quality = 0;
$isBoostFeed = $_GET['boostfeed'] ?? false;
if ($limit === 0) {
return Factory::response([
'boosts' => [],
]);
} elseif ($limit > 500) {
$limit = 500;
}
$this->type = $pages[0] ?? 'newsfeed';
$cacher = Core\Data\cache\factory::build('Redis');
$offset = $cacher->get(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator');
if ($this->isBoostFeed) {
$this->offset = $_GET['from_timestamp'] ?? 0;
}
if ($isBoostFeed) {
$offset = $_GET['from_timestamp'] ?? 0;
switch ($this->type) {
case 'newsfeed':
$this->getBoosts();
break;
default:
$this->sendError('Unsupported boost type');
return;
}
// Options specific to newly created users (<=1 hour) and iOS users
$this->sendResponse();
}
if ($platform === 'ios') {
$rating = 1; // they can only see safe content
$quality = 90;
} elseif (time() - $currentUser->getTimeCreated() <= 3600) {
$rating = 1; // they can only see safe content
$quality = 75;
protected function parseAndValidateParams(): bool
{
$this->limit = abs(intval($_GET['limit'] ?? 2));
$this->offset = $_GET['offset'] ?? 0;
$this->rating = intval($_GET['rating'] ?? $this->currentUser->getBoostRating());
$this->platform = $_GET['platform'] ?? 'other';
$this->isBoostFeed = $_GET['boostfeed'] ?? false;
if ($this->limit === 0) {
return false;
}
//
if ($this->limit > 500) {
$this->limit = 500;
}
$boosts = [];
$next = null;
return true;
}
switch ($type) {
case 'newsfeed':
// Newsfeed boosts
$resolver = new Core\Entities\Resolver();
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator
->setLimit(10)
->setOffset($offset)
->setRating($rating)
->setQuality($quality)
->setType($type)
->setPriority(true)
->setHydrate(false);
foreach ($iterator as $boost) {
$feedSyncEntity = new Core\Feeds\FeedSyncEntity();
$feedSyncEntity
->setGuid((string) $boost->getGuid())
->setOwnerGuid((string) $boost->getOwnerGuid())
->setTimestamp($boost->getCreatedTimestamp())
->setUrn(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
$entity = $resolver->single(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
if (!$entity) {
continue; // Duff entity?
}
$feedSyncEntity->setEntity($entity);
$boosts[] = $feedSyncEntity;
}
// $boosts = iterator_to_array($iterator, false);
$next = $iterator->getOffset();
if (isset($boosts[1]) && !$isBoostFeed) { // Always offset to 2rd in list if in rotator
// if (!$offset) {
// $next = $boosts[1]->getTimestamp();
// } else {
// $next = 0;
// }
$len = count($boosts);
if ($boosts[$len -1]) {
$next = $boosts[$len -1]->getTimestamp();
}
} elseif ($isBoostFeed) {
$len = count($boosts);
if ($boosts[$len -1]) {
$next = $boosts[$len -1]->getTimestamp();
}
}
// $ttl = 1800; // 30 minutes
// if (($next / 1000) < strtotime('48 hours ago')) {
$ttl = 300; // 5 minutes;
// }
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator', $next, $ttl);
break;
protected function validBoostUser(): bool
{
return !($this->currentUser->disabled_boost && $this->currentUser->isPlus());
}
case 'content':
// TODO: Content boosts
default:
return Factory::response([
'status' => 'error',
'message' => 'Unsupported boost type'
]);
}
protected function sendResponse(): void
{
$boosts = empty($this->boosts) ? [] : Exportable::_($this->boosts);
Factory::response([
'entities' => $boosts,
'load-next' => $this->next,
]);
}
return Factory::response([
'entities' => Exportable::_($boosts),
'load-next' => $next ?: null,
protected function sendError(string $message): void
{
Factory::response([
'status' => 'error',
'message' => $message
]);
}
protected function getBoosts()
{
$feed = new Core\Boost\Feeds\Boost($this->currentUser);
$this->boosts = $feed->setLimit($this->limit)
->setOffset($this->offset)
->setRating($this->rating)
->setPlatform($this->platform)
->setQuality($this->quality)
->get();
$this->next = $feed->getOffset();
}
/**
* Equivalent to HTTP POST method
* @param array $pages
......
<?php
/**
* Boost Fetch
*
* @version 2
* @author emi
*
*/
namespace Minds\Controllers\api\v2\boost;
use Minds\Api\Exportable;
use Minds\Common\Urn;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Helpers;
use Minds\Entities;
use Minds\Interfaces;
use Minds\Api\Factory;
class fetch implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws \Exception
*/
public function get($pages)
{
Factory::isLoggedIn();
/** @var Entities\User $currentUser */
$currentUser = Core\Session::getLoggedinUser();
if ($currentUser->disabled_boost && $currentUser->isPlus()) {
return Factory::response([
'boosts' => [],
]);
}
// Parse parameters
$type = $pages[0] ?? 'newsfeed';
$limit = abs(intval($_GET['limit'] ?? 2));
$offset = $_GET['offset'] ?? null;
$rating = intval($_GET['rating'] ?? $currentUser->getBoostRating());
$platform = $_GET['platform'] ?? 'other';
$quality = 0;
$sync = (bool) ($_GET['sync'] ?? true);
if ($limit === 0) {
return Factory::response([
'boosts' => [],
]);
} elseif ($sync && $limit > 500) {
$limit = 500;
} elseif (!$sync && $limit > 50) {
$limit = 50;
}
// Options specific to newly created users (<=1 hour) and iOS users
if ($platform === 'ios') {
$rating = 1; // they can only see safe content
$quality = 90;
} elseif (time() - $currentUser->getTimeCreated() <= 3600) {
$rating = 1; // they can only see safe content
$quality = 75;
}
//
$boosts = [];
$next = null;
switch ($type) {
case 'newsfeed':
// Newsfeed boosts
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator
->setLimit($limit)
->setOffset($offset)
->setRating($rating)
->setQuality($quality)
->setType($type)
->setPriority(true)
->setHydrate(!$sync);
if ($sync) {
foreach ($iterator as $boost) {
$feedSyncEntity = new Core\Feeds\FeedSyncEntity();
$feedSyncEntity
->setGuid((string) $boost->getGuid())
->setOwnerGuid((string) $boost->getOwnerGuid())
->setUrn(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
$boosts[] = $feedSyncEntity;
}
} else {
$boosts = iterator_to_array($iterator, false);
}
$next = $iterator->getOffset();
break;
case 'content':
// TODO: Content boosts
default:
return Factory::response([
'status' => 'error',
'message' => 'Unsupported boost type'
]);
}
return Factory::response([
'boosts' => Exportable::_($boosts),
'load-next' => $next ?: null,
]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public function post($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -54,13 +54,13 @@ class suggested implements Interfaces\Api
$offset = intval($_GET['offset']);
}
$rating = Core\Session::getLoggedinUser()->boost_rating ?: 1;
$rating = Core\Session::getLoggedinUser()->boost_rating ?: Core\Boost\Network\Boost::RATING_SAFE;
if (isset($_GET['rating'])) {
$rating = intval($_GET['rating']);
}
if ($type == 'user') {
$rating = 1;
$rating = Core\Boost\Network\Boost::RATING_SAFE;
}
$hashtag = null;
......
<?php
/**
* Blockchain Peer Boost Events
*
* @author emi
*/
namespace Minds\Core\Blockchain\Events;
use Minds\Core\Blockchain\Transactions\Repository;
use Minds\Core\Blockchain\Transactions\Transaction;
use Minds\Core\Data;
use Minds\Core\Config;
use Minds\Core\Di\Di;
class BoostEvent implements BlockchainEventInterface
......@@ -23,8 +17,6 @@ class BoostEvent implements BlockchainEventInterface
'blockchain:fail' => 'boostFail',
];
protected $mongo;
/** @var Repository $txRepository */
protected $txRepository;
......@@ -35,12 +27,10 @@ class BoostEvent implements BlockchainEventInterface
protected $config;
public function __construct(
$mongo = null,
$txRepository = null,
$boostRepository = null,
$config = null
) {
$this->mongo = $mongo ?: Data\Client::build('MongoDB');
$this->txRepository = $txRepository ?: Di::_()->get('Blockchain\Transactions\Repository');
$this->boostRepository = $boostRepository ?: Di::_()->get('Boost\Repository');
$this->config = $config ?: Di::_()->get('Config');
......@@ -95,13 +85,8 @@ class BoostEvent implements BlockchainEventInterface
}
$transaction->setFailed(true);
$this->txRepository->update($transaction, [ 'failed' ]);
$boost->setState('failed')
->save();
$this->mongo->remove("boost", [ '_id' => $boost->getId() ]);
$boost->setState('failed')->save();
}
public function boostSent($log, $transaction)
......@@ -126,8 +111,7 @@ class BoostEvent implements BlockchainEventInterface
throw new \Exception("Boost with hash {$tx} already processed. State: " . $boost->getState());
}
$boost->setState('created')
->save();
$boost->setState('created')->save();
echo "{$boost->getGuid()} now marked completed";
}
......
......@@ -3,18 +3,17 @@
namespace Minds\Core\Boost;
use Minds\Core\Boost\Network;
use Minds\Core\Data;
use Minds\Core\Data\Client;
use Minds\Core\Di\Provider;
use Minds\Core\Di;
/**
* Boost Providers
*/
class BoostProvider extends Provider
class BoostProvider extends Di\Provider
{
/**
* Registers providers onto DI
* @return void
* @throws Di\ImmutableException
*/
public function register()
{
......@@ -22,40 +21,22 @@ class BoostProvider extends Provider
return new Repository();
}, ['useFactory' => true]);
$this->di->bind('Boost\Network', function ($di) {
return new Network([], Client::build('MongoDB'), new Data\Call('entities_by_time'));
}, ['useFactory' => true]);
$this->di->bind('Boost\Network\Manager', function ($di) {
return new Network\Manager;
return new Network\Manager();
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Iterator', function ($di) {
return new Network\Iterator();
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Metrics', function ($di) {
return new Network\Metrics(Client::build('MongoDB'));
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Review', function ($di) {
return new Network\Review();
}, ['useFactory' => false]);
$this->di->bind('Boost\Network\Expire', function ($di) {
return new Network\Expire();
}, ['useFactory' => false]);
$this->di->bind('Boost\Newsfeed', function ($di) {
return new Newsfeed([], Client::build('MongoDB'), new Data\Call('entities_by_time'));
}, ['useFactory' => true]);
$this->di->bind('Boost\Content', function ($di) {
return new Content([], Client::build('MongoDB'), new Data\Call('entities_by_time'));
}, ['useFactory' => true]);
$this->di->bind('Boost\Peer', function ($di) {
return new Peer();
}, ['useFactory' => true]);
$this->di->bind('Boost\Peer\Metrics', function ($di) {
return new Peer\Metrics(Client::build('MongoDB'));
}, ['useFactory' => false]);
$this->di->bind('Boost\Peer\Review', function ($di) {
return new Peer\Review();
}, ['useFactory' => false]);
$this->di->bind('Boost\Payment', function ($di) {
return new Payment();
}, ['useFactory' => true]);
......
<?php
namespace Minds\Core\Boost;
use Minds\Core;
use Minds\Core\Data;
use Minds\Interfaces;
use Minds\Entities;
use Minds\Helpers;
/**
* Channel boost handler
* @deprecated Please use the Peer controller instead. This is for polyfill support of mobile only
*/
class Channel implements Interfaces\BoostHandlerInterface
{
private $guid;
public function __construct($options)
{
if (isset($options['destination'])) {
if (is_numeric($options['destination'])) {
$this->guid = $options['destination'];
} elseif (is_string($options['destination'])) {
$lookup = new Data\lookup();
$this->guid = key($lookup->get(strtolower($options['destination'])));
}
}
}
/**
* Boost an entity
* @param object/int $entity - the entity to boost
* @param int $points
* @return boolean
*/
public function boost($entity_guid, $points)
{
$entity = Entities\Factory::build($entity_guid);
$destination = Entities\Factory::build($this->guid);
$boost = (new Entities\Boost\Peer())
->setEntity($entity)
->setType('points')
->setBid($points)
->setDestination($destination)
->setOwner(Core\Session::getLoggedInUser())
->setState('created')
->save();
Core\Events\Dispatcher::trigger('notification', 'boost', [
'to'=> [$boost->getDestination()->guid],
'entity' => $boost->getEntity(),
'notification_view' => 'boost_peer_request',
'params' => [
'bid' => $boost->getBid(),
'type' => $boost->getType(),
'title' => $boost->getEntity()->title ?: $boost->getEntity()->message
]
]);
return $boost->getGuid();
/*if (is_object($entity)) {
$guid = $entity->guid;
} else {
$guid = $entity;
}
$db = new Data\Call('entities_by_time');
$result = $db->insert("boost:channel:$this->guid:review", array($guid => $points));
//send a notification of boost offer
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to' => array($this->guid),
'entity' => $guid,
'notification_view' => 'boost_request',
'params' => array('points'=>$points),
'points' => $points
));
//send back to use
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array(Core\Session::getLoggedinUser()->guid),
'entity' => $guid,
'notification_view' => 'boost_submitted_p2p',
'params' => array(
'points' => $points,
'channel' => isset($_POST['destination']) ? $_POST['destination'] : $this->guid
),
'points' => $points
));
return $result;*/
}
/**
* Return boosts for review
* @param int $limit
* @param string $offset
* @return array
*/
public function getReviewQueue($limit, $offset = "")
{
return false;
}
/**
* Accept a boost and do a remind
* @param object/int $entity
* @param int points
* @return boolean
*/
public function accept($entity, $points)
{
/*if (is_object($entity)) {
$guid = $entity->guid;
} else {
$guid = $entity;
}
$db = new Data\Call('entities_by_time');
$embeded = new Entities\Entity($guid);
$embeded = core\Entities::build($embeded); //more accurate, as entity doesn't do this @todo maybe it should in the future
\Minds\Helpers\Counters::increment($guid, 'remind');
$activity = new Entities\Activity();
$activity->p2p_boosted = true;
switch ($embeded->type) {
case 'activity':
if ($embeded->remind_object) {
$activity->setRemind($embeded->remind_object)->save();
} else {
$activity->setRemind($embeded->export())->save();
}
break;
case 'object':
break;
default:
switch ($embeded->subtype) {
case 'blog':
$message = false;
if ($embeded->owner_guid != elgg_get_logged_in_user_guid()) {
$message = 'via <a href="'.$embeded->getOwnerEntity()->getURL() . '">'. $embeded->getOwnerEntity()->name . '</a>';
}
$activity->p2p_boosted = true;
$activity->setTitle($embeded->title)
->setBlurb(elgg_get_excerpt($embeded->description))
->setURL($embeded->getURL())
->setThumbnail($embeded->getIconUrl())
->setMessage($message)
->setFromEntity($embeded)
->save();
break;
}
}
//remove from review
error_log('user_guid is ' . $this->guid);
$db->removeAttributes("boost:channel:$this->guid:review", array($guid));
$db->removeAttributes("boost:channel:all:review", array("$this->guid:$guid"));
$entity = new \Minds\Entities\Activity($guid);
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array($entity->owner_guid),
'entity' => $guid,
'title' => $entity->title,
'notification_view' => 'boost_accepted',
'params' => array('points'=>$points),
'points' => $points
));
return true;
*/
}
/**
* Reject a boost
* @param object/int $entity
* @return boolean
*/
public function reject($entity)
{
///
/// REFUND THE POINTS TO THE USER
///
/* if (is_object($entity)) {
$guid = $entity->guid;
} else {
$guid = $entity;
}
$db = new Data\Call('entities_by_time');
$db->removeAttributes("boost:channel:$this->guid:review", array($guid));
$db->removeAttributes("boost:channel:all:review", array("$this->guid:$guid"));
$entity = new \Minds\Entities\Activity($guid);
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array($entity->owner_guid),
'entity' => $guid,
'title' => $entity->title,
'notification_view' => 'boost_rejected',
));
return true;//need to double check somehow..
*/
}
/**
* Return a boost
* @return array
*/
public function getBoost($offset = "")
{
///
//// THIS DOES NOT APPLY BECAUSE IT'S PRE-AGREED
///
}
public function autoExpire()
{
/*$db = new Data\Call('entities_by_time');
$boosts = $db->getRow("boost:channel:all:review");
foreach ($boosts as $boost => $ts) {
list($destination, $guid) = explode(':', $boost);
if (time() > $ts + (3600 * 48)) {
$this->guid = $destination;
$guids = $this->getReviewQueue(1, $guid);
$points = reset($guids);
if (!$destination) {
echo "$guid issue with destination.. \n";
continue;
}
echo "$guid has expired. refunding ($points) points to $destination \n";
$db->removeAttributes("boost:channel:all:review", array($boost));
$db->removeAttributes("boost:channel:$destination:review", array($guid));
$entity = new \Minds\Entities\Activity($guid);
Helpers\Wallet::createTransaction($entity->owner_guid, $points, $guid, "boost refund");
Core\Events\Dispatcher::trigger('notification', 'boost', array(
'to'=>array($entity->owner_guid),
'from'=> $destination,
'entity' => $entity,
'title' => $entity->title,
'notification_view' => 'boost_rejected',
));
} else {
echo "$guid is ok... \n";
}
}*/
}
/**
* @param mixed $entity
* @return boolean
*/
public static function validateEntity($entity)
{
return true;
}
}
<?php
/**
* Description
*
* @author emi
*/
namespace Minds\Core\Boost;
use Minds\Entities\Entity;
......
......@@ -4,7 +4,6 @@ namespace Minds\Core\Boost;
use Minds\Core\Email\Campaigns;
use Minds\Core\Events\Dispatcher;
use Minds\Core\Payments;
/**
* Minds Payments Events.
......
<?php
namespace Minds\Core\Boost\Exceptions;
use Throwable;
class EntityAlreadyBoostedException extends \Exception
{
public function __construct($code = 0, Throwable $previous = null)
{
parent::__construct("There's already an ongoing boost for this entity", $code, $previous);
}
}
<?php
namespace Minds\Core\Boost;
use Minds\Core\Data;
use Minds\Interfaces;
/**
* A factory providing handlers boosting items
*/
class Factory
{
public static function getClassHandler($handler)
{
$handler = ucfirst($handler);
$handler = "Minds\\Core\\Boost\\$handler";
if (class_exists($handler)) {
return $handler;
}
throw new \Exception("Handler not found");
}
/**
* Build the handler
* @param string $handler
* @param array $options (optional)
* @return BoostHandlerInterface
*/
public static function build($handler, $options = [], $db = null)
{
if ($handler == 'newsfeed') {
$handler = 'network';
}
$handler = ucfirst($handler);
$handler = "Minds\\Core\\Boost\\$handler";
if (class_exists($handler)) {
$class = new $handler($options, $db);
if ($class instanceof Interfaces\BoostHandlerInterface) {
return $class;
}
}
throw new \Exception("Handler not found");
}
}
<?php
namespace Minds\Core\Boost;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Entities\Resolver;
use Minds\Entities\User;
use Minds\Core;
use Minds\Helpers\Time;
abstract class Feed
{
/** @var Resolver */
protected $resolver;
/** @var User */
protected $currentUser;
/** @var abstractCacher */
protected $cacher;
protected $mockIterator;
/** @var array */
protected $boosts = [];
protected $offset;
protected $limit;
protected $rating;
protected $platform;
protected $quality = 0;
protected $type = 'newsfeed';
protected $offsetCacheTtl = Time::FIVE_MIN;
public function __construct(
User $currentUser = null,
Resolver $resolver = null,
abstractCacher $cacher = null
) {
$this->currentUser = $currentUser ?: Core\Session::getLoggedinUser();
$this->resolver = $resolver ?: new Resolver();
$this->cacher = $cacher ?: Core\Data\cache\factory::build('Redis');
}
public function setMockIterator($mockIterator): self
{
$this->mockIterator = $mockIterator;
return $this;
}
public function setLimit(int $limit): self
{
$this->limit = $limit;
return $this;
}
public function getOffset(): int
{
return $this->offset;
}
public function setOffset(int $offset): self
{
$this->offset = $offset;
return $this;
}
public function setRating(int $rating): self
{
$this->rating = $rating;
return $this;
}
public function setPlatform(string $platform): self
{
$this->platform = $platform;
return $this;
}
public function setQuality(int $quality): self
{
$this->quality = $quality;
return $this;
}
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* @return Core\Feeds\FeedSyncEntity[]
*/
public function get(): array
{
$this->makeRatingAndQualitySafe();
$this->setOffsetFromCache();
$this->getItems();
$this->setOffsetCache();
return $this->boosts;
}
protected function makeRatingAndQualitySafe(): void
{
if ($this->platform === 'ios') {
$this->rating = Core\Boost\Network\Boost::RATING_SAFE;
$this->quality = 90;
} elseif (time() - $this->currentUser->getTimeCreated() <= Time::ONE_HOUR) {
$this->rating = Core\Boost\Network\Boost::RATING_SAFE;
$this->quality = 75;
}
}
protected function setOffsetFromCache(): void
{
$offsetCache = $this->getOffsetCache();
if (is_int($offsetCache)) {
$this->offset = $offsetCache;
}
}
protected function setOffsetCache(): void
{
$this->cacher->set($this->getOffsetCacheKey(), $this->offset, $this->offsetCacheTtl);
}
protected function getOffsetCache()
{
return $this->cacher->get($this->getOffsetCacheKey());
}
abstract protected function getItems(): void;
abstract protected function getOffsetCacheKey(): string;
}
<?php
namespace Minds\Core\Boost\Feeds;
use Minds\Common\Urn;
use Minds\Core;
use Minds\Core\Boost\Feed;
class Boost extends Feed
{
protected function getItems(): void
{
/** @var Core\Boost\Network\Iterator $iterator */
$iterator = $this->mockIterator ?: Core\Di\Di::_()->get('Boost\Network\Iterator');
$iterator
->setLimit($this->limit)
->setOffset($this->offset)
->setQuality($this->quality)
->setType($this->type)
->setHydrate(false);
if (!is_null($this->currentUser)) {
$iterator->setUserGuid($this->currentUser->getGUID());
if (!is_null($this->rating)) {
$iterator->setRating($this->currentUser->getBoostRating());
}
}
/** @var Core\Boost\Network\Boost $boost */
foreach ($iterator as $boost) {
$boostUrn = new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}");
$feedSyncEntity = new Core\Feeds\FeedSyncEntity();
$feedSyncEntity
->setGuid((string)$boost->getGuid())
->setOwnerGuid((string)$boost->getOwnerGuid())
->setTimestamp($boost->getCreatedTimestamp())
->setUrn($boostUrn);
$entity = $this->resolver->single($boostUrn);
if (!$entity) {
continue; // Duff entity?
}
$feedSyncEntity->setEntity($entity);
$this->boosts[] = $feedSyncEntity;
}
$this->offset = $iterator->getOffset();
if (isset($this->boosts[1])) { // Always offset to 2rd in list if in rotator
$len = count($this->boosts);
if ($this->boosts[$len - 1]) {
$this->offset = $this->boosts[$len - 1]->getTimestamp();
}
}
}
protected function getOffsetCacheKey(): string
{
return $this->currentUser->guid . ':boost-offset-rotator';
}
}
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces;
/**
* Channel boost handler
*/
class Channel implements Interfaces\BoostHandlerInterface
{
/**
* @param mixed $entity
* @return boolean
*/
public function validateEntity($entity)
{
return true;
}
}
<?php
namespace Minds\Core\Boost;
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
use Minds\Core;
use Minds\Core\Data;
use Minds\Entities;
use Minds\Helpers;
/**
* Content Boost handler
*/
class Content extends Network implements BoostHandlerInterface
class Content implements BoostHandlerInterface
{
protected $handler = 'content';
......@@ -18,7 +17,7 @@ class Content extends Network implements BoostHandlerInterface
* @param mixed $entity
* @return bool
*/
public static function validateEntity($entity)
public function validateEntity($entity)
{
if (!$entity || !is_object($entity)) {
return false;
......
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
/**
* A factory providing handlers boosting items
*/
class Factory
{
const HANDLER_CHANNEL = 'channel';
const HANDLER_CONTENT = 'content';
const HANDLER_NETWORK = 'network';
const HANDLER_NEWSFEED = 'newsfeed';
const HANDLER_PEER = 'peer';
const HANDLERS = [
self::HANDLER_CHANNEL => Channel::class,
self::HANDLER_CONTENT => Content::class,
self::HANDLER_NETWORK => Network::class,
self::HANDLER_NEWSFEED => Newsfeed::class,
self::HANDLER_PEER => Peer::class
];
/**
* @param string $handler
* @return BoostHandlerInterface
* @throws \Exception
*/
public static function get(string $handler): BoostHandlerInterface
{
if (!isset(self::HANDLERS[$handler]) || !class_exists(self::HANDLERS[$handler])) {
throw new \Exception("Handler not found");
}
$class = self::HANDLERS[$handler];
return new $class;
}
}
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
/**
* Newsfeed Boost handler
*/
class Network implements BoostHandlerInterface
{
/**
* @param mixed $entity
* @return boolean
*/
public function validateEntity($entity)
{
return true;
}
}
<?php
namespace Minds\Core\Boost;
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces\BoostHandlerInterface;
use Minds\Core;
use Minds\Core\Data;
use Minds\Entities;
use Minds\Helpers;
/**
* Newsfeed Boost handler
*/
class Newsfeed extends Network implements BoostHandlerInterface
class Newsfeed implements BoostHandlerInterface
{
protected $handler = 'newsfeed';
......@@ -19,7 +17,7 @@ class Newsfeed extends Network implements BoostHandlerInterface
* @param mixed $entity
* @return bool
*/
public static function validateEntity($entity)
public function validateEntity($entity)
{
if (!$entity || !is_object($entity)) {
return false;
......
<?php
namespace Minds\Core\Boost\Handler;
use Minds\Interfaces;
/**
* Peer Boost Handler
*/
class Peer implements Interfaces\BoostHandlerInterface
{
/**
* @param mixed $entity
* @return boolean
*/
public function validateEntity($entity)
{
return true;
}
}
This diff is collapsed.
......@@ -4,9 +4,7 @@
*/
namespace Minds\Core\Boost\Network;
use Minds\Common\Repository\Response;
use Minds\Core\Di\Di;
use Minds\Helpers;
use Minds\Core\Data\ElasticSearch\Prepared;
class Analytics
......@@ -56,11 +54,6 @@ class Analytics
'field' => '@reviewed',
],
],
[
'term' => [
'is_campaign' => true
],
],
],
],
],
......
......@@ -4,45 +4,76 @@
*/
namespace Minds\Core\Boost\Network;
use Minds\Entities\Entity;
use Minds\Entities\User;
use Minds\Traits\MagicAttributes;
/**
* Boost Entity
* @package Minds\Core\Boost\Network
* @method Boost setGuid(long $guid)
* @method long getGuid()
* @method string getEntityGuid()
* @method Boost setEntiyGuid()
* @method Boost setEntity()
* @method Boost setGuid(int $guid)
* @method int getGuid()
* @method Boost setEntiyGuid(int $entityGuid)
* @method int getEntityGuid()
* @method Boost setEntity($entity)
* @method Entity getEntity()
* @method Boost setBid()
* @method Boost setBidType()
* @method Booot setImpressions()
* @method Boost setBid(double $bid)
* @method double getBid()
* @method Boost setBidType(string $bidType)
* @method string getBidType()
* @method Boost setImpressions(int $impressions)
* @method int getImpressions()
* @method Boost setOwnerGuid()
* @method long getOwnerGuid()
* @method Boost setOwner()
* @method Boost setImpressionsMet(int $impressions)
* @method int getImpressionsMet()
* @method Boost setOwnerGuid(int $ownerGuid)
* @method int getOwnerGuid()
* @method Boost setOwner(User $owner)
* @method User getOwner()
*
* @method Boost setRejectedReason(int $reason)
* @method int getRejectedReason()
* @method Boost setCompletedTimestamp(int $ts)
* @method int getCreatedTimestamp()
* @method Boost setCreatedTimestamp(int $ts)
* @method int getReviewedTimestamp()
* @method Boost setReviewedTimestamp(int $ts)
* @method int getRejectedTimestamp()
* @method Boost setRejectedTimestamp(int $ts)
* @method Boost setCreatedTimestamp(int $ts)
* @method int getRevokedTimestamp()
* @method Boost setRevokedTimestamp(int $ts)
* @method int getCompletedTimestamp()
* @method Boost setCompletedTimestamp(int $ts)
* @method string getTransactionId()
* @method Boost setTransactionId(string $transactionId)
* @method string getType()
* @method Boost setType(string $value)
* @method bool getPriority()
* @method Boost setPriority(bool $priority)
* @method int getRating()
* @method Boost setRating(int $rating)
* @method array getTags()
* @method Boost setTags(array $value)
* @method string getType()
* @method array getNsfw()
* @method Boost setNsfw(array $nsfw)
* @method int getRejectedReason()
* @method Boost setRejectedReason(int $reason)
* @method string getChecksum()
* @method Boost setChecksum(string $checksum)
*/
class Boost
{
use MagicAttributes;
/** @var long $guid */
const STATE_COMPLETED = 'completed';
const STATE_REJECTED = 'rejected';
const STATE_APPROVED = 'approved';
const STATE_REVOKED = 'revoked';
const STATE_CREATED = 'created';
const TYPE_NEWSFEED = 'newsfeed';
const TYPE_CONTENT = 'content';
const RATING_SAFE = 1;
const RATING_OPEN = 2;
/** @var int $guid */
private $guid;
/** @var long $entityGuid */
/** @var int $entityGuid */
private $entityGuid;
/** @var Entity $entity */
......@@ -60,7 +91,7 @@ class Boost
/** @var int $impressionsMet */
private $impressionsMet;
/** @var long $ownerGuid */
/** @var int $ownerGuid */
private $ownerGuid;
/** @var User $owner */
......@@ -105,27 +136,24 @@ class Boost
/** @var string $checksum */
private $checksum;
/** @var string $mongoId */
private $mongoId;
/**
* Return the state
*/
public function getState()
{
if ($this->completedTimestamp) {
return 'completed';
return self::STATE_COMPLETED;
}
if ($this->rejectedTimestamp) {
return 'rejected';
return self::STATE_REJECTED;
}
if ($this->reviewedTimestamp) {
return 'approved';
return self::STATE_APPROVED;
}
if ($this->revokedTimestamp) {
return 'revoked';
return self::STATE_REVOKED;
}
return 'created';
return self::STATE_CREATED;
}
/**
......@@ -167,4 +195,29 @@ class Boost
'transaction_id' => $this->transactionId,
];
}
/* TODO - Spec Test this */
/**
* Validate the boost type string
* @param string $type
* @return bool
*/
public static function validType(string $type): bool
{
$validTypes = [
self::TYPE_CONTENT,
self::TYPE_NEWSFEED
];
return in_array($type, $validTypes, true);
}
/**
* Returns true if Boost has an entity object set
* @return bool
*/
public function hasEntity(): bool
{
return !is_null($this->entity);
}
}
......@@ -9,8 +9,9 @@ use Minds\Common\Repository\Response;
use Minds\Core\Di\Di;
use Minds\Core\Data\Cassandra\Prepared;
use Cassandra;
use Minds\Helpers\Time;
class Repository
class CassandraRepository
{
/** @var Client $client */
private $client;
......@@ -65,27 +66,13 @@ class Repository
$boost = new Boost();
$data = json_decode($row['data'], true);
if (!isset($data['schema']) && $data['schema'] != '04-2019') {
$data['entity_guid'] = $data['entity']['guid'];
$data['owner_guid'] = $data['owner']['guid'];
$data['@created'] = $data['time_created'] * 1000;
$data['@reviewed'] = $data['state'] === 'accepted' ? ($data['last_updated'] * 1000) : null;
$data['@revoked'] = $data['state'] === 'revoked' ? ($data['last_updated'] * 1000) : null;
$data['@rejected'] = $data['state'] === 'rejected' ? ($data['last_updated'] * 1000) : null;
$data['@completed'] = $data['state'] === 'completed' ? ($data['last_updated'] * 1000) : null;
}
$data = $this->updateTimestampsToMsValues($data);
if ($data['@created'] < 1055503139000) {
$data['@created'] = $data['@created'] * 1000;
}
if ($data['is_campaign'] ?? false) {
// Skip campaigns
continue;
if (!isset($data['schema']) && $data['schema'] != '04-2019') {
$data = $this->updateOldSchema($data);
}
$boost->setGuid((string) $row['guid'])
->setMongoId($data['_id'])
->setEntityGuid($data['entity_guid'])
->setOwnerGuid($data['owner_guid'])
->setType($row['type'])
......@@ -116,6 +103,41 @@ class Repository
return $response;
}
protected function updateTimestampsToMsValues(array $data): array
{
if (isset($data['last_updated'])) {
if ($data['last_updated'] < Time::HISTORIC_MS_VALUE) {
$data['last_updated'] = Time::sToMs($data['last_updated']);
}
}
if (isset($data['time_created'])) {
if ($data['time_created'] < Time::HISTORIC_MS_VALUE) {
$data['time_created'] = Time::sToMs($data['time_created']);
}
}
if ($data['@created'] < Time::HISTORIC_MS_VALUE) {
$data['@created'] = Time::sToMs($data['@created']);
}
return $data;
}
protected function updateOldSchema(array $data): array
{
$data['entity_guid'] = $data['entity']['guid'];
$data['owner_guid'] = $data['owner']['guid'];
$data['@created'] = $data['time_created'];
$data['@reviewed'] = $data['state'] === Boost::STATE_APPROVED ? $data['last_updated'] : null;
$data['@revoked'] = $data['state'] === Boost::STATE_REVOKED ? $data['last_updated'] : null;
$data['@rejected'] = $data['state'] === Boost::STATE_REJECTED ? $data['last_updated'] : null;
$data['@completed'] = $data['state'] === Boost::STATE_COMPLETED ? $data['last_updated'] : null;
$data['schema'] = '04-2019';
return $data;
}
/**
* Return a single boost via urn
* @param string $urn
......@@ -150,26 +172,22 @@ class Repository
}
$template = "INSERT INTO boosts
(type, guid, owner_guid, destination_guid, mongo_id, state, data)
(type, guid, owner_guid, state, data)
VALUES
(?, ?, ?, ?, ?, ?, ?)
(?, ?, ?, ?, ?)
";
$data = [
'guid' => $boost->getGuid(),
'schema' => '04-2019',
'_id' => $boost->getMongoId(), //TODO: remove once on production
'entity_guid' => $boost->getEntityGuid(),
'entity' => $boost->getEntity() ? $boost->getEntity()->export() : null, //TODO: remove once on production
'bid' => $boost->getBid(),
'impressions' => $boost->getImpressions(),
//'bidType' => $boost->getBidType(),
'bidType' => in_array($boost->getBidType(), [ 'onchain', 'offchain' ], true) ? 'tokens' : $boost->getBidType(), //TODO: remove once on production
'owner_guid' => $boost->getOwnerGuid(),
'owner' => $boost->getOwner() ? $boost->getOwner()->export() : null, //TODO: remove once on production
'@created' => $boost->getCreatedTimestamp(),
'time_created' => $boost->getCreatedTimestamp(), //TODO: remove once on production
'last_updated' => time(), //TODO: remove once on production
'@reviewed' => $boost->getReviewedTimestamp(),
'@rejected' => $boost->getRejectedTimestamp(),
'@revoked' => $boost->getRevokedTimestamp(),
......@@ -190,8 +208,6 @@ class Repository
(string) $boost->getType(),
new Cassandra\Varint($boost->getGuid()),
new Cassandra\Varint($boost->getOwnerGuid()),
null,
(string) $boost->getMongoId(),
(string) $boost->getState(),
json_encode($data)
];
......
......@@ -5,6 +5,7 @@
namespace Minds\Core\Boost\Network;
use Minds\Common\Repository\Response;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Data\ElasticSearch\Prepared;
use Minds\Core\Util\BigNumber;
......@@ -27,7 +28,7 @@ class ElasticRepository
public function getList($opts = [])
{
$opts = array_merge([
'rating' => 3,
'rating' => Boost::RATING_OPEN,
'token' => 0,
'offset' => null,
'order' => null,
......@@ -50,12 +51,6 @@ class ElasticRepository
],
];
$must_not[] = [
'term' => [
'is_campaign' => true,
]
];
if ($opts['offset']) {
$must[] = [
'range' => [
......@@ -82,7 +77,7 @@ class ElasticRepository
];
}
if ($opts['state'] === 'approved') {
if ($opts['state'] === Manager::OPT_STATEQUERY_APPROVED) {
$must[] = [
'exists' => [
'field' => '@reviewed',
......@@ -95,26 +90,48 @@ class ElasticRepository
],
],
];
}
if ($opts['offchain']) {
$must[] = [
'term' => [
'token_method' => 'offchain',
$must_not[] = [
'exists' => [
'field' => '@completed',
],
];
$must_not[] = [
'exists' => [
'field' => '@rejected',
],
];
$must_not[] = [
'exists' => [
'field' => '@revoked',
],
];
}
if ($opts['state'] === 'review') {
if ($opts['state'] === Manager::OPT_STATEQUERY_REVIEW) {
$must_not[] = [
'exists' => [
'field' => '@reviewed',
],
];
$must_not[] = [
'exists' => [
'field' => '@completed',
],
];
$must_not[] = [
'exists' => [
'field' => '@rejected',
],
];
$must_not[] = [
'exists' => [
'field' => '@revoked',
],
];
$sort = ['@timestamp' => 'asc'];
}
if ($opts['state'] === 'approved' || $opts['state'] === 'review' || $opts['state'] === 'active') {
if ($opts['state'] === Manager::OPT_STATEQUERY_ACTIVE) {
$must_not[] = [
'exists' => [
'field' => '@completed',
......@@ -132,6 +149,14 @@ class ElasticRepository
];
}
if ($opts['offchain']) {
$must[] = [
'term' => [
'token_method' => 'offchain',
],
];
}
$body = [
'query' => [
'bool' => [
......
<?php
namespace Minds\Core\Boost\Network;
use Minds\Core;
use Minds\Core\Data;
class Expire
{
/** @var Boost $boost */
protected $boost;
/** @var Manager $manager */
protected $manager;
public function __construct($manager = null)
{
$this->manager = $manager ?: new Manager;
}
/**
* Set the boost to expire
* @param Boost $boost
* @return void
*/
public function setBoost($boost)
{
$this->boost = $boost;
}
/**
* Expire a boost from the queue
* @return bool
*/
public function expire()
{
if (!$this->boost) {
return false;
}
if ($this->boost->getState() == 'completed') {
// Re-sync ElasticSearch
$this->manager->resync($this->boost);
// Already completed
return true;
}
$this->boost->setCompletedTimestamp(round(microtime(true) * 1000));
$this->manager->update($this->boost);
Core\Events\Dispatcher::trigger('boost:completed', 'boost', ['boost' => $this->boost]);
Core\Events\Dispatcher::trigger('notification', 'boost', [
'to' => [$this->boost->getOwnerGuid()],
'from' => 100000000000000519,
'entity' => $this->boost->getEntity(),
'notification_view' => 'boost_completed',
'params' => [
'impressions' => $this->boost->getImpressions(),
'title' => $this->boost->getEntity()->title ?: $this->boost->getEntity()->message
],
'impressions' => $this->boost->getImpressions()
]);
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -2,127 +2,21 @@
namespace Minds\Core\Boost\Network;
use Minds\Core\Boost\Repository;
use Minds\Core\Data;
use Minds\Core\Di\Di;
use Minds\Entities\Boost\Network;
use Minds\Helpers;
class Metrics
{
protected $mongo;
protected $type;
public function __construct(Data\Interfaces\ClientInterface $mongo = null)
{
$this->mongo = $mongo ?: Data\Client::build('MongoDB');
}
/**
* @param string $type
* @return $this
*/
public function setType($type)
{
$this->type = strtolower($type);
return $this;
}
/**
* Increments impressions to a given boost
* @param Network $boost
* @return int updated boost impressions count
*/
public function incrementViews($boost)
public function incrementViews($boost): int
{
//increment impression counter
Helpers\Counters::increment((string) $boost->getGuid(), "boost_impressions", 1);
//get the current impressions count for this boost
Helpers\Counters::increment(0, "boost_impressions", 1);
$count = Helpers\Counters::get((string) $boost->getGuid(), "boost_impressions", false);
if ($boost->getMongoId()) {
$count += Helpers\Counters::get((string) $boost->getMongoId(), "boost_impressions", false);
}
return $count;
}
public function getBacklogCount($userGuid = null)
{
$query = [
'state' => 'approved',
'type' => $this->type,
];
if ($userGuid) {
$match['owner_guid'] = $userGuid;
}
return (int) $this->mongo->count('boost', $query);
}
public function getPriorityBacklogCount()
{
return (int) $this->mongo->count('boost', [
'state' => 'approved',
'type' => $this->type,
'priority' => [
'$exists' => true,
'$gt' => 0
],
]);
}
public function getBacklogImpressionsSum()
{
$result = $this->mongo->aggregate('boost', [
[
'$match' => [
'state' => 'approved',
'type' => $this->type
]
],
[
'$group' => [
'_id' => null,
'total' => ['$sum' => '$impressions']
]
]
]);
return reset($result)->total ?: 0;
}
public function getAvgApprovalTime()
{
$result = $this->mongo->aggregate('boost', [
[
'$match' => [
'state' => 'approved',
'type' => $this->type,
'createdAt' => ['$ne' => null],
'approvedAt' => ['$ne' => null]
]
],
[
'$project' => [
'diff' => [
'$subtract' => ['$approvedAt', '$createdAt']
]
]
],
[
'$group' => [
'_id' => null,
'count' => ['$sum' => 1],
'diffSum' => ['$sum' => '$diff']
]
]
]);
$totals = reset($result);
return ($totals->diffSum ?: 0) / ($totals->count ?: 1);
return Helpers\Counters::get((string) $boost->getGuid(), "boost_impressions", false);
}
}
......@@ -3,16 +3,12 @@
namespace Minds\Core\Boost\Network;
use Minds\Core;
use Minds\Core\Data;
use Minds\Entities\Boost\BoostEntityInterface;
use Minds\Entities\Boost\Network;
use Minds\Helpers\MagicAttributes;
use Minds\Interfaces\BoostReviewInterface;
use Minds\Core\Boost\Delegates;
use Minds\Core\Boost\Delegates\OnchainBadgeDelegate;
use MongoDB\BSON;
class Review implements BoostReviewInterface
{
/** @var Network $boost */
......@@ -209,7 +205,7 @@ class Review implements BoostReviewInterface
{
return $this->manager->getList([
'type' => $this->type,
'state' => 'review',
'state' => Manager::OPT_STATEQUERY_REVIEW,
'limit' => $limit,
'offset' => $offset,
]);
......
<?php
namespace Minds\Core\Boost;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Data;
use Minds\Interfaces;
use Minds\Entities;
use Minds\Helpers;
use Minds\Core\Payments;
/**
* Peer Boost Handler
*/
class Peer implements Interfaces\BoostHandlerInterface
{
private $guid;
public function __construct($options)
{
if (isset($options['destination'])) {
$this->guid = $options['destination'];
}
}
/**
* Boost an entity. Not used.
* @param int|object $entity
* @param int $points
* @return null
*/
public function boost($entity, $points)
{
return null;
}
/**
* Gets a single boost entity
* @param mixed $guid
* @return object
*/
public function getBoostEntity($guid)
{
/** @var Repository $repository */
$repository = Di::_()->get('Boost\Repository');
return $repository->getEntity('peer', $guid);
}
/**
* Return a boost. Not used.
* @deprecated
* @return array
*/
public function getBoost($offset = "")
{
///
//// THIS DOES NOT APPLY BECAUSE IT'S PRE-AGREED
///
}
public function accept($entity, $impressions)
{
return false;
}
/**
* @param mixed $entity
* @return boolean
*/
public static function validateEntity($entity)
{
return true;
}
}
<?php
namespace Minds\Core\Boost\Peer;
use Minds\Core\Boost\Repository;
use Minds\Core\Data;
use Minds\Core\Di\Di;
class Metrics
{
protected $mongo;
public function __construct(Data\Interfaces\ClientInterface $mongo = null)
{
$this->mongo = $mongo ?: Data\Client::build('MongoDB');
}
}
......@@ -3,23 +3,15 @@
namespace Minds\Core\Boost\Peer;
use Minds\Core;
use Minds\Core\Data;
use Minds\Entities;
use Minds\Helpers\MagicAttributes;
use Minds\Interfaces\BoostReviewInterface;
class Review implements BoostReviewInterface
{
/** @var Entities\Boost\Peer $boost */
protected $boost;
protected $mongo;
protected $type;
public function __construct(Data\Interfaces\ClientInterface $mongo = null)
{
$this->mongo = $mongo ?: Data\Client::build('MongoDB');
}
/**
* @param string $type
* @return $this
......@@ -97,26 +89,6 @@ class Review implements BoostReviewInterface
->save();
}
protected function enableBoostRejectionReasonFlag($entity = null, $reason = -1)
{
if (!$entity || !is_object($entity)) {
return false;
}
$dirty = false;
// Main boost rejection reason flag
if (MagicAttributes::setterExists($entity, 'setBoostRejectionReason')) {
$entity->setBoostRejectionReason($reason);
$dirty = true;
} elseif (property_exists($entity, 'boost_rejection_reason')) {
$entity->boost_rejection_reason = true;
$dirty = true;
}
return $dirty;
}
/**
* Gets a single boost entity
* @param mixed $guid
......
......@@ -209,43 +209,6 @@ class Repository
return $boost;
}
/**
* @param string $type
* @param string $mongo_id
* @return Entities\Boost\BoostEntityInterface|false
*/
public function getEntityById($type, $mongo_id)
{
if (!$type || !$mongo_id) {
return false;
}
$template = "SELECT * FROM boosts_by_mongo_id WHERE type = ? AND mongo_id = ? LIMIT ?";
$values = [
(string) $type,
(string) $mongo_id,
1
];
$query = new Prepared\Custom();
$query->query($template, $values);
$boost = false;
try {
$result = $this->db->request($query);
if (isset($result[0]) && $result[0]) {
$boost = (new Entities\Boost\Factory())->build($result[0]['type']);
$boost->loadFromArray($result[0]['data']);
}
} catch (\Exception $e) {
// TODO: Log or warning
}
return $boost;
}
/**
* Insert or update a boost
* @param string $type
......@@ -271,7 +234,7 @@ class Repository
throw new \Exception('State is required');
}
$template = "INSERT INTO boosts (type, guid, owner_guid, destination_guid, mongo_id, state, data) VALUES (?, ?, ?, ?, ?, ?, ?)";
$template = "INSERT INTO boosts (type, guid, owner_guid, destination_guid, state, data) VALUES (?, ?, ?, ?, ?, ?)";
$destination = null;
......@@ -284,7 +247,6 @@ class Repository
new Cassandra\Varint($data['guid']),
new Cassandra\Varint($data['owner']['guid']),
$destination,
(string) $data['_id'],
(string) $data['state'],
json_encode($data)
];
......
......@@ -34,17 +34,4 @@ interface BoostEntityInterface
* @return string
*/
public function getState();
/**
* Set the rating of the boost
* @param string $rating
* @return $this
*/
//public function setRating($rating);
/**
* Return rating of the boost
* @return int
*/
//public function getRating();
}
......@@ -17,7 +17,6 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
public $type = 'boost';
public $subtype = 'network';
protected $_id; //specific to mongo related
protected $entity;
protected $bid;
protected $impressions;
......@@ -36,7 +35,7 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
protected $checksum = null;
protected $exportableDefaults = [
'guid', '_id', 'entity', 'bid', 'bidType', 'destination', 'owner', 'state',
'guid', 'entity', 'bid', 'bidType', 'destination', 'owner', 'state',
'transactionId', 'time_created', 'last_updated', 'type', 'subtype', 'handler',
'rating', 'quality', 'impressions', 'categories', 'rejection_reason', 'checksum'
];
......@@ -66,7 +65,6 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
$array = is_array($array) ? $array : json_decode($array, true);
$this->guid = $array['guid'];
$this->_id = $array['_id'];
$this->entity = Entities\Factory::build($array['entity']);
$this->bid = $array['bid'];
$this->bidType = $array['bidType'];
......@@ -99,7 +97,6 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
$data = [
'guid' => $this->guid,
'_id' => $this->_id,
'entity' => $this->entity->export(),
'bid' => $this->bid,
'impressions' => $this->impressions,
......@@ -150,26 +147,6 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
return $this->guid;
}
/**
* Set the internal data id
* @param string $_id
* @return $this
*/
public function setId($_id)
{
$this->_id = $_id;
return $this;
}
/**
* Get the internal $id
* @return string
*/
public function getId()
{
return $this->_id;
}
/**
* Set the entity to boost
* @param Entity $entity
......@@ -447,8 +424,7 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
$export = array_merge($export, \Minds\Core\Events\Dispatcher::trigger('export:extender', 'all', ['entity' => $this], []));
$export = \Minds\Helpers\Export::sanitize($export);
$export['met_impressions'] = Counters::get((string) $this->getId(), "boost_impressions")
+ Counters::get($this->getGuid(), "boost_impressions");
$export['met_impressions'] = Counters::get($this->getGuid(), "boost_impressions");
return $export;
}
}
<?php
namespace Minds\Helpers;
class Time
{
const HISTORIC_MS_VALUE = 1000000000000;
const HALF_HOUR = 1800;
const ONE_HOUR = 3600;
const TWO_HOUR = 7200;
const ONE_DAY = 86400;
const ONE_DAY_MS = 86400000;
const ONE_MIN = 60;
const FIVE_MIN = 300;
const TEN_MIN = 600;
const FIFTEEN_MIN = 900;
/**
* Return the interval timestamp a timestamp is within
* @param int $ts
* @param int $interval
* @return int
*/
public static function toInterval(int $ts, int $interval)
{
return $ts - ($ts % $interval);
}
/**
* Return an array of interval values between two timestamps
* @param int $start
* @param int $end
* @param int $interval
* @return array
*/
public static function intervalsBetween(int $start, int $end, int $interval): array
{
$startTs = self::toInterval($start, $interval);
$endTs = self::toInterval($end, $interval);
/* Exclusive not inclusive range should ignore first interval value */
if ($startTs < $endTs) {
$startTs += $interval;
}
$intervals = [];
while ($startTs <= $endTs) {
$intervals[] = $startTs;
$startTs += $interval;
}
return $intervals;
}
/**
* Descriptive method explicitly converts s value to ms value
* @param int $s
* @return int
*/
public static function sToMs(int $s) : int
{
return $s * 1000;
}
}
......@@ -6,33 +6,9 @@ namespace Minds\Interfaces;
*/
interface BoostHandlerInterface
{
/**
* Boost an entity, place in a review queue first
* @param object|int $entity - the entity to boost
* @param int $impressions
* @return bool
*/
public function boost($entity, $impressions);
/**
* Accept a boost
* @param object|int $entity
* @param int $impressions
* @return bool
*/
public function accept($entity, $impressions);
/**
* Return a boost
* @return array
*/
public function getBoost();
/**
* @param mixed $entity
* @return boolean
*/
public static function validateEntity($entity);
public function validateEntity($entity);
}
<?php
namespace Spec\Minds\Core\Boost\Feeds;
use Minds\Common\Urn;
use Minds\Core\Boost\Feeds\Boost;
use Minds\Core\Boost\Network\Iterator;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Entities\Resolver;
use Minds\Entities\Entity;
use Minds\Entities\User;
use Minds\Helpers\Time;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\Core\Boost\Network\Boost as BoostObj;
class BoostSpec extends ObjectBehavior
{
protected $user;
protected $resolver;
protected $cacher;
protected $iterator;
public function let(User $user, Resolver $resolver, abstractCacher $cacher, Iterator $iterator)
{
$this->beConstructedWith($user, $resolver, $cacher);
$this->user = $user;
$this->resolver = $resolver;
$this->cacher = $cacher;
$this->iterator = $iterator;
$this->setMockIterator($iterator);
}
public function it_is_initializable()
{
$this->shouldHaveType(Boost::class);
}
public function it_should_get_boosts(BoostObj $boost1, BoostObj $boost2)
{
$params = [
'limit' => 10,
'offset' => 10,
'rating' => 0,
'quality' => 0
];
$this->user->getGUID()->shouldBeCalled()->willReturn(1234);
$this->user->get('guid')->shouldBeCalled()->willReturn(1234);
$this->user->getTimeCreated()->shouldBeCalled()->willReturn(time() - Time::TWO_HOUR);
$this->user->getBoostRating()->shouldBeCalled()->willReturn(0);
$this->iterator->setLimit(10)->shouldBeCalled()->willReturn($this->iterator);
$this->iterator->setOffset(10)->shouldBeCalled()->willReturn($this->iterator);
$this->iterator->setRating(0)->shouldBeCalled()->willReturn($this->iterator);
$this->iterator->setQuality(0)->shouldBeCalled()->willReturn($this->iterator);
$this->iterator->setType('newsfeed')->shouldBeCalled()->willReturn($this->iterator);
$this->iterator->setHydrate(false)->shouldBeCalled()->willReturn($this->iterator);
$this->iterator->setUserGuid(1234)->shouldBeCalled()->willReturn($this->iterator);
$this->iterator->rewind()->shouldBeCalled();
$this->iterator->valid()->shouldBeCalled()->willReturn(true, true, false);
$this->iterator->current()->shouldBeCalled()->willReturn($boost1, $boost2);
$this->iterator->next()->shouldBeCalled();
$this->iterator->getOffset()->shouldBeCalled()->willReturn($params['offset']);
$boost1->getCreatedTimestamp()->shouldBeCalled()->willReturn(1571749729);
$boost1->getGuid()->shouldBeCalled()->willReturn(1234);
$boost1->getOwnerGuid()->shouldBeCalled()->willReturn(5678);
$boost1->getType()->shouldBeCalled()->willReturn('newsfeed');
$this->resolver->single(Argument::type(Urn::class))->shouldBeCalled()->willReturn(new Entity());
$boost2->getCreatedTimestamp()->shouldBeCalled()->willReturn(1571750627);
$boost2->getGuid()->shouldBeCalled()->willReturn(3456);
$boost2->getOwnerGuid()->shouldBeCalled()->willReturn(5678);
$boost2->getType()->shouldBeCalled()->willReturn('newsfeed');
$this->setLimit($params['limit'])->shouldReturn($this);
$this->setOffset($params['offset'])->shouldReturn($this);
$this->setRating(0)->shouldReturn($this);
$this->get()->shouldBeArray();
}
}
<?php
namespace Spec\Minds\Core\Boost;
namespace Spec\Minds\Core\Boost\Handler;
use Minds\Core\Boost\Handler\Factory;
use Minds\Core\Boost\Handler\Newsfeed;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\Core\Data\MongoDB\Client as MongoClient;
class FactorySpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Boost\Factory');
$this->shouldHaveType(Factory::class);
}
public function it_should_build_a_handler(MongoClient $db)
public function it_should_return_a_handler()
{
$this::build("Newsfeed", Argument::any(), $db)->shouldHaveType('Minds\Core\Boost\Newsfeed');
$this::get(Factory::HANDLER_NEWSFEED)->shouldHaveType(Newsfeed::class);
}
public function it_should_throw_an_error_if_handler_doesnt_exist()
{
$this->shouldThrow("\Exception")->during("build", ["FakeBoost"]);
$this->shouldThrow("\Exception")->during("get", ["FakeBoost"]);
}
}
<?php
namespace Spec\Minds\Core\Boost\Handler;
use Minds\Core\Blogs\Blog;
use Minds\Core\Boost\Handler\Newsfeed;
use Minds\Entities\Activity;
use Minds\Entities\Image;
use Minds\Entities\User;
use Minds\Entities\Video;
use PhpSpec\ObjectBehavior;
class NewsfeedSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(Newsfeed::class);
}
public function it_should_validate_entity(
Activity $activity,
Video $video,
Image $image,
Blog $blog,
User $user
) {
$this->validateEntity($activity)->shouldReturn(true);
$this->validateEntity($video)->shouldReturn(true);
$this->validateEntity($image)->shouldReturn(true);
$this->validateEntity($blog)->shouldReturn(true);
$this->validateEntity($user)->shouldReturn(false);
}
}
......@@ -2,14 +2,14 @@
namespace Spec\Minds\Core\Boost\Network;
use Minds\Core\Boost\Network\Repository;
use Minds\Core\Boost\Network\CassandraRepository;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
class CassandraRepositorySpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
$this->shouldHaveType(CassandraRepository::class);
}
}
<?php
namespace Spec\Minds\Core\Boost\Network;
use Minds\Core\Data\MongoDB;
use Minds\Entities\Boost\Network;
use Minds\Core\Boost\Network\Manager;
use Minds\Core\Boost\Network\Boost;
use Minds\Entities\Entity;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ExpireSpec extends ObjectBehavior
{
private $manager;
public function let(Manager $manager)
{
$this->beConstructedWith($manager);
$this->manager = $manager;
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Boost\Network\Expire');
}
public function it_should_expire_a_boost(Boost $boost)
{
$this->manager->update($boost)
->shouldBeCalled();
$boost->getState()
->shouldBeCalled()
->willReturn('created');
$boost->setCompletedTimestamp(Argument::approximate(time() * 1000, -5))
->shouldBeCalled()
->willReturn($boost);
$boost->getOwnerGuid()
->shouldBeCalled()
->willReturn(123);
$boost->getEntity()
->shouldBeCalled()
->willReturn((new Entity));
$boost->getImpressions()
->shouldBeCalled();
$this->setBoost($boost);
$this->expire();
}
}
<?php
namespace Spec\Minds\Core\Boost\Network;
use Minds\Common\Repository\Response;
use Minds\Core\Boost\Network\Boost;
use Minds\Core\Boost\Network\ElasticRepository;
use Minds\Core\Boost\Network\Iterator;
use Minds\Core\Boost\Network\Manager;
use Minds\Core\EntitiesBuilder;
use PhpSpec\ObjectBehavior;
class IteratorSpec extends ObjectBehavior
{
/** @var ElasticRepository */
protected $elasticRepository;
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Manager */
protected $manager;
public function let(ElasticRepository $elasticRepository, EntitiesBuilder $entitiesBuilder, Manager $manager)
{
$this->beConstructedWith($elasticRepository, $entitiesBuilder, $manager);
$this->elasticRepository = $elasticRepository;
$this->entitiesBuilder = $entitiesBuilder;
$this->manager = $manager;
}
public function it_is_initializable()
{
$this->shouldHaveType(Iterator::class);
}
public function it_should_enforce_max_limit()
{
$this->setLimit(Iterator::MAX_LIMIT + 10);
$this->getLimit()->shouldBe(Iterator::MAX_LIMIT);
}
public function it_should_only_set_valid_type()
{
// Default
$this->getType()->shouldBe(Boost::TYPE_NEWSFEED);
$this->setType(Boost::TYPE_CONTENT);
$this->getType()->shouldBe(Boost::TYPE_CONTENT);
$this->setType('UnknownType');
$this->getType()->shouldBe(Boost::TYPE_CONTENT);
}
public function it_should_fetch_number_of_boosts_requested()
{
$this->setLimit(3);
$this->setHydrate(false);
$boost1 = new Boost();
$boost1->setGuid(1234);
$boost1->setCreatedTimestamp(111111);
$boost2 = new Boost();
$boost2->setGuid(3456);
$boost2->setCreatedTimestamp(222222);
$response1 = new Response();
$response1[] = $boost1;
$response1[] = $boost2;
$response1->setPagingToken(123456789);
$boost3 = new Boost();
$boost3->setGuid(5678);
$boost3->setCreatedTimestamp(333333);
$boost4 = new Boost();
$boost4->setGuid(7890);
$boost4->setCreatedTimestamp(444444);
$response2 = new Response();
$response2[] = $boost3;
$response2[] = $boost4;
$response2->setPagingToken(0);
$opts1 = [
'type' => 'newsfeed',
'limit' => 3,
'offset' => 0,
'state' => 'approved',
'rating' => 1
];
$opts2 = [
'type' => 'newsfeed',
'limit' => 3,
'offset' => 222222,
'state' => 'approved',
'rating' => 1
];
$this->elasticRepository->getList($opts1)->shouldBeCalled()->willReturn($response1);
$this->elasticRepository->getList($opts2)->shouldBeCalled()->willReturn($response2);
$this->rewind(); // Simulate start of a foreach
$this->count()->shouldBe(3); // Even though repo returned 4
$this->getOffset()->shouldBe(333333); // Timestamp of last in set
}
// TODO: Add a test for blocked boost in set
}
This diff is collapsed.
......@@ -2,56 +2,13 @@
namespace Spec\Minds\Core\Boost\Network;
use Minds\Core\Boost\Repository;
use Minds\Core\Data\MongoDB;
use Minds\Core\Di\Di;
use Minds\Core\Boost\Network\Metrics;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class MetricsSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Boost\Network\Metrics');
}
public function it_should_get_backlog_count(MongoDB\Client $mongo)
{
$mongo->count(Argument::containingString('boost'), Argument::any())
->shouldBeCalled()
->willReturn(3);
$this->beConstructedWith($mongo);
$this->getBacklogCount('newsfeed', '123')->shouldReturn(3);
}
public function it_should_get_priority_backlog_count(MongoDB\Client $mongo)
{
$mongo->count(Argument::containingString('boost'), Argument::any())
->shouldBeCalled()
->willReturn(3);
$this->beConstructedWith($mongo);
$this->getPriorityBacklogCount('newsfeed', '123')->shouldReturn(3);
}
public function it_should_get_backlog_impressions_sum(MongoDB\Client $mongo)
{
$total = new \stdClass();
$total->total = 10;
$result = ['total' => $total];
$mongo->aggregate(Argument::containingString('boost'), Argument::any())
->shouldBeCalled()
->willReturn($result);
$this->beConstructedWith($mongo);
$this->getBacklogImpressionsSum('newsfeed')->shouldReturn(10);
$this->shouldHaveType(Metrics::class);
}
}
<?php
namespace Spec\Minds\Core\Boost;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Prophecy\Prophet;
use Minds\Entities\User;
use Minds\Entities\Boost\Network;
use Minds\Core\Data\Call;
use Minds\Core\Data\MongoDB\Client;
use Minds\Core\Data\Interfaces\ClientInterface;
class NewsfeedSpec extends ObjectBehavior
{
public function let(Client $mongo, Call $db, User $user)
{
$mongo->insert(Argument::type('string'), Argument::type('array'))
->willReturn("boost_id");
//$db->getRow(Argument::type(''))->will
$this->beConstructedWith([], $mongo, $db);
}
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Boost\Newsfeed');
}
public function it_can_boost_a_post(Network $boost, User $user, Client $mongo, Call $db)
{
$boost->getGuid()->willReturn('foo');
$boost->getImpressions()->willReturn(10);
$boost->getOwner()->willReturn($user);
$boost->getRating()->willReturn(1);
$boost->getQuality()->willReturn(75);
$boost->getImpressions()->willReturn(100);
$boost->getPriorityRate()->willReturn(0);
$boost->getCategories()->willReturn(['art', 'music']);
$this->boost($boost)->shouldBeString();
}
}
......@@ -245,54 +245,6 @@ class RepositorySpec extends ObjectBehavior
->shouldReturn(false);
}
// getEntityById()
public function it_should_get_a_single_boost_based_on_mongo()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn([
[ 'type' => 'network', 'data' => [ ] ],
]);
$this
->getEntityById('network', 'm2000')
->shouldReturnAnInstanceOf(Entities\Boost\Network::class);
}
public function it_should_not_get_a_single_boost_based_on_mongo_if_no_type()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->getEntityById(null, 'm2000')
->shouldReturn(false);
}
public function it_should_not_get_a_single_boost_based_on_mongo_if_no_id()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldNotBeCalled();
$this
->getEntityById('network', null)
->shouldReturn(false);
}
public function it_should_not_get_a_single_boost_based_on_mongo_if_not_exists()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
->shouldBeCalled()
->willReturn([]);
$this
->getEntityById('network', 'm2404')
->shouldReturn(false);
}
// upsert()
public function it_should_store_a_boost()
{
$this->_client->request(Argument::type(Prepared\Custom::class))
......
Please register or to comment