...
 
Commits (2)
<?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 [];
}
}
......@@ -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' => '',
]);
}
......
<?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 ]);
}
}
<?php
namespace Minds\Core\Hashtags\Entity;
use Cassandra\Varint;
use Minds\Core\Data\Cassandra\Client as CassandraClient;
use Minds\Core\Data\Cassandra\Prepared\Custom;
use Minds\Core\Di\Di;
use Minds\Core\Hashtags\HashtagEntity;
class Repository
{
/** @var CassandraClient */
protected $db;
/**
* Repository constructor.
* @param null $db
*/
public function __construct($db = null)
{
$this->db = $db ?: Di::_()->get('Database\Cassandra\Cql');
}
/**
* Return all hashtags
* @param array $opts
* @return array
* @throws \Exception
*/
public function getAll(array $opts = [])
{
$opts = array_merge([
'entity_guid' => null
], $opts);
if (!$opts['entity_guid']) {
throw new \Exception('entity_guid must be provided');
}
$cql = "SELECT * FROM entity_hashtags WHERE guid = ?";
$values = [
new Varint($opts['entity_guid']),
];
$prepared = new Custom();
$prepared->query($cql, $values);
$rows = $this->db->request($prepared);
$response = [];
foreach ($rows as $row) {
$response[] = $row;
}
return $response;
}
/**
* @param HashtagEntity[] $hashtags
* @return bool
*/
public function add($hashtags)
{
$success = true;
$cql = "INSERT INTO entity_hashtags (guid, hashtag) VALUES (?, ?)";
foreach ($hashtags as $hashtag) {
if (!$hashtag->getHashtag()) {
continue;
}
$values = [
new Varint($hashtag->getGuid()),
(string) $hashtag->getHashtag(),
];
$prepared = new Custom();
$prepared->query($cql, $values);
try {
$success = $success && (bool) $this->db->request($prepared, true);
} catch (\Exception $e) {
error_log($e->getMessage());
$success = false;
}
}
return $success;
}
}
......@@ -17,10 +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\Trending\Repository', function ($di) {
return new Trending\Repository();
});
......
......@@ -1414,9 +1414,3 @@ CREATE TABLE minds.hidden_hashtags (
hidden_since timestamp,
PRIMARY KEY (hashtag)
);
CREATE TABLE minds.entity_hashtags (
guid varint,
hashtag text,
PRIMARY KEY (guid, hashtag)
) WITH CLUSTERING ORDER BY (hashtag ASC);
......@@ -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,
......
<?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\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()
......