...
 
Commits (25)
<?php
namespace Minds\Controllers\Cli;
use Minds\Core\Minds;
use Minds\Cli;
use Minds\Core\Feeds\Suggested\Manager;
use Minds\Interfaces;
class Suggested extends Cli\Controller implements Interfaces\CliControllerInterface
{
/** @var Manager */
private $manager;
public function __construct()
{
$minds = new Minds();
$minds->start();
$this->manager = new Manager();
}
public function help($command = null)
{
$this->out('Syntax usage: cli trending <type>');
}
public function exec()
{
$this->out('Syntax usage: cli trending <type>');
}
public function sync_all()
{
error_reporting(E_ALL);
ini_set('display_errors', 1);
$this->out('Collecting trending items');
$this->manager->setFrom($this->getOpt('from') ?: strtotime('-12 hours') * 1000);
$this->manager->setTo($this->getOpt('to') ?: time() * 1000);
$this->manager->run('all');
$this->out('Completed syncing all');
}
public function sync_newsfeed()
{
error_reporting(E_ALL);
ini_set('display_errors', 1);
$this->out('Syncing newsfeed');
$this->manager->setFrom($this->getOpt('from') ?: strtotime('-12 hours') * 1000);
$this->manager->setTo($this->getOpt('to') ?: time() * 1000);
$this->manager->run('newsfeed');
$this->out('Completed syncing newsfeed');
}
public function sync_images()
{
$this->out('Syncing images');
$this->manager->setFrom($this->getOpt('from') ?: strtotime('-12 hours') * 1000);
$this->manager->setTo($this->getOpt('to') ?: time() * 1000);
$this->manager->run('images');
$this->out('Completed syncing images');
}
public function sync_videos()
{
$this->out('Syncing videos');
$this->manager->setFrom($this->getOpt('from') ?: strtotime('-12 hours') * 1000);
$this->manager->setTo($this->getOpt('to') ?: time() * 1000);
$this->manager->run('videos');
$this->out('Completed syncing videos');
}
public function sync_groups()
{
$this->out('Syncing groups');
$this->manager->setFrom($this->getOpt('from') ?: strtotime('-12 hours') * 1000);
$this->manager->setTo($this->getOpt('to') ?: time() * 1000);
$this->manager->run('groups');
$this->out('Completed syncing groups');
}
public function sync_blogs()
{
$this->out('Syncing blogs');
$this->manager->setFrom($this->getOpt('from') ?: strtotime('-12 hours') * 1000);
$this->manager->setTo($this->getOpt('to') ?: time() * 1000);
$this->manager->run('blogs');
$this->out('Completed syncing blogs');
}
}
......@@ -178,32 +178,8 @@ class fetch implements Interfaces\Api
private function getSuggestedPosts($opts = [])
{
$opts = array_merge([
'offset' => 0,
'limit' => 12,
'rating' => 1,
], $opts);
/** @var Core\Feeds\Suggested\Manager $repo */
$repo = Di::_()->get('Feeds\Suggested\Manager');
$opts = [
'user_guid' => Core\Session::getLoggedInUserGuid(),
'rating' => $opts['rating'],
'limit' => $opts['limit'],
'offset' => $opts['offset'],
'type' => 'newsfeed',
'all' => true,
];
$result = $repo->getFeed($opts);
// Remove all unlisted content if it appears
$result = array_values(array_filter($result, function($entity) {
return $entity->getAccessId() != 0;
}));
return $result;
// @deprecated
return [];
}
}
......@@ -99,7 +99,14 @@ class comments implements Interfaces\Api
switch ($pages[0]) {
case "update":
$comment = $manager->getByLuid($pages[1]);
if (!$comment || !$comment->canEdit()) {
$canEdit = $comment->canEdit();
if ($canEdit && $comment->getOwnerGuid() != Core\Session::getLoggedInUserGuid()) {
$canEdit = false;
}
if (!$comment || !$canEdit) {
$response = array('status' => 'error', 'message' => 'This comment can not be edited');
break;
}
......
......@@ -394,6 +394,7 @@ class newsfeed implements Interfaces\Api
'mature' => $embeded instanceof Flaggable ? $embeded->getFlag('mature') : false,
'width' => $embeded->width,
'height' => $embeded->height,
'gif' => (bool) $embeded->gif ?? false,
]])
->setMature($embeded instanceof Flaggable ? $embeded->getFlag('mature') : false)
->setFromEntity($embeded)
......@@ -410,6 +411,7 @@ class newsfeed implements Interfaces\Api
'mature' => $embeded instanceof Flaggable ? $embeded->getFlag('mature') : false,
'width' => $embeded->width,
'height' => $embeded->height,
'gif' => (bool) $embeded->gif ?? false,
]])
->setMature($embeded instanceof Flaggable ? $embeded->getFlag('mature') : false)
->setFromEntity($embeded)
......@@ -606,6 +608,7 @@ class newsfeed implements Interfaces\Api
'mature' => $attachment instanceof Flaggable ? $attachment->getFlag('mature') : false,
'width' => $attachment->width,
'height' => $attachment->height,
'gif' => (bool) $attachment->gif ?? false,
]])
->setFromEntity($attachment)
->setTitle($attachment->message);
......
......@@ -47,7 +47,6 @@ class notifications implements Interfaces\Api
if (!isset($pages[0])) {
$pages = ['list'];
}
return [];
$repository = Di::_()->get('Notification\Manager');
$repository->setUser(Core\Session::getLoggedInUser());
......
......@@ -99,7 +99,6 @@ class register implements Interfaces\Api, Interfaces\ApiIgnorePam
'guid' => $guid,
'user' => $user->export()
];
} catch (\Exception $e) {
$response = array('status'=>'error', 'message'=>$e->getMessage());
}
......
......@@ -92,7 +92,14 @@ class comments implements Interfaces\Api
switch ($pages[0]) {
case "update":
$comment = $manager->getByLuid($pages[1]);
if (!$comment || !$comment->canEdit()) {
$canEdit = $comment->canEdit();
if ($canEdit && $comment->getOwnerGuid() != Core\Session::getLoggedInUserGuid()) {
$canEdit = false;
}
if (!$comment || !$canEdit) {
$response = array('status' => 'error', 'message' => 'This comment can not be edited');
break;
}
......
......@@ -68,9 +68,6 @@ class suggested implements Interfaces\Api
$hashtag = $_GET['hashtag'];
}
/** @var Core\Feeds\Suggested\Manager $repo */
$repo = Di::_()->get('Feeds\Suggested\Manager');
$opts = [
'user_guid' => Core\Session::getLoggedInUserGuid(),
'rating' => $rating,
......@@ -84,17 +81,19 @@ class suggested implements Interfaces\Api
$opts['hashtag'] = $hashtag;
}
$result = $repo->getFeed($opts);
// @deprecated
$result = [];
// Remove all unlisted content if it appears
$result = array_values(array_filter($result, function($entity) {
return $entity->getAccessId() != 0;
}));
// @deprecated
// // Remove all unlisted content if it appears
// $result = array_values(array_filter($result, function($entity) {
// return $entity->getAccessId() != 0;
// }));
return Factory::response([
'status' => 'success',
'entities' => Factory::exportable($result),
'load-next' => $limit + $offset,
'load-next' => '',
]);
}
......
......@@ -76,7 +76,7 @@ class DataProvider extends Provider
$username,
null,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_PERSISTENT => isset($config['persistent']) ? $config['persistent'] : false,
]);
......
......@@ -46,6 +46,7 @@ class Register
$manager = Di::_()->get('Referrals\Manager');
$manager->add($referral);
}
});
......
<?php
namespace Minds\Core\Feeds\Suggested;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities\Entity;
use Minds\Core\EntitiesBuilder;
class Manager
{
/** @var Repository */
protected $feedsRepository;
/** @var Core\EntitiesBuilder */
protected $entitiesBuilder;
/** @var \Minds\Core\Hashtags\Entity\Repository */
private $entityHashtagsRepository;
/** @var array */
private $maps;
/** @var Core\Trending\EntityValidator */
private $validator;
private $from;
private $to;
public function __construct(
$repo = null,
$entityHashtagsRepository = null,
$validator = null,
$maps = null,
$entitiesBuilder = null
) {
$this->feedsRepository = $repo ?: Di::_()->get('Feeds\Suggested\Repository');
$this->entityHashtagsRepository = $entityHashtagsRepository ?: Di::_()->get('Hashtags\Entity\Repository');
$this->validator = $validator ?: new Core\Trending\EntityValidator();
$this->maps = $maps ?: Core\Trending\Maps::$maps;
$this->entitiesBuilder = $entitiesBuilder ?: new EntitiesBuilder;
$this->from = strtotime('-12 hours') * 1000;
$this->to = time() * 1000;
}
/**
* @param array $opts
* @return Entity[]
* @throws \Exception
*/
public function getFeed(array $opts = [])
{
$opts = array_merge([
'user_guid' => null,
'offset' => 0,
'limit' => 12,
'rating' => 1,
'type' => null,
'all' => false,
], $opts);
$guids = [];
foreach ($this->feedsRepository->getFeed($opts) as $item) {
$guids[] = $item['guid'];
}
$entities = [];
if (count($guids) > 0) {
$entities = $this->entitiesBuilder->get(['guids' => $guids]);
}
return $entities;
}
public function run(string $type)
{
//\Minds\Core\Security\ACL::$ignore = true;
$scores = [];
$maps = null;
switch ($type) {
case 'all':
$maps = $this->maps;
break;
case 'channels':
$maps = ['user' => $this->maps['channels']];
break;
case 'newsfeed':
$maps = ['newsfeed' => $this->maps['newsfeed']];
break;
case 'images':
$maps = ['image' => $this->maps['images']];
break;
case 'videos':
$maps = ['video' => $this->maps['videos']];
break;
case 'groups':
$maps = ['group' => $this->maps['groups']];
break;
case 'blogs':
$maps = ['blog' => $this->maps['blogs']];
break;
case 'default':
throw new \Exception("Invalid type. Valid values are: 'newsfeed', 'images', 'videos', 'groups' and 'blogs'");
break;
}
foreach ($maps as $key => $map) {
if (!isset($scores[$key])) {
$scores[$key] = [];
}
$ratings = [];
foreach ($map['aggregates'] as $aggregate) {
$class = is_string($aggregate) ? new $aggregate : $aggregate;
$class->setLimit(10000);
$class->setType($map['type']);
$class->setSubtype($map['subtype']);
$class->setFrom($this->from);
$class->setTo($this->to);
foreach ($class->get() as $guid => $score) {
if ($score < 2) {
continue;
}
echo "\n$guid ($score)";
//collect the entity
$entity = $this->entitiesBuilder->single($guid);
if (!$entity->guid) {
continue;
}
$tags = $entity->getTags();
if ($entity->container_guid != 0 && $entity->container_guid != $entity->owner_guid && $key == 'newsfeed') {
echo " skipping because group post";
continue; // skip groups
}
$this->saveTags($entity->guid, $tags);
$ratings[$entity->guid] = $entity->getRating();
//validate this entity is ok
if (!$this->validator->isValid($entity)) {
echo "\n[$entity->getRating()] $key: $guid ($score) invalid";
continue;
}
//is this an activity entity?
//if so, let add it the guids for this key
if ($entity->custom_type == 'batch' && $entity->entity_guid) {
if (!isset($scores['image'][$entity->entity_guid])) {
$scores['image'][$entity->entity_guid] = 0;
}
$scores['image'][$entity->entity_guid] += $score;
$ratings[$entity->entity_guid] = $ratings[$guid];
$this->saveTags($entity->entity_guid, $tags);
} elseif ($entity->custom_type == 'video' && $entity->entity_guid) {
if (!isset($scores['video'][$entity->entity_guid])) {
$scores['video'][$entity->entity_guid] = 0;
}
$scores['video'][$entity->entity_guid] += $score;
$ratings[$entity->entity_guid] = $ratings[$guid];
$this->saveTags($entity->entity_guid, $tags);
} elseif (strpos($entity->perma_url, '/blog') !== false && $entity->entity_guid) {
if (!isset($scores['blog'][$entity->entity_guid])) {
$scores['blog'][$entity->entity_guid] = 0;
}
$scores['blog'][$entity->entity_guid] += $score;
$ratings[$entity->entity_guid] = $ratings[$guid];
$this->saveTags($entity->entity_guid, $tags);
echo "\n\tblog here $entity->entity_guid";
}
if (!isset($scores[$key][$guid])) {
$scores[$key][$guid] = 0;
}
$scores[$key][$guid] += $score;
}
}
//arsort($scores[$key]);
$sync = time();
foreach ($scores as $_key => $_scores) {
foreach ($_scores as $guid => $score) {
if (! (int) $score || !$guid) {
continue;
}
if (!isset($ratings[$guid])) {
$ratings[$guid] = 2;
}
$this->feedsRepository->add([
'entity_guid' => $guid,
'score' => (int) $score,
'type' => $_key,
'rating' => $ratings[$guid],
'lastSynced' => $sync,
]);
echo "\n[{$ratings[$guid]}] $_key: $guid ($score)";
}
}
}
//\Minds\Core\Security\ACL::$ignore = false;
}
private function saveTags($guid, $tags)
{
if (!$tags) {
echo " no tags";
return;
}
$hashtagEntities = [];
foreach ($tags as $tag) {
if (strpos($tag, '#', 0) === 0) {
$tag = substr($tag, 1);
}
echo "\n\t#$tag";
$hashtagEntity = new Core\Hashtags\HashtagEntity();
$hashtagEntity->setGuid($guid);
$hashtagEntity->setHashtag(strtolower($tag));
$hashtagEntities[] = $hashtagEntity;
}
//if ($key == 'newsfeed' && count($hashtagEntities) >= 5) {
// continue;
//}
foreach ($hashtagEntities as $hashtagEntity) {
$this->entityHashtagsRepository->add([$hashtagEntity]);
}
echo "\nSaved tags to repo";
}
}
<?php
namespace Minds\Core\Feeds\Suggested;
use Minds\Core\Di\Di;
class Repository
{
/** @var \PDO */
protected $db;
public function __construct($db = null)
{
$this->db = $db ?: Di::_()->get('Database\PDO');
}
/**
* @param array $opts
* @return array
* @throws \Exception
*/
public function getFeed(array $opts = [])
{
$opts = array_merge([
'user_guid' => null,
'offset' => 0,
'limit' => 12,
'rating' => 1,
'hashtag' => null,
'type' => null,
'all' => false, // if true, it ignores user selected hashtags
], $opts);
if (!$opts['user_guid']) {
throw new \Exception('user_guid must be provided');
}
if (!$opts['type']) {
throw new \Exception('type must be provided');
}
if ($opts['hashtag']) {
$query = "SELECT DISTINCT suggested.guid as guid,
lastSynced, score
FROM suggested
JOIN entity_hashtags
ON suggested.guid = entity_hashtags.guid
WHERE entity_hashtags.hashtag = ?
AND type = ?
AND rating <= ?
ORDER BY lastSynced DESC, score DESC
LIMIT ? OFFSET ?";
$opts = [
$opts['hashtag'],
$opts['type'],
$opts['rating'],
$opts['limit'],
$opts['offset']
];
} else {
// ignore user selected hashtags if all is true
if ($opts['all']) {
$query = "SELECT DISTINCT suggested.guid as guid,
lastSynced, score
FROM suggested
WHERE type = ?
AND rating <= ?
ORDER BY lastSynced DESC, score DESC
LIMIT ? OFFSET ?";
$opts = [
$opts['type'],
$opts['rating'],
$opts['limit'],
$opts['offset']
];
} else {
$query = "SELECT DISTINCT suggested.guid as guid,
lastSynced, score
FROM user_hashtags
INNER JOIN entity_hashtags
ON user_hashtags.hashtag = entity_hashtags.hashtag
INNER JOIN suggested
ON entity_hashtags.guid = suggested.guid
WHERE user_hashtags.guid = ?
AND type = ?
AND rating <= ?
ORDER BY lastSynced DESC, score DESC
LIMIT ? OFFSET ?";
$opts = [
$opts['user_guid'],
$opts['type'],
$opts['rating'],
//date('c', strtotime('-48 minutes') / $opts['user_hashtag_count']),
//strtotime('-48 hours'),
$opts['limit'],
$opts['offset']
];
}
}
$statement = $this->db->prepare($query);
$statement->execute($opts);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
public function add(array $opts = [])
{
$opts = array_merge([
'entity_guid' => null,
'score' => null,
'type' => null,
'rating' => null,
'lastSynced' => time(),
], $opts);
if (!$opts['entity_guid']) {
throw new \Exception('entity_guid must be provided');
}
if (!$opts['score']) {
throw new \Exception('score must be provided');
}
if (!$opts['type']) {
throw new \Exception('type must be provided');
}
if (!$opts['rating']) {
throw new \Exception('rating must be provided');
}
$query = "UPSERT INTO suggested (guid, rating, type, score, lastSynced) VALUES (?, ?, ?, ?, ?)";
$values = [
$opts['entity_guid'],
$opts['rating'],
$opts['type'],
$opts['score'],
date('c', $opts['lastSynced']),
];
$statement = $this->db->prepare($query);
return $statement->execute($values);
//if ($rating > 1) {
// $template .= " USING TTL 1200";
//}
}
public function removeAll($type)
{
if (!$type) {
throw new \Exception('type must be provided');
}
if ($type === 'all') {
$statement = $this->db->prepare("TRUNCATE suggested");
return $statement->execute();
}
$selectQuery = "SELECT suggested.guid AS guid
FROM suggested
JOIN entity_hashtags
ON suggested.guid = entity_hashtags.guid
WHERE suggested.type = ?";
$params = [
$type
];
$statement = $this->db->prepare($selectQuery);
$statement->execute($params);
$guids = $statement->fetchAll(\PDO::FETCH_ASSOC);
$guids = array_map(function ($item) {
return $item['guid'];
}, $guids);
$variables = implode(',', array_fill(0, count($guids), '?'));
$deleteQuery = "DELETE FROM suggested WHERE guid IN ({$variables})";
$statement = $this->db->prepare($deleteQuery);
return $statement->execute($guids);
}
}
......@@ -8,14 +8,6 @@ class FeedsProvider extends Provider
{
public function register()
{
$this->di->bind('Feeds\Suggested\Repository', function ($di) {
return new Suggested\Repository();
});
$this->di->bind('Feeds\Suggested\Manager', function ($di) {
return new Suggested\Manager();
});
$this->di->bind('Feeds\Top\Manager', function ($di) {
return new Top\Manager();
});
......
<?php
namespace Minds\Core\Feeds\Suggested;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities\Entity;
use Minds\Core\EntitiesBuilder;
class Manager
{
/** @var Repository */
protected $feedsRepository;
/** @var Core\EntitiesBuilder */
protected $entitiesBuilder;
/** @var \Minds\Core\Hashtags\Entity\Repository */
private $entityHashtagsRepository;
/** @var array */
private $maps;
/** @var Core\Trending\EntityValidator */
private $validator;
private $from;
private $to;
public function __construct(
$repo = null,
$entityHashtagsRepository = null,
$validator = null,
$maps = null,
$entitiesBuilder = null
) {
$this->feedsRepository = $repo ?: Di::_()->get('Feeds\Suggested\Repository');
$this->entityHashtagsRepository = $entityHashtagsRepository ?: Di::_()->get('Hashtags\Entity\Repository');
$this->validator = $validator ?: new Core\Trending\EntityValidator();
$this->maps = $maps ?: Core\Trending\Maps::$maps;
$this->entitiesBuilder = $entitiesBuilder ?: new EntitiesBuilder;
$this->from = strtotime('-12 hours') * 1000;
$this->to = time() * 1000;
}
public function setFrom($from)
{
$this->from = $from;
return $this;
}
/**
* @param array $opts
* @return Entity[]
* @throws \Exception
*/
public function getFeed(array $opts = [])
{
$opts = array_merge([
'user_guid' => null,
'offset' => 0,
'limit' => 12,
'rating' => 1,
'type' => null,
'all' => false,
], $opts);
$guids = [];
foreach ($this->feedsRepository->getFeed($opts) as $item) {
$guids[] = $item['guid'];
}
$entities = [];
if (count($guids) > 0) {
$entities = $this->entitiesBuilder->get(['guids' => $guids]);
}
return $entities;
}
public function run(string $type)
{
//\Minds\Core\Security\ACL::$ignore = true;
$scores = [];
$maps = null;
switch ($type) {
case 'all':
$maps = $this->maps;
break;
case 'channels':
$maps = ['user' => $this->maps['channels']];
break;
case 'newsfeed':
$maps = ['newsfeed' => $this->maps['newsfeed']];
break;
case 'images':
$maps = ['image' => $this->maps['images']];
break;
case 'videos':
$maps = ['video' => $this->maps['videos']];
break;
case 'groups':
$maps = ['group' => $this->maps['groups']];
break;
case 'blogs':
$maps = ['blog' => $this->maps['blogs']];
break;
case 'default':
throw new \Exception("Invalid type. Valid values are: 'newsfeed', 'images', 'videos', 'groups' and 'blogs'");
break;
}
foreach ($maps as $key => $map) {
if (!isset($scores[$key])) {
$scores[$key] = [];
}
$ratings = [];
$ownersCounts = [];
foreach ($map['aggregates'] as $aggregate) {
$class = is_string($aggregate) ? new $aggregate : $aggregate;
$class->setLimit(10000);
$class->setType($map['type']);
$class->setSubtype($map['subtype']);
$class->setFrom($this->from);
$class->setTo($this->to);
foreach ($class->get() as $guid => $score) {
if ($score < 2) {
// continue;
}
echo "\n$guid ($score)";
//collect the entity
$entity = $this->entitiesBuilder->single($guid);
if (!$entity->guid) {
continue;
}
if (isset($ownersCounts[$entity->owner_guid]) && $entity->type != 'user') {
continue;
}
$ownersCounts[$entity->owner_guid] = 1;
$tags = $entity->getTags();
if ($entity->container_guid != 0 && $entity->container_guid != $entity->owner_guid) {
echo " skipping because group post";
continue; // skip groups
}
$this->saveTags($entity->guid, $tags);
$ratings[$entity->guid] = $entity->getRating();
//validate this entity is ok
if (!$this->validator->isValid($entity)) {
echo "\n[$entity->getRating()] $key: $guid ($score) invalid";
$this->feedsRepository->remove($key, $guid);
continue;
}
//is this an activity entity?
//if so, let add it the guids for this key
if ($entity->custom_type == 'batch' && $entity->entity_guid) {
if (!isset($scores['image'][$entity->entity_guid])) {
$scores['image'][$entity->entity_guid] = 0;
}
//$scores['image'][$entity->entity_guid] += $score;
//$ratings[$entity->entity_guid] = $ratings[$guid];
$this->saveTags($entity->entity_guid, $tags);
} elseif ($entity->custom_type == 'video' && $entity->entity_guid) {
if (!isset($scores['video'][$entity->entity_guid])) {
$scores['video'][$entity->entity_guid] = 0;
}
//$scores['video'][$entity->entity_guid] += $score;
//$ratings[$entity->entity_guid] = $ratings[$guid];
$this->saveTags($entity->entity_guid, $tags);
} elseif (strpos($entity->perma_url, '/blog') !== false && $entity->entity_guid) {
if (!isset($scores['blog'][$entity->entity_guid])) {
$scores['blog'][$entity->entity_guid] = 0;
}
//$scores['blog'][$entity->entity_guid] += $score;
//$ratings[$entity->entity_guid] = $ratings[$guid];
$this->saveTags($entity->entity_guid, $tags);
echo "\n\tblog here $entity->entity_guid";
}
if (!isset($scores[$key][$guid])) {
$scores[$key][$guid] = 0;
}
$scores[$key][$guid] += $score;
}
}
//arsort($scores[$key]);
$sync = time();
foreach ($scores as $_key => $_scores) {
foreach ($_scores as $guid => $score) {
if (! (int) $score || !$guid) {
echo "\n[{$ratings[$guid]}] $_key: $guid ($score) FAILED";
continue;
}
if (!isset($ratings[$guid])) {
$ratings[$guid] = 2;
}
$this->feedsRepository->add([
'entity_guid' => $guid,
'score' => (int) $score,
'type' => $_key,
'rating' => $ratings[$guid],
'lastSynced' => $sync,
]);
echo "\n[{$ratings[$guid]}] $_key: $guid ($score)";
}
}
}
//\Minds\Core\Security\ACL::$ignore = false;
}
private function saveTags($guid, $tags)
{
if (!$tags) {
echo " no tags";
return;
}
$hashtagEntities = [];
foreach ($tags as $tag) {
if (strpos($tag, '#', 0) === 0) {
$tag = substr($tag, 1);
}
echo "\n\t#$tag";
$hashtagEntity = new Core\Hashtags\HashtagEntity();
$hashtagEntity->setGuid($guid);
$hashtagEntity->setHashtag(strtolower($tag));
$hashtagEntities[] = $hashtagEntity;
}
//if ($key == 'newsfeed' && count($hashtagEntities) >= 5) {
// continue;
//}
foreach ($hashtagEntities as $hashtagEntity) {
$this->entityHashtagsRepository->add([$hashtagEntity]);
}
echo "\nSaved tags to repo";
}
}
<?php
namespace Minds\Core\Feeds\Suggested;
use Minds\Core\Di\Di;
class Repository
{
/** @var \PDO */
protected $db;
public function __construct($db = null)
{
$this->db = $db ?: Di::_()->get('Database\PDO');
}
/**
* @param array $opts
* @return array
* @throws \Exception
*/
public function getFeed(array $opts = [])
{
$opts = array_merge([
'user_guid' => null,
'offset' => 0,
'limit' => 12,
'rating' => 1,
'hashtag' => null,
'type' => null,
'all' => false, // if true, it ignores user selected hashtags
], $opts);
if (!$opts['user_guid']) {
throw new \Exception('user_guid must be provided');
}
if (!$opts['type']) {
throw new \Exception('type must be provided');
}
if ($opts['hashtag']) {
$opts['hashtag'] = strtolower($opts['hashtag']);
$query = "SELECT DISTINCT suggested.guid as guid,
lastSynced, score
FROM suggested
JOIN entity_hashtags
ON suggested.guid = entity_hashtags.guid
WHERE entity_hashtags.hashtag = ?
AND type = ?
AND rating <= ?
ORDER BY lastSynced DESC, score DESC
LIMIT ? OFFSET ?";
$opts = [
$opts['hashtag'],
$opts['type'],
$opts['rating'],
$opts['limit'],
$opts['offset']
];
} else {
// ignore user selected hashtags if all is true
if ($opts['all']) {
$query = "SELECT DISTINCT suggested.guid as guid,
lastSynced, score
FROM suggested
WHERE type = ?
AND rating <= ?
ORDER BY lastSynced DESC, score DESC
LIMIT ? OFFSET ?";
$opts = [
$opts['type'],
$opts['rating'],
$opts['limit'],
$opts['offset']
];
} else {
$query = "SELECT DISTINCT suggested.guid as guid,
lastSynced, score
FROM user_hashtags
INNER JOIN entity_hashtags
ON user_hashtags.hashtag = entity_hashtags.hashtag
INNER JOIN suggested
ON entity_hashtags.guid = suggested.guid
WHERE user_hashtags.guid = ?
AND type = ?
AND rating <= ?
ORDER BY lastSynced DESC, score DESC
LIMIT ? OFFSET ?";
$opts = [
$opts['user_guid'],
$opts['type'],
$opts['rating'],
//date('c', strtotime('-48 minutes') / $opts['user_hashtag_count']),
//strtotime('-48 hours'),
$opts['limit'],
$opts['offset']
];
}
}
$statement = $this->db->prepare($query);
$statement->execute($opts);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
public function add(array $opts = [])
{
$opts = array_merge([
'entity_guid' => null,
'score' => null,
'type' => null,
'rating' => null,
'lastSynced' => time(),
], $opts);
if (!$opts['entity_guid']) {
throw new \Exception('entity_guid must be provided');
}
if (!$opts['score']) {
throw new \Exception('score must be provided');
}
if (!$opts['type']) {
throw new \Exception('type must be provided');
}
if (!$opts['rating']) {
throw new \Exception('rating must be provided');
}
$query = "UPSERT INTO suggested (guid, rating, type, score, lastSynced) VALUES (?, ?, ?, ?, ?)";
$values = [
$opts['entity_guid'],
$opts['rating'],
$opts['type'],
$opts['score'],
date('c', $opts['lastSynced']),
];
$statement = $this->db->prepare($query);
return $statement->execute($values);
//if ($rating > 1) {
// $template .= " USING TTL 1200";
//}
}
public function removeAll($type)
{
if (!$type) {
throw new \Exception('type must be provided');
}
if ($type === 'all') {
$statement = $this->db->prepare("TRUNCATE suggested");
return $statement->execute();
}
$selectQuery = "SELECT suggested.guid AS guid
FROM suggested
JOIN entity_hashtags
ON suggested.guid = entity_hashtags.guid
WHERE suggested.type = ?";
$params = [
$type
];
$statement = $this->db->prepare($selectQuery);
$statement->execute($params);
$guids = $statement->fetchAll(\PDO::FETCH_ASSOC);
$guids = array_map(function ($item) {
return $item['guid'];
}, $guids);
$variables = implode(',', array_fill(0, count($guids), '?'));
$deleteQuery = "DELETE FROM suggested WHERE guid IN ({$variables})";
$statement = $this->db->prepare($deleteQuery);
return $statement->execute($guids);
}
public function remove($key, $guid)
{
$statement = $this->db->prepare("DELETE FROM suggested WHERE guid = ?");
return $statement->execute([ $guid ]);
}
}
......@@ -101,7 +101,8 @@ class Entities
];
$activity = new Activity();
$activity->setEphemeral(true);
$activity->setEphemeral(true)
->setHideImpressions(true);
if ($entity instanceof Blog) {
// New entities
......
<?php
namespace Minds\Core\Hashtags\Entity;
use Minds\Core\Di\Di;
use Minds\Core\Hashtags\HashtagEntity;
class Repository
{
/** @var \PDO */
protected $db;
public function __construct($db = null)
{
$this->db = $db ?: Di::_()->get('Database\PDO');
}
/**
* Return all hashtags
*/
public function getAll($opts = [])
{
$opts = array_merge([
'entity_guid' => null
], $opts);
if (!$opts['entity_guid']) {
throw new \Exception('entity_guid must be provided');
}
$query = "SELECT * FROM entity_hashtags WHERE guid=?";
$params = [$opts['entity_guid']];
$statement = $this->db->prepare($query);
$statement->execute($params);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* @param HashtagEntity[] $hashtags
* @return bool
*/
public function add($hashtags)
{
$query = "UPSERT INTO entity_hashtags (guid, hashtag) VALUES (?, ?)";
foreach ($hashtags as $hashtag) {
try {
$statement = $this->db->prepare($query);
if (!$hashtag->getHashtag()) {
continue;
}
return $statement->execute([$hashtag->getGuid(), $hashtag->getHashtag()]);
} catch (\Exception $e) {
error_log($e->getMessage());
}
}
return false;
}
}
......@@ -32,5 +32,4 @@ class HashtagEntity
'hashtag' => $this->getHashtag(),
];
}
}
......@@ -17,14 +17,6 @@ class HashtagsProvider extends Provider
return new User\Manager();
});
$this->di->bind('Hashtags\Entity\Repository', function ($di) {
return new Entity\Repository();
});
$this->di->bind('Hashtags\Suggested\Repository', function ($di) {
return new Suggested\Repository();
});
$this->di->bind('Hashtags\Trending\Repository', function ($di) {
return new Trending\Repository();
});
......
<?php
namespace Minds\Core\Hashtags\Suggested;
use Minds\Core\Di\Di;
class Repository
{
/** @var \PDO $db */
protected $db;
public function __construct(\PDO $db = null)
{
$this->db = $db ?: Di::_()->get('Database\PDO');
}
/**
* @param array $opts
* @return array
* @throws \Exception
*/
public function getAll(array $opts = [])
{
$opts = array_merge([
'user_guid' => null,
'limit' => null
], $opts);
if (!$opts['user_guid']) {
throw new \Exception('user_guid must be provided');
}
$query = "SELECT DISTINCT
CASE WHEN user_hashtags.hashtag IS NOT NULL THEN user_hashtags.hashtag ELSE entity_hashtags.hashtag END as value,
CASE WHEN user_hashtags.guid IS NOT NULL THEN true ELSE false END as selected
FROM suggested
JOIN entity_hashtags
ON suggested.guid = entity_hashtags.guid
FULL OUTER JOIN user_hashtags
ON (entity_hashtags.hashtag = user_hashtags.hashtag OR user_hashtags.hashtag = null)
WHERE suggested.lastSynced > ? OR suggested.lastsynced IS NULL AND user_hashtags.guid = ?
ORDER BY selected DESC, suggested.score DESC";
$params = [
date('c', strtotime('24 hours ago')),
$opts['user_guid'],
];
if ($opts['limit']) {
$query .= " LIMIT ?";
$params[] = $opts['limit'];
}
$statement = $this->db->prepare($query);
$statement->execute($params);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
}
......@@ -2,6 +2,11 @@
namespace Minds\Core\Hashtags\Trending;
use Cassandra\Timestamp;
use Cassandra\Bigint;
use Minds\Core\Data\Cassandra\Client as CassandraClient;
use Minds\Core\Data\Cassandra\Prepared\Custom;
use Minds\Core\Data\ElasticSearch\Client as ElasticSearchClient;
use Minds\Core\Di\Di;
use Minds\Core\Data\ElasticSearch\Prepared\Search as Prepared;
use Minds\Common\Repository\Response;
......@@ -11,15 +16,15 @@ use Minds\Common\Repository\Response;
*/
class Repository
{
/** @var \PDO $db */
/** @var CassandraClient $db */
protected $db;
/** @var ElasticSearch $es */
/** @var ElasticSearchClient $es */
protected $es;
public function __construct(\PDO $db = null, $es = null)
public function __construct($db = null, $es = null)
{
$this->db = $db ?: Di::_()->get('Database\PDO');
$this->db = $db ?: Di::_()->get('Database\Cassandra\Cql');
$this->es = $es ?: Di::_()->get('Database\ElasticSearch');
}
......@@ -122,7 +127,7 @@ class Repository
}
/**
* Return a confifdence score
* Return a confidence score
* TODO: Move to global helper
* @param int $positive
* @param int $total
......@@ -137,114 +142,102 @@ class Repository
return $n / $d;
}
/**
* Get trending hashtags
*
* @param array $opts
* @return array
*/
public function getTrending(array $opts = [])
{
$opts = array_merge([
'from_date' => date('c', strtotime('24 hours ago')),
'limit' => 20
], $opts);
$query = "SELECT entity_hashtags.hashtag, COUNT(*) as hashCount
FROM entity_hashtags
JOIN suggested ON suggested.guid = entity_hashtags.guid
LEFT JOIN hidden_hashtags ON hidden_hashtags.hashtag = entity_hashtags.hashtag
WHERE suggested.lastsynced >= ? AND hidden_hashtags.hashtag IS NULL
GROUP BY entity_hashtags.hashtag
ORDER BY hashCount Desc
LIMIT ?";
$statement = $this->db->prepare($query);
$statement->execute([$opts['from_date'], $opts['limit']]);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Hide a tag from trending list
*
* @param string $tag
* @param string $admin_guid
* @return void
* @return bool
*/
public function hide($tag, $admin_guid)
{
$query = "INSERT INTO hidden_hashtags(hashtag, hidden_since, admin_guid) VALUES (?, ?, ?)";
$cql = "INSERT INTO hidden_hashtags (hashtag, hidden_since, admin_guid) VALUES (?, ?, ?)";
$values = [
$tag,
new Timestamp(time()),
new Bigint($admin_guid),
];
try {
$statement = $this->db->prepare($query);
$prepared = new Custom();
$prepared->query($cql, $values);
return $statement->execute([$tag, date('c'), $admin_guid]);
try {
return (bool) $this->db->request($prepared, true);
} catch (\Exception $e) {
error_log($e->getMessage());
return false;
}
return false;
}
/**
* Unhide a tag from trending list
*
* @param string $tag
* @return void
* @return bool
*/
public function unhide($tag)
{
$query = "DELETE FROM hidden_hashtags WHERE hashtag = ?";
$cql = "DELETE FROM hidden_hashtags WHERE hashtag = ?";
$values = [
$tag
];
try {
$statement = $this->db->prepare($query);
$prepared = new Custom();
$prepared->query($cql, $values);
return $statement->execute([$tag]);
try {
return (bool) $this->db->request($prepared, true);
} catch (\Exception $e) {
error_log($e->getMessage());
return false;
}
return false;
}
/**
* Return hidden hashtags list
* @param array $opts
* @return array
*/
public function getHidden($opts = [])
public function getHidden(array $opts = [])
{
$opts = array_merge([
'limit' => 500
], $opts);
$query = "SELECT hashtag, admin_guid, hidden_since FROM hidden_hashtags LIMIT ?";
$params = [$opts['limit']];
$cql = "SELECT hashtag, hidden_since, admin_guid FROM hidden_hashtags";
$statement = $this->db->prepare($query);
$prepared = new Custom();
$prepared->query($cql);
$prepared->setOpts([
'page_size' => (int) $opts['limit'],
]);
$statement->execute($params);
$rows = $this->db->request($prepared);
$response = [];
return $statement->fetchAll(\PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$response[] = $row;
}
return $response;
}
/**
* Clear trending hidden table
*
* @return void
* @return bool
*/
public function clearHidden()
{
$query = "TRUNCATE TABLE hidden_hashtags";
$cql = "TRUNCATE TABLE hidden_hashtags";
try {
$statement = $this->db->prepare($query);
$prepared = new Custom();
$prepared->query($cql);
return $statement->execute();
try {
return (bool) $this->db->request($prepared, true);
} catch (\Exception $e) {
error_log($e->getMessage());
return false;
}
return false;
}
}
<?php
namespace Minds\Core\Hashtags\User;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Di\Di;
use Minds\Core\Hashtags\HashtagEntity;
class LegacyRepository
{
/** @var \PDO */
protected $db;
/** @var abstractCacher */
protected $cacher;
public function __construct($db = null, $cacher = null)
{
$this->db = $db ?: Di::_()->get('Database\PDO');
$this->cacher = $cacher ?: Di::_()->get('Cache');
}
/**
* Return all hashtags
*/
public function getAll($opts = [])
{
$opts = array_merge([
'user_guid' => null
], $opts);
if (!$opts['user_guid']) {
throw new \Exception('user_guid must be provided');
}
$query = "SELECT hashtag FROM user_hashtags WHERE guid = ?";
$params = [$opts['user_guid']];
$statement = $this->db->prepare($query);
$statement->execute($params);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Return all hashtags in db
* For migration
*/
public function _getEverything($offset, $limit)
{
$query = "SELECT * FROM user_hashtags LIMIT ? OFFSET ?";
$params = [intval($limit), intval($offset)];
$statement = $this->db->prepare($query);
$statement->execute($params);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* @param HashtagEntity[] $hashtags
* @return bool
*/
public function add($hashtags)
{
$query = "UPSERT INTO user_hashtags(guid, hashtag) VALUES (?, ?)";
foreach ($hashtags as $hashtag) {
try {
$statement = $this->db->prepare($query);
$this->cacher->destroy("user-selected-hashtags:{$hashtag->getGuid()}");
return $statement->execute([$hashtag->getGuid(), $hashtag->getHashtag()]);
} catch (\Exception $e) {
error_log($e->getMessage());
}
}
return false;
}
/**
* @param $user_guid
* @param array $hashtags
* @return bool
*/
public function remove($user_guid, array $hashtags)
{
$variables = implode(',', array_fill(0, count($hashtags), '?'));
$query = "DELETE FROM user_hashtags WHERE guid = ? AND hashtag IN ({$variables})";
$statement = $this->db->prepare($query);
$this->cacher->destroy("user-selected-hashtags:{$user_guid}");
return $statement->execute(array_merge([$user_guid], $hashtags));
}
public function update()
{
}
}
......@@ -4,7 +4,6 @@ namespace Minds\Core\Hashtags\User;
use Cassandra\Varint;
use Minds\Common\Repository\Response;
use Minds\Core\Config;
use Minds\Core\Data\Cassandra\Client;
use Minds\Core\Data\Cassandra\Prepared\Custom;
use Minds\Core\Di\Di;
......@@ -15,17 +14,9 @@ class Repository
/** @var Client */
protected $db;
/** @var LegacyRepository */
protected $legacyRepository;
/** @var Config */
protected $config;
public function __construct($db = null, $legacyRepository = null, $config = null)
public function __construct($db = null)
{
$this->db = $db ?: Di::_()->get('Database\Cassandra\Cql');
$this->legacyRepository = $legacyRepository ?: new LegacyRepository();
$this->config = $config ?: Di::_()->get('Config');
}
/**
......@@ -35,20 +26,6 @@ class Repository
*/
public function getAll($opts = [])
{
// Legacy fallback
if ($this->config->get('user_hashtags_legacy_read')) {
$rows = $this->legacyRepository->getAll($opts);
$response = new Response();
foreach ($rows as $row) {
$response[] = (new HashtagEntity())
->setGuid($opts['user_guid'])
->setHashtag($row['hashtag']);
}
return $response;
}
$opts = array_merge([
'user_guid' => null
], $opts);
......@@ -112,10 +89,6 @@ class Repository
}
}
if ($this->config->get('user_hashtags_migration')) {
$this->legacyRepository->add($hashtags);
}
return true;
}
......@@ -148,10 +121,6 @@ class Repository
return false;
}
if ($this->config->get('user_hashtags_migration')) {
$this->legacyRepository->remove($userGuid, $hashtagValues);
}
return true;
}
......
......@@ -42,7 +42,7 @@ class Manager
$this->image->setImageBackgroundColor('white');
$this->image = $this->image->flattenImages();
$this->image = $this->image->mergeImageLayers($this->image::LAYERMETHOD_FLATTEN);
$this->image->setImageCompression($quality);
$this->image->setImageFormat('jpg');
......
......@@ -154,7 +154,7 @@ class Resize
$this->output->setImageBackgroundColor('white');
$this->output = $this->output->flattenImages();
$this->output = $this->output->mergeImageLayers($this->image::LAYERMETHOD_FLATTEN);
$this->output->setImageCompression($quality);
$this->output->setImageFormat('jpg');
......
......@@ -74,6 +74,10 @@ class CassandraRepository
return false;
}
if (!$result) {
return false;
}
$response = new Response();
foreach ($result as $row) {
$notification = new Notification();
......@@ -100,8 +104,8 @@ class CassandraRepository
*/
public function get($urn)
{
list ($to_guid, $uuid) = explode('-', $this->urn->setUrn($urn)->getNss());
list ($to_guid, $uuid) = explode('-', $this->urn->setUrn($urn)->getNss(), 2);
$response = $this->getList([
'to_guid' => $to_guid,
'uuid' => $uuid,
......@@ -162,7 +166,7 @@ class CassandraRepository
return false;
}
return $success;
return $notification->getUuid();
}
// TODO
......
......@@ -6,6 +6,7 @@
namespace Minds\Core\Notification;
use Minds\Core;
use Minds\Core\Features\Manager as FeaturesManager;
use Minds\Entities;
use Minds\Helpers;
use Minds\Core\Di\Di;
......@@ -17,13 +18,20 @@ class Counters
/** @var $sql */
private $sql;
/** @var FeaturesManager */
private $features;
/** @var User $user */
private $user;
public function __construct($sql = null)
public function __construct($sql = null, $features = null)
{
$this->sql = $sql ?: Di::_()->get('Database\PDO');
$this->user = Core\Session::getLoggedInUser();
$this->features = $features ?: new FeaturesManager;
if (!$this->features->has('cassandra-notifications')) {
$this->sql = $sql ?: Di::_()->get('Database\PDO');
}
}
/**
......@@ -50,7 +58,9 @@ class Counters
*/
public function getCount(array $options = [])
{
// return Helpers\Counters::get($this->user, 'notifications:count', false);
if ($this->features->has('cassandra-notifications')) {
return Helpers\Counters::get($this->user, 'notifications:count', false);
}
// TODO: Remove below once settled
......@@ -104,21 +114,23 @@ class Counters
// TODO: Remove below once settled
$query = "BEGIN;
UPDATE notifications
SET read_timestamp = NOW()
WHERE to_guid = ?
ORDER BY created_timestamp DESC
LIMIT 6
RETURNING NOTHING;
COMMIT;";
$params = [
(int) $this->user->getGuid(),
];
if (!$this->features->has('cassandra-notifications')) {
$query = "BEGIN;
UPDATE notifications
SET read_timestamp = NOW()
WHERE to_guid = ?
ORDER BY created_timestamp DESC
LIMIT 6
RETURNING NOTHING;
COMMIT;";
$statement = $this->sql->prepare($query);
$statement->execute($params);
$params = [
(int) $this->user->getGuid(),
];
$statement = $this->sql->prepare($query);
$statement->execute($params);
}
}
}
......@@ -37,9 +37,12 @@ class Manager
)
{
$this->config = $config ?: Di::_()->get('Config');
$this->repository = $repository ?: new Repository;
$this->cassandraRepository = $cassandraRepository ?: new CassandraRepository;
$this->features = $features ?: new FeaturesManager;
if (!$this->features->has('cassandra-notifications')) {
$this->repository = $repository ?: new Repository;
}
}
/**
......@@ -60,10 +63,13 @@ class Manager
*/
public function getSingle($urn)
{
if (strpos($urn, 'urn:') !== FALSE) {
return $this->cassandraRepository->get($urn);
if (strpos($urn, 'urn:') === FALSE) {
$urn = "urn:notification:" . implode('-', [
$this->user->getGuid(),
$urn
]);
}
return $this->repository->get($urn);
return $this->cassandraRepository->get($urn);
}
/**
......@@ -94,6 +100,8 @@ class Manager
'friends',
'welcome_chat',
'welcome_discover',
'referral_pending',
'referral_complete'
];
break;
case "groups":
......@@ -154,7 +162,10 @@ class Manager
{
try {
$this->cassandraRepository->add($notification);
$uuid = $this->repository->add($notification);
if (!$this->features->has('cassandra-notifications')) {
$uuid = $this->repository->add($notification);
}
return $uuid;
} catch (\Exception $e) {
......@@ -178,6 +189,8 @@ class Manager
case 'friends':
case 'welcome_chat':
case 'welcome_discover':
case 'referral_pending':
case 'referral_complete':
return 'subscriptions';
break;
case 'group_invite':
......
......@@ -32,7 +32,7 @@ class Manager
public function __construct($items = null, $config = null)
{
$this->items = $items ?: [
'creator_frequency' => new Delegates\CreatorFrequencyDelegate(),
// 'creator_frequency' => new Delegates\CreatorFrequencyDelegate(),
'suggested_hashtags' => new Delegates\SuggestedHashtagsDelegate(),
'suggested_channels' => new Delegates\SuggestedChannelsDelegate(),
// 'suggested_groups' => new Delegates\SuggestedGroupsDelegate(),
......
......@@ -1408,6 +1408,13 @@ CREATE TABLE minds.update_markers (
PRIMARY KEY (user_guid, entity_type, entity_guid, marker)
);
CREATE TABLE minds.hidden_hashtags (
hashtag text,
admin_guid bigint,
hidden_since timestamp,
PRIMARY KEY (hashtag)
);
CREATE TABLE minds.referrals (
referrer_guid bigint,
prospect_guid bigint,
......
<?php
/**
* Notification delegate for referrals
*/
namespace Minds\Core\Referrals\Delegates;
use Minds\Core\Di\Di;
use Minds\Core\Referrals\Referral;
use Minds\Core\Events\EventsDispatcher;
class NotificationDelegate
{
/** @var EventsDispatcher */
protected $dispatcher;
/** @var EntitiesBuilder $entitiesBuilder */
protected $entitiesBuilder;
public function __construct($dispatcher = null, $entitiesBuilder = null, $urn = null)
{
$this->dispatcher = $dispatcher ?: Di::_()->get('EventsDispatcher');
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
}
/**
* Sends a notification to referrer
* @param Referral $referral
* @return void
*/
public function send(Referral $referral)
{
$entityGuid = $referral->getProspectGuid();
$entity = $this->entitiesBuilder->single($entityGuid);
if (!$referral->getJoinTimestamp()) {
$notification_view = 'referral_pending';
} else {
$notification_view = 'referral_complete';
}
$this->dispatcher->trigger('notification', 'all', [
'to' => [$referral->getReferrerGuid()],
'entity' => $entity,
'from' => 100000000000000519,
'notification_view' => $notification_view,
'params' => [],
]);
}
}
\ No newline at end of file
......@@ -13,12 +13,18 @@ class Manager
/** @var Repository $repository */
private $repository;
/** @var Delegates\NotificationDelegate $notificationDelegate */
private $notificationDelegate;
public function __construct(
$repository = null,
$notificationDelegate = null,
$entitiesBuilder = null
)
{
$this->repository = $repository ?: new Repository;
$this->notificationDelegate = $notificationDelegate ?: new Delegates\NotificationDelegate;
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
}
......@@ -56,6 +62,12 @@ class Manager
{
$this->repository->add($referral);
<<<<<<< HEAD
// Send a notification to the referrer
$this->notificationDelegate->send($referral);
=======
>>>>>>> 314232eec078b2da38dc518fe260a53ce87f1e63
return true;
}
......@@ -66,7 +78,14 @@ class Manager
*/
public function update($referral)
{
<<<<<<< HEAD
$this->repository->update($referral);
// Send a notification to the referrer
$this->notificationDelegate->send($referral);
=======
$this->repository->update($referral);
>>>>>>> 314232eec078b2da38dc518fe260a53ce87f1e63
return true;
}
......
......@@ -14,9 +14,5 @@ class Provider extends DiProvider
$this->di->bind('Referrals\Manager', function ($di) {
return new Manager();
}, [ 'useFactory'=>false ]);
// $this->di->bind('Referrals\Repository', function ($di) {
// return new Repository();
// }, ['useFactory' => true]);
}
}
......@@ -29,7 +29,8 @@ class ReferralDelegate
->setProspectGuid($user->guid)
->setJoinTimestamp(time());
$this->manager->update($referral);
$this->manager->update($referral);
}
}
\ No newline at end of file
......@@ -403,6 +403,11 @@ class Defaults
'description' => 'Everything you need to know about Minds',
'image' => 'assets/photos/balloon.jpg',
],
'mobile' => [
'title' => 'Minds Mobile App',
'description' => 'Download the Minds mobile app for Android & iOS.',
'image' => 'assets/photos/mobile-app.jpg',
]
];
foreach ($marketing as $uri => $page) {
......
......@@ -23,12 +23,10 @@ class Manager
public function __construct(
$repository = null,
$entitiesBuilder = null,
$suggestedFeedsManager = null,
$subscriptionsManager = null
) {
$this->repository = $repository ?: new Repository();
$this->entitiesBuilder = $entitiesBuilder ?: new EntitiesBuilder();
//$this->suggestedFeedsManager = $suggestedFeedsManager ?: Di::_()->get('Feeds\Suggested\Manager');
$this->subscriptionsManager = $subscriptionsManager ?: Di::_()->get('Subscriptions\Manager');
}
......@@ -106,14 +104,6 @@ class Manager
$response = new Response();
//$result = $this->suggestedFeedsManager->getFeed($opts);
//foreach ($result as $user) {
// $suggestion = new Suggestion();
// $suggestion->setEntityGuid($user->guid);
// $response[] = $suggestion;
//}
$guids = [
626772382194872329,
100000000000065670,
......
......@@ -15,6 +15,8 @@ class Activity extends Entity
protected $dirtyIndexes = false;
protected $hide_impressions = false;
/**
* Initialize entity attributes
* @return null
......@@ -215,6 +217,7 @@ class Activity extends Entity
'pending',
'rating',
'ephemeral',
'hide_impressions',
));
}
......@@ -272,6 +275,10 @@ class Activity extends Entity
$export['ephemeral'] = $this->getEphemeral();
$export['ownerObj'] = $this->getOwnerObj();
if ($this->hide_impressions) {
$export['hide_impressions'] = $this->hide_impressions;
}
switch($this->custom_type) {
case 'video':
if ($this->custom_data['guid']) {
......@@ -681,6 +688,24 @@ class Activity extends Entity
return $this;
}
/**
* @return bool
*/
public function getHideImpressions()
{
return (bool) $this->hide_impressions;
}
/**
* @param $value
* @return $this
*/
public function setHideImpressions($value)
{
$this->hide_impressions = (bool) $value;
return $this;
}
public function getOwnerObj()
{
if (!$this->ownerObj && $this->owner_guid) {
......
<?php
namespace Spec\Minds\Core\Feeds\Suggested;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Feeds\Suggested\Manager;
use Minds\Core\Feeds\Suggested\Repository;
use Minds\Core\Trending\Aggregates\Aggregate;
use Minds\Core\Trending\EntityValidator;
use Minds\Entities\Activity;
use Minds\Entities\Entity;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $repo;
private $entityHashtagsRepo;
private $agg;
private $aggImages;
private $validator;
private $entitiesBuilder;
function let(
Repository $repo,
\Minds\Core\Hashtags\Entity\Repository $entityHashtagsRepo,
Aggregate $agg,
Aggregate $aggImages,
EntityValidator $validator,
EntitiesBuilder $entitiesBuilder
) {
$this->repo = $repo;
$this->agg = $agg;
$this->aggImages = $aggImages;
$this->entityHashtagsRepo = $entityHashtagsRepo;
$this->validator = $validator;
$maps = [
'newsfeed' => [
'type' => 'activity',
'subtype' => '',
'aggregates' => [
$agg
]
],
'images' => [
'type' => 'object',
'subtype' => 'image',
'aggregates' => [
$aggImages
]
]
];
$this->entitiesBuilder = $entitiesBuilder;
$this->beConstructedWith($repo, $entityHashtagsRepo, $validator, $maps, $entitiesBuilder);
}
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
function it_should_get_an_empty_feed()
{
$this->repo->getFeed([
'user_guid' => 100,
'offset' => 0,
'limit' => 12,
'rating' => 2,
'type' => 'activity',
'all' => false
])
->shouldBeCalled()
->willReturn([]);
$this->getFeed(['user_guid' => 100, 'type' => 'activity', 'rating' => 2, 'all' => false])->shouldReturn([]);
}
function it_should_get_the_suggested_feed(Activity $activity1, Activity $activity2)
{
$this->repo->getFeed([
'user_guid' => 100,
'offset' => 0,
'limit' => 12,
'rating' => 2,
'type' => 'activity',
'all' => false
])
->shouldBeCalled()
->willReturn([['guid' => 1], ['guid' => 2]]);
$this->entitiesBuilder->get(['guids' => [1, 2]])
->shouldBeCalled()
->willReturn([$activity1, $activity2]);
$this->getFeed(['user_guid' => 100, 'type' => 'activity', 'rating' => 2, 'all' => false])->shouldReturn([
$activity1,
$activity2
]);
}
function it_should_collect_and_store_suggested_entities()
{
$entities = [
1 => (new Entity)
->set('guid', 10)
->set('owner_guid', 1)
->set('type', 'activity')
->set('perma_url', 'https://minds.com/blog/view/1000001')
->set('message', '#hashtag')
->setRating(1),
2 => (new Entity)
->set('guid', 20)
->set('owner_guid', 2)
->set('type', 'activity')
->set('message', '#hashtag')
->setRating(1),
3 => (new Entity)
->set('guid', 30)
->set('owner_guid', 3)
->set('type', 'activity')
->set('message', '#hashtag')
->setRating(1),
];
foreach ($entities as $entity) {
$this->entitiesBuilder->single($entity->guid)
->willReturn($entity);
}
$this->validator->isValid(Argument::any())
->shouldBeCalledTimes(4)
->willReturn(true);
$this->agg->setType('activity')->shouldBeCalled();
$this->agg->setSubtype('')->shouldBeCalled();
$this->agg->setFrom(Argument::any())->shouldBeCalled();
$this->agg->setTo(Argument::any())->shouldBeCalled();
$this->agg->setLimit(10000)->shouldBeCalled();
$this->agg->get()
->shouldBeCalled()
->willReturn([
10 => 5,
20 => 10,
30 => 10,
]);
$this->aggImages->setType('object')->shouldBeCalled();
$this->aggImages->setSubtype('image')->shouldBeCalled();
$this->aggImages->setFrom(Argument::any())->shouldBeCalled();
$this->aggImages->setTo(Argument::any())->shouldBeCalled();
$this->aggImages->setLimit(10000)->shouldBeCalled();
$this->aggImages->get()
->shouldBeCalled()
->willReturn([
10 => 5,
]);
$this->repo->add(Argument::any())
->shouldBeCalledTimes(7);
$this->run('all');
}
}
<?php
namespace Spec\Minds\Core\Feeds\Suggested;
use Minds\Core\Feeds\Suggested\Repository;
use Minds\Entities\Activity;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
{
private $db;
function let(\PDO $db)
{
$this->db = $db;
$this->beConstructedWith($db);
}
function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
}
function it_should_throw_an_exception_when_getting_the_feed_if_user_guid_isnt_set()
{
$this->shouldThrow(new \Exception('user_guid must be provided'))->during('getFeed');
}
function it_should_throw_an_exception_when_getting_the_feed_if_type_isnt_set()
{
$this->shouldThrow(new \Exception('type must be provided'))->during('getFeed', [['user_guid' => '100']]);
}
function it_should_get_the_suggested_feed(Activity $activity, \PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([
100,
'activity',
1,
12,
0
])
->shouldBeCalled();
$statement->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([$activity]);
$this->getFeed(['user_guid' => 100, 'type' => 'activity'])->shouldReturn([$activity]);
}
function it_should_get_the_suggested_feed_filtering_by_hashtag(Activity $activity, \PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([
'hashtag',
'activity',
1,
12,
0
])
->shouldBeCalled();
$statement->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([$activity]);
$this->getFeed(['user_guid' => 100, 'type' => 'activity', 'hashtag' => 'hashtag'])->shouldReturn([$activity]);
}
function it_should_get_the_suggested_feed_filtering_by_capitalized_hashtag(Activity $activity, \PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([
'hashtag',
'activity',
1,
12,
0
])
->shouldBeCalled();
$statement->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([$activity]);
$this->getFeed(['user_guid' => 100, 'type' => 'activity', 'hashtag' => 'HaShTag'])->shouldReturn([$activity]);
}
function it_should_get_the_suggested_feed_ignoring_user_selected_hashtags(
Activity $activity,
\PDOStatement $statement
) {
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([
'activity',
1,
12,
0
])
->shouldBeCalled();
$statement->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([$activity]);
$this->getFeed(['user_guid' => 100, 'type' => 'activity', 'all' => true])->shouldReturn([$activity]);
}
function it_should_fail_to_add_add_an_entry_to_suggested_if_entity_guid_isnt_set()
{
$this->shouldThrow(new \Exception('entity_guid must be provided'))->during('add',
[['score' => 100, 'rating' => 1]]);
}
function it_should_fail_to_add_add_an_entry_to_suggested_if_score_isnt_set()
{
$this->shouldThrow(new \Exception('score must be provided'))->during('add',
[['entity_guid' => 100, 'rating' => 1]]);
}
function it_should_fail_to_add_add_an_entry_to_suggested_if_type_isnt_set()
{
$this->shouldThrow(new \Exception('type must be provided'))->during('add',
[['entity_guid' => 100, 'score' => 100]]);
}
function it_should_fail_to_add_add_an_entry_to_suggested_if_rating_isnt_set()
{
$this->shouldThrow(new \Exception('rating must be provided'))->during('add',
[['entity_guid' => 100, 'score' => 100, 'type' => 'image']]);
}
function it_should_add_an_entry_to_suggested(\PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute(Argument::any())
->shouldBeCalled()
->willReturn(true);
$this->add(['entity_guid' => 100, 'score' => 100, 'rating' => 1, 'type' => 'image'])->shouldReturn(true);
}
function it_should_fail_to_remove_from_suggested_table_if_type_isnt_set()
{
$this->shouldThrow(new \Exception('type must be provided'))->during('removeAll', [null]);
}
function it_should_truncate_suggested_table(\PDOStatement $statement)
{
$this->db->prepare("TRUNCATE suggested")
->shouldBeCalled()
->willReturn($statement);
$statement->execute()
->shouldBeCalled()
->willReturn(true);
$this->removeAll('all')->shouldReturn(true);
}
function it_should_delete_all_entities_with_a_specific_type_from_the_suggested_table(
\PDOStatement $statement1,
\PDOStatement $statement2
) {
$this->db->prepare("SELECT suggested.guid AS guid
FROM suggested
JOIN entity_hashtags
ON suggested.guid = entity_hashtags.guid
WHERE suggested.type = ?")
->shouldBeCalled()
->willReturn($statement1);
$statement1->execute(['activity'])
->shouldBeCalled();
$statement1->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([['guid' => 1], ['guid' => 2]]);
$this->db->prepare("DELETE FROM suggested WHERE guid IN (?,?)")
->shouldBeCalled()
->willReturn($statement2);
$statement2->execute([1, 2])
->shouldBeCalled()
->willReturn(true);
$this->removeAll('activity')->shouldReturn(true);
}
function it_should_remove_a_key(\PDOStatement $statement) {
$this->db->prepare("DELETE FROM suggested WHERE guid = ?")
->shouldBeCalled()
->willReturn($statement);
$statement->execute([ 123123 ])
->shouldBeCalled()
->willReturn(true);
$this->remove('key', 123123)
->shouldReturn(true);
}
}
<?php
namespace Spec\Minds\Core\Hashtags\Entity;
use Minds\Core\Hashtags\Entity\Repository;
use Minds\Core\Hashtags\HashtagEntity;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
{
private $db;
function let(\PDO $db)
{
$this->db = $db;
$this->beConstructedWith($db);
}
function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
}
function it_should_throw_an_exception_when_getting_hashtags_if_entity_guid_isnt_set()
{
$this->shouldThrow(new \Exception('entity_guid must be provided'))->during('getAll');
}
function it_should_get_all_hashtags(\PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([100])
->shouldBeCalled();
$statement->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([['guid' => 1, 'hashtag' => 'hashtag1']]);
$this->getAll(['entity_guid' => 100])->shouldReturn([['guid' => 1, 'hashtag' => 'hashtag1']]);
}
function it_should_add_an_entity_hashtag(\PDOStatement $statement, HashtagEntity $hashtag)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([100, 'hashtag1'])
->shouldBeCalled()
->willReturn(true);
$hashtag->getGuid()
->shouldBeCalled()
->willReturn(100);
$hashtag->getHashtag()
->shouldBeCalled()
->willReturn('hashtag1');
$this->add([$hashtag])->shouldReturn(true);
}
function it_should_try_to_add_any_hashtags_if_array_is_empty()
{
$this->add([])->shouldReturn(false);
}
}
<?php
namespace Spec\Minds\Core\Hashtags\Suggested;
use Minds\Core\Hashtags\Suggested\Repository;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
{
protected $db;
function let(\PDO $db)
{
$this->db = $db;
$this->beConstructedWith($db);
}
function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
}
function it_should_throw_an_exception_when_getting_the_suggested_hashtags_if_user_guid_isnt_set()
{
$this->shouldThrow(new \Exception('user_guid must be provided'))->during('getAll');
}
function it_should_get_the_suggested_hashtags(\PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([date('c', strtotime('24 hours ago')), 100])
->shouldBeCalled();
$statement->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([
['value' => 'hashtag1', 'selected' => true],
['value' => 'hashtag2', 'selected' => false]
]);
$this->getAll(['user_guid' => 100])->shouldReturn([
['value' => 'hashtag1', 'selected' => true],
['value' => 'hashtag2', 'selected' => false]
]);
}
}
<?php
namespace Spec\Minds\Core\Hashtags\User;
use Minds\Core\Data\cache\Redis;
use Minds\Core\Hashtags\HashtagEntity;
use Minds\Core\Hashtags\User\LegacyRepository;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class LegacyRepositorySpec extends ObjectBehavior
{
private $db;
private $cacher;
function let(\PDO $db, Redis $cacher)
{
$this->db = $db;
$this->cacher = $cacher;
$this->beConstructedWith($db, $cacher);
}
function it_is_initializable()
{
$this->shouldHaveType(LegacyRepository::class);
}
function it_should_throw_an_exception_when_getting_hashtags_if_user_guid_isnt_set()
{
$this->shouldThrow(new \Exception('user_guid must be provided'))->during('getAll');
}
function it_should_get_all_hashtags(\PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([100])
->shouldBeCalled();
$statement->fetchAll(\PDO::FETCH_ASSOC)
->shouldBeCalled()
->willReturn([['hashtag' => 'hashtag1']]);
$this->getAll(['user_guid' => 100])->shouldReturn([['hashtag' => 'hashtag1']]);
}
function it_should_add_a_user_hashtag(\PDOStatement $statement, HashtagEntity $hashtag)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$statement->execute([100, 'hashtag1'])
->shouldBeCalled()
->willReturn(true);
$this->cacher->destroy("user-selected-hashtags:100")
->shouldBeCalled();
$hashtag->getGuid()
->shouldBeCalled()
->willReturn(100);
$hashtag->getHashtag()
->shouldBeCalled()
->willReturn('hashtag1');
$this->add([$hashtag])->shouldReturn(true);
}
function it_should_try_to_add_any_hashtags_if_array_is_empty()
{
$this->add([])->shouldReturn(false);
}
function it_should_remove_a_user_hashtag(\PDOStatement $statement)
{
$this->db->prepare(Argument::any())
->shouldBeCalled()
->willReturn($statement);
$this->cacher->destroy("user-selected-hashtags:100")
->shouldBeCalled();
$statement->execute([100, 'hashtag1'])
->shouldBeCalled()
->willReturn(true);
$this->remove(100, ['hashtag1'])->shouldReturn(true);
}
}
......@@ -95,7 +95,7 @@ class CassandraRepositorySpec extends ObjectBehavior
->willReturn(null);
$this->add($notification)
->shouldReturn(true);
->shouldNotReturn(false);
}
function it_should_load_notification_for_user()
......
......@@ -9,6 +9,7 @@ use Minds\Core\Notification\Notification;
use Minds\Core\Notification\Repository;
use Minds\Core\Notification\CassandraRepository;
use Minds\Core\Features\Manager as FeaturesManager;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -46,9 +47,13 @@ class ManagerSpec extends ObjectBehavior
$this->shouldHaveType(Manager::class);
}
function it_should_get_a_single_notification(Notification $notification)
function it_should_get_a_single_notification(Notification $notification, User $user)
{
$this->repository->get('1234')
$this->setUser($user);
$user->getGUID()
->willReturn(456);
$this->cassandraRepository->get('urn:notification:456-1234')
->shouldBeCalled()
->willReturn($notification);
......
......@@ -6,6 +6,8 @@ use Minds\Common\Repository\Response;
use Minds\Core\Referrals\Referral;
use Minds\Core\Referrals\Repository;
use Minds\Core\Data\Cassandra\Client;
use Cassandra\Bigint;
use Cassandra\Timestamp;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -25,18 +27,6 @@ class RepositorySpec extends ObjectBehavior
$this->shouldHaveType(Repository::class);
}
// function it_should_insert_a_new_referral()
// {
// $referral = new Referral();
// $referral->setReferrerGuid(123)
// ->setProspectGuid(456)
// ->setRegisterimestamp(789);
// $this
// ->add($referral)
// ->shouldReturn(true);
// }
function it_should_add_a_referral(Referral $referral)
{
$referral->getReferrerGuid()
......@@ -65,69 +55,56 @@ class RepositorySpec extends ObjectBehavior
$this->add($referral)
->shouldBe(true);
}
// function it_should_get_a_list_of_referrals()
// {
// $this->client->request(Argument::that(function($prepared) {
// $query = $prepared->build();
// $values = $query['values'];
// return $values[0] === 'reported';
// }))
// ->shouldBeCalled()
// ->willReturn([
// [
// 'user_hashes' => (new Set(Type::text()))
// ->add('hash'),
// 'entity_urn' => 'urn:activity:123',
// 'entity_owner_guid' => new Bigint(456),
// 'reason_code' => new Float_(2),
// 'sub_reason_code' => new Float_(5),
// 'timestamp' => new Timestamp(time() * 1000),
// 'state' => 'reported',
// 'state_changes' => (new Map(Type::text(), Type::timestamp()))
// ->set('reported', time() * 1000),
// 'reports' => (new Set(Type::bigint()))
// ->add(789),
// ],
// [
// 'user_hashes' => (new Set(Type::text()))
// ->add('hash'),
// 'entity_urn' => 'urn:activity:456',
// 'entity_owner_guid' => new Bigint(456),
// 'reason_code' => new Float_(2),
// 'sub_reason_code' => new Float_(5),
// 'timestamp' => new Timestamp(time() * 1000),
// 'state' => 'reported',
// 'state_changes' => (new Map(Type::text(), Type::timestamp()))
// ->set('reported', time() * 1000),
// 'reports' => (new Set(Type::bigint()))
// ->add(789),
// ],
// ]);
// $response = $this->getList([
// 'user' => $user,
// 'juryType' => 'initial',
// ]);
// $response->shouldHaveCount(2);
// }
// function it_should_update_a_referral()
// {
// $referral = new Referral();
// $referral->setReferrerGuid(123)
// ->setProspectGuid(456)
// ->setJoinTimestamp(789);
function it_should_update_a_referral()
{
$referral = new Referral();
$referral->setReferrerGuid(123)
->setProspectGuid(456)
->setJoinTimestamp(789);
// $this
// ->update($referral)
// ->shouldReturn(true);
$this
->update($referral)
->shouldReturn(true);
}
// function it_should_return_a_list_of_referrals()
// {
// $this->client->request(Argument::that(function($prepared) {
// return true;
// }))
// ->shouldBeCalled()
// ->willReturn([
// [
// 'referrer_guid' => new Bigint(123),
// 'prospect_guid' => new Bigint(456),
// 'register_timestamp' => new Timestamp(1545451597777),
// 'join_timestamp' => new Timestamp(1545451597778),
// ],
// [
// 'referrer_guid' => new Bigint(123),
// 'prospect_guid' => new Bigint(567),
// 'register_timestamp' => new Timestamp(1545451598888),
// 'join_timestamp' => new Timestamp(1545451598889),
// ],
// ]);
// $response = $this->getList([
// 'referrer_guid' => 123,
// ]);
// $response->shouldHaveCount(2);
// $response[0]->getProspectGuid()
// ->shouldBe(456);
// $response[0]->getRegisterTimestamp()
// ->shouldBe(1545451597777);
// $response[0]->getJoinTimestamp()
// ->shouldBe(1545451597778);
// // err:Error("Call to a member function pagingStateToken() on array")
// }
function it_should_throw_if_no_referrer_guid_during_get_list()
{
$opts = [
......