Commit 58c6b07b authored by Guy Thouret's avatar Guy Thouret

Refactor Boost Code - #1149

1 merge request!410WIP: Refactor Boost Code
Pipeline #101318088 passed with stages
in 6 minutes and 58 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,52 +22,56 @@ 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;
$rating = isset($_GET['rating']) ? (int) $_GET['rating'] : $user->getBoostRating();
$platform = isset($_GET['platform']) ? $_GET['platform'] : 'other';
$offset = !empty($_GET['offset']) ? (int) $_GET['offset'] : null;
$quality = 0;
// 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 (!is_null($offset)) {
$iterator->setOffset($offset);
}
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 +85,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 +95,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 +107,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) {
......
......@@ -23,9 +23,8 @@ class views implements Interfaces\Api
switch ($pages[0]) {
case 'boost':
$expire = Di::_()->get('Boost\Network\Expire');
$metrics = Di::_()->get('Boost\Network\Metrics');
$manager = Di::_()->get('Boost\Network\Manager');
$metrics = new Core\Boost\Network\Metrics();
$manager = new Core\Boost\Network\Manager();
$urn = "urn:boost:newsfeed:{$pages[1]}";
......@@ -40,8 +39,7 @@ class views implements Interfaces\Api
$count = $metrics->incrementViews($boost);
if ($count > $boost->getImpressions()) {
$expire->setBoost($boost);
$expire->expire();
$manager->expire($boost);
}
Counters::increment($boost->getEntity()->guid, "impression");
......@@ -71,10 +69,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 +122,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;
/**
* Class Feed
* @package Minds\Core\Boost
*/
abstract class Feed
{
/** @var Resolver */
protected $resolver;
/** @var User */
protected $currentUser;
/** @var abstractCacher */
protected $cacher;
protected $mockIterator;
/** @var array */
protected $boosts = [];
/** @var int */
protected $offset;
/** @var int */
protected $limit;
/** @var int */
protected $rating;
/** @var string */
protected $platform;
/** @var int */
protected $quality = 0;
/** @var string */
protected $type = 'newsfeed';
/** @var int */
protected $offsetCacheTtl = Time::FIVE_MIN;
/**
* Feed constructor.
* @param User|null $currentUser
* @param Resolver|null $resolver
* @param abstractCacher|null $cacher
*/
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');
}
/**
* Set a mock iterator
* @param $mockIterator
* @return $this
*/
public function setMockIterator($mockIterator): self
{
$this->mockIterator = $mockIterator;
return $this;
}
/**
* Set limit
* @param int $limit
* @return $this
*/
public function setLimit(int $limit): self
{
$this->limit = $limit;
return $this;
}
/**
* Get offset
* @return int
*/
public function getOffset(): int
{
return $this->offset;
}
/**
* Set offset
* @param int $offset
* @return $this
*/
public function setOffset(int $offset): self
{
$this->offset = $offset;
return $this;
}
/**
* Set rating
* @param int $rating
* @return $this
*/
public function setRating(int $rating): self
{
$this->rating = $rating;
return $this;
}
/**
* Set platform
* @param string $platform
* @return $this
*/
public function setPlatform(string $platform): self
{
$this->platform = $platform;
return $this;
}
/**
* Set quality
* @param int $quality
* @return $this
*/
public function setQuality(int $quality): self
{
$this->quality = $quality;
return $this;
}
/**
* Set type
* @param string $type
* @return $this
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Get the feed items
* @return Core\Feeds\FeedSyncEntity[]
*/
public function get(): array
{
$this->makeRatingAndQualitySafe();
$this->setOffsetFromCache();
$this->getItems();
$this->setOffsetCache();
return $this->boosts;
}
/**
* Make rating and quality safe
*/
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;
}
}
/**
* Set the offset from cache
*/
protected function setOffsetFromCache(): void
{
$offsetCache = $this->getOffsetCache();
if (is_int($offsetCache)) {
$this->offset = $offsetCache;
}
}
/**
* Set the offset cache
*/
protected function setOffsetCache(): void
{
$this->cacher->set($this->getOffsetCacheKey(), $this->offset, $this->offsetCacheTtl);
}
/**
* Get the offset cache
* @return mixed
*/
protected function getOffsetCache()
{
return $this->cacher->get($this->getOffsetCacheKey());
}
/**
* Get items
*/
abstract protected function getItems(): void;
/**
* Get offset cache key
* @return string
*/
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 setEntityGuid(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;
}
if ($data['@created'] < 1055503139000) {
$data['@created'] = $data['@created'] * 1000;
}
$data = $this->updateTimestampsToMsValues($data);
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'])
......@@ -102,7 +89,7 @@ class Repository
->setRating($data['rating'])
->setTags($data['tags'])
->setNsfw($data['nsfw'])
->setRejectReason($data['rejection_reason'])
->setRejectedReason($data['rejection_reason'])
->setChecksum($data['checksum']);
$response[] = $boost;
......@@ -116,6 +103,51 @@ class Repository
return $response;
}
/**
* Update any s timestamps to ms values
* @param array $data
* @return array
*/
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;
}
/**
* Update the old schema
* @param array $data
* @return array
*/
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 +182,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(),
......@@ -178,11 +206,11 @@ class Repository
'type' => $boost->getType(),
'handler' => $boost->getType(), //TODO: remove once on production
'state' => $boost->getState(), //TODO: remove once on production
'priority' => $boost->isPriority(),
'priority' => $boost->getPriority(),
'rating' => $boost->getRating(),
'tags' => $boost->getTags(),
'nsfw' => $boost->getNsfw(),
'rejection_reason'=> $boost->getRejectReason(),
'rejection_reason'=> $boost->getRejectedReason(),
'checksum' => $boost->getChecksum(),
];
......@@ -190,8 +218,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,20 @@
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
* @param Boost $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,
]);
......
......@@ -8,6 +8,7 @@ use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Payments;
use Minds\Core\Util\BigNumber;
use Minds\Entities\Boost\Network;
use Minds\Entities\Boost\Peer;
use Minds\Entities\User;
use Minds\Core\Data\Locks\LockFailedException;
......@@ -49,7 +50,7 @@ class Payment
}
/**
* @param Network|Peer $boost
* @param Core\Boost\Network\Boost $boost
* @param $payload
* @return null
* @throws \Exception
......@@ -62,7 +63,7 @@ class Payment
switch ($currency) {
case 'usd':
case 'money':
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
throw new \Exception('Money P2P boosts are not supported');
}
......@@ -99,7 +100,7 @@ class Payment
case 'tokens':
switch ($payload['method']) {
case 'offchain':
if ($boost->getHandler() === 'peer' && !$boost->getDestination()->getPhoneNumberHash()) {
if ($this->isPeerBoost($boost) && !$boost->getDestination()->getPhoneNumberHash()) {
throw new \Exception('Boost target should participate in the Rewards program.');
}
......@@ -115,10 +116,10 @@ class Payment
$txData = [
'amount' => (string) $boost->getBid(),
'guid' => (string) $boost->getGuid(),
'handler' => (string) $boost->getHandler(),
'handler' => $this->boostType($boost),
];
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
$txData['sender_guid'] = (string) $boost->getOwner()->guid;
$txData['receiver_guid'] = (string) $boost->getDestination()->guid;
}
......@@ -135,7 +136,7 @@ class Payment
return $tx->getTx();
case 'creditcard':
if ($boost->getHandler() === 'peer' && !$boost->getDestination()->getPhoneNumberHash()) {
if ($this->isPeerBoost($boost) && !$boost->getDestination()->getPhoneNumberHash()) {
throw new \Exception('Boost target should participate in the Rewards program.');
}
......@@ -178,10 +179,10 @@ class Payment
$txData = [
'amount' => (string) $boost->getBid(),
'guid' => (string) $boost->getGuid(),
'handler' => (string) $boost->getHandler()
'handler' => $this->boostType($boost)
];
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
$txData['sender_guid'] = (string) $boost->getOwner()->guid;
$txData['receiver_guid'] = (string) $boost->getDestination()->guid;
}
......@@ -201,17 +202,17 @@ class Payment
return $tx;
case 'onchain':
if ($boost->getHandler() === 'peer' && !$boost->getDestination()->getEthWallet()) {
if ($this->isPeerBoost($boost) && !$boost->getDestination()->getEthWallet()) {
throw new \Exception('Boost target should participate in the Rewards program.');
}
$txData = [
'amount' => (string) $boost->getBid(),
'guid' => (string) $boost->getGuid(),
'handler' => (string) $boost->getHandler()
'handler' => $this->boostType($boost)
];
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
$txData['sender_guid'] = (string) $boost->getOwner()->guid;
$txData['receiver_guid'] = (string) $boost->getDestination()->guid;
}
......@@ -228,7 +229,7 @@ class Payment
->setData($txData);
$this->txManager->add($sendersTx);
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
$receiversTx = new Core\Blockchain\Transactions\Transaction();
$receiversTx
->setUserGuid($boost->getDestination()->guid)
......@@ -241,7 +242,7 @@ class Payment
->setData([
'amount' => (string) $boost->getBid(),
'guid' => (string) $boost->getGuid(),
'handler' => (string) $boost->getHandler(),
'handler' => $this->boostType($boost),
'sender_guid' => (string) $boost->getOwner()->guid,
'receiver_guid' => (string) $boost->getDestination()->guid,
]);
......@@ -292,7 +293,7 @@ class Payment
return false;
break;
case 'offchain':
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
/** @var Core\Blockchain\Wallets\OffChain\Transactions $receiversTx */
$receiversTx = Di::_()->get('Blockchain\Wallets\OffChain\Transactions');
$receiversTx
......@@ -357,7 +358,7 @@ class Payment
switch ($method) {
case 'onchain':
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
// Already refunded
return true;
}
......@@ -387,7 +388,7 @@ class Payment
->setData([
'amount' => (string) $boost->getBid(),
'guid' => (string) $boost->getGuid(),
'handler' => (string) $boost->getHandler(),
'handler' => $this->boostType($boost),
'refund' => true,
]);
......@@ -410,7 +411,7 @@ class Payment
'guid' => (string) $boost->getGuid(),
];
if ($boost->getHandler() === 'peer') {
if ($this->isPeerBoost($boost)) {
$txData['sender_guid'] = (string) $boost->getOwner()->guid;
$txData['receiver_guid'] = (string) $boost->getDestination()->guid;
}
......@@ -440,4 +441,25 @@ class Payment
throw new \Exception('Payment Method not supported');
}
protected function isPeerBoost($boost): bool
{
return (is_object($boost) && $boost instanceof Peer);
}
protected function isNetworkBoost($boost): bool
{
return (is_object($boost) && $boost instanceof Network);
}
protected function boostType($boost): string
{
if ($this->isPeerBoost($boost)) {
return 'peer';
} elseif ($this->isNetworkBoost($boost)) {
return 'network';
} else {
return $boost->getType();
}
}
}
<?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;
......@@ -34,9 +33,10 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
protected $categories = [];
protected $rejection_reason = -1;
protected $checksum = null;
protected $schemaVersion = 2;
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 +66,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'];
......@@ -83,6 +82,11 @@ class Network extends Entities\DenormalizedEntity implements BoostEntityInterfac
$this->categories = $array['categories'];
$this->rejection_reason = $array['rejection_reason'];
$this->checksum = $array['checksum'];
if (isset($array['_id'])) {
$this->schemaVersion = 1;
}
return $this;
}
......@@ -99,7 +103,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 +153,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 +430,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'] = ($this->schemaVersion < 2) ? 0 : 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();
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Please register or to comment