Commit 691dc667 authored by Mark Harding's avatar Mark Harding

(feat): implement controller and further spec tests

1 merge request!327WIP: Subscription Requests - #604
Pipeline #82633426 running with stages
......@@ -14,10 +14,24 @@ class incoming implements Interfaces\Api
{
public function get($pages)
{
// Return a list of subscriptions
// Return a single request
$manager = Di::_()->get('Subscriptions\Requests\Manager');
// Construct URN on the fly
$subscriberGuid = $pages[0];
$urn = "urn:subscription-request:" . implode('-', [ Session::getLoggedInUserGuid(), $subscriberGuid ]);
$request = $manager->get($urn);
return Factory::response([
if (!$request || $request->getPublisherGuid() != Session::getLoggedInUserGuid()) {
return Factory::response([
'status' => 'error',
'message' => 'Not found',
]);
}
return Factory::response([
'request' => $request->export(),
]);
}
......@@ -30,6 +44,37 @@ class incoming implements Interfaces\Api
public function put($pages)
{
// Accept / Deny
$manager = Di::_()->get('Subscriptions\Requests\Manager');
// Construct URN on the fly
$subscriberGuid = $pages[0];
$urn = "urn:subscription-request:" . implode('-', [ Session::getLoggedInUserGuid(), $subscriberGuid ]);
$request = $manager->get($urn);
if (!$request || $request->getPublisherGuid() != Session::getLoggedInUserGuid()) {
return Factory::response([
'status' => 'error',
'message' => 'Not found',
]);
}
try {
switch ($pages[1]) {
case "accept":
$manager->accept($request);
break;
case "decline":
$manager->decline($request);
break;
}
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
return Factory::response([]);
}
......
<?php
namespace Minds\Controllers\api\v2\subscriptions\incoming;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Interfaces;
/**
* Incoming subscritions
*/
class all implements Interfaces\Api
{
public function get($pages)
{
// Return a list of subscription requests
$manager = Di::_()->get('Subscriptions\Requests\Manager');
$requests = $manager->getIncomingRequests(Session::getLoggedInUserGuid(), []);
return Factory::response([
'requests' => Factory::exportable($requests),
'next' => $requests->getPagingToken(),
]);
}
public function post($pages)
{
// Void
return Factory::response([]);
}
public function put($pages)
{
// Void
return Factory::response([]);
}
public function delete($pages)
{
// Void
return Factory::response([]);
}
}
......@@ -5,6 +5,7 @@ namespace Minds\Controllers\api\v2\subscriptions;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Entities\Factory as EntitiesFactory;
use Minds\Interfaces;
/**
......@@ -14,10 +15,24 @@ class outgoing implements Interfaces\Api
{
public function get($pages)
{
// Return a list of subscriptions we made
// Return a single request
$manager = Di::_()->get('Subscriptions\Requests\Manager');
// Construct URN on the fly
$publisherGuid = $pages[0];
$urn = "urn:subscription-request:" . implode('-', [ $publisherGuid, Session::getLoggedInUserGuid() ]);
$request = $manager->get($urn);
return Factory::response([
if (!$request || $request->getSubscriberGuid() != Session::getLoggedInUserGuid()) {
return Factory::response([
'status' => 'error',
'message' => 'Not found',
]);
}
return Factory::response([
'request' => $request->export(),
]);
}
......@@ -30,6 +45,21 @@ class outgoing implements Interfaces\Api
public function put($pages)
{
// Make a subscription request
$manager = Di::_()->get('Subscriptions\Requests\Manager');
$request = new SubscriptionRequest();
$request->setPublisherGuid($pages[0])
->setSubscriberGuid(Session::getLoggedInGuid());
try {
$manager->add($request);
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
return Factory::response([]);
}
......
<?php
namespace Minds\Controllers\api\v2\subscriptions\outgoing;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Interfaces;
/**
* Outgoing subscritions
*/
class all implements Interfaces\Api
{
public function get($pages)
{
// Todo
return Factory::response([
]);
}
public function post($pages)
{
// Void
return Factory::response([]);
}
public function put($pages)
{
// Make a subscription request
return Factory::response([]);
}
public function delete($pages)
{
// Delete a subscription request
return Factory::response([]);
}
}
......@@ -1446,4 +1446,12 @@ CREATE TABLE minds.email_campaign_logs (
PRIMARY KEY (receiver_guid, time_sent)
) WITH CLUSTERING ORDER BY (time_sent desc);
ALTER TABLE minds.views ADD owner_guid text;
\ No newline at end of file
ALTER TABLE minds.views ADD owner_guid text;
CREATE TABLE minds.subscription_requests (
publisher_guid bigint,
subscriber_guid bigint,
timestamp timestamp,
accepted boolean,
PRIMARY KEY (publisher_guid, subscriber_guid)
);
\ No newline at end of file
......@@ -14,5 +14,8 @@ class Provider extends DiProvider
$this->di->bind('Subscriptions\Manager', function ($di) {
return new Manager();
}, [ 'useFactory'=>false ]);
$this->di->bind('Subscriptions\Requests\Manager', function ($di) {
return new Requests\Manager();
}, [ 'useFactory'=>false ]);
}
}
......@@ -6,7 +6,10 @@ namespace Minds\Core\Subscriptions\Requests;
use Minds\Core\Subscriptions\Requests\Delegates\NotificationsDelegate;
use Minds\Core\Subscriptions\Requests\Delegates\SubscriptionsDelegate;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Di\Di;
use Minds\Common\Repository\Response;
use Minds\Entities\User;
class Manager
{
......@@ -19,11 +22,15 @@ class Manager
/** @var SubscriptionsDelegate */
private $subscriptionsDelegate;
public function __construct($repository = null, $notificationsDelegate = null, $subscriptionsDelegate = null)
/** @var EntitiesBuilder */
private $entitiesBuilder;
public function __construct($repository = null, $notificationsDelegate = null, $subscriptionsDelegate = null, $entitiesBuilder = null)
{
$this->repository = $repository ?? new Repository();
$this->notificationsDelegate = $notificationsDelegate ?? new NotificationsDelegate;
$this->subscriptionsDelegate = $subscriptionsDelegate ?? new SubscriptionsDelegate;
$this->entitiesBuilder = $entitiesBuilder ?? Di::_()->get('EntitiesBuilder');
}
/**
......@@ -31,8 +38,9 @@ class Manager
* @param array $opts
* @return Response
*/
public function getIncomingList(array $opts = [])
public function getIncomingList($user_guid, array $opts = [])
{
$opts['publisher_guid'] = $user_guid;
$response = $this->repository->getList($opts);
return $response;
}
......@@ -60,6 +68,12 @@ class Manager
throw new SubscriptionRequestExistsException();
}
// Check if the user exists
$publisher = $this->entitiesBuilder->single($subscriptionRequest->getPublisherGuid());
if (!$publisher || !$publisher instanceof User) {
throw new SubscriptionRequestChannelDoesntExist();
}
$this->repository->add($subscriptionRequest);
$this->notificationsDelegate->onAdd($subscriptionRequest);
......
......@@ -6,7 +6,12 @@ namespace Minds\Core\Subscriptions\Requests;
use Minds\Core\Di\Di;
use Minds\Core\Data\Cassandra\Client;
use Minds\Core\Data\Cassandra\Prepared;
use Minds\Common\Repository\Response;
use Minds\Common\Urn;
use Cassandra\Timestamp;
use Cassandra\Bigint;
use Cassandra\Boolean;
class Repository
{
......@@ -25,6 +30,16 @@ class Repository
*/
public function getList(array $opts = []): Response
{
$opts = array_merge([
'publisher_guid' => null,
'limit' => 5000,
'token' => '',
], $opts);
if (!$opts['publisher_guid']) {
throw new \Exception('publisher_guid not set');
}
$prepared = new Prepared\Custom();
$prepared->query(
"SELECT * FROM subscription_requests
......@@ -41,9 +56,13 @@ class Repository
$subscriptionRequest
->setPublisherGuid((string) $row['publisher_guid'])
->setSubscriberGuid((string) $row['subscriber_guid'])
->setAccepted((bool) $row['accepted'])
->setTimestampMs((int) $row['timestamp']->time());
$result[] = $subscriptionRequest;
if ($row['accepted']) {
$subscriptionRequest->setAccepted((bool) $row['accepted']);
}
$response[] = $subscriptionRequest;
}
return $response;
......@@ -81,9 +100,12 @@ class Repository
$subscriptionRequest
->setPublisherGuid((string) $row['publisher_guid'])
->setSubscriberGuid((string) $row['subscriber_guid'])
->setAccepted((bool) $row['accepted'])
->setTimestampMs((int) $row['timestamp']->time());
if ($row['accepted']) {
$subscriptionRequest->setAccepted((bool) $row['accepted']);
}
return $subscriptionRequest;
}
......@@ -94,14 +116,18 @@ class Repository
*/
public function add(SubscriptionRequest $subscriptionRequest): bool
{
$statement = "INSERT INTO subscription_requests (publisher_guid, subscriber_guid, timestamp) VALUES (?, ?, ?)";
$statement = "INSERT INTO subscription_requests
(publisher_guid, subscriber_guid, timestamp)
VALUES
(?, ?, ?)
IF NOT EXISTS";
$values = [
new Bigint($subscriptionRequest->getPublisherGuid()),
new Bigint($subscriptionRequest->getSubscriberGuid()),
new Timestamp($subscriptionRequest->getTimestamp() ?? round(microtime(true) * 1000)),
new Timestamp($subscriptionRequest->getTimestampMs() ?? round(microtime(true) * 1000)),
];
$prepared = new Custom\Prepared();
$prepared = new Prepared\Custom();
$prepared->query($statement, $values);
return (bool) $this->db->request($prepared);
......@@ -115,14 +141,17 @@ class Repository
*/
public function update(SubscriptionRequest $subscriptionRequest, array $field = []): bool
{
$statement = "INSERT INTO subscription_requests (publisher_guid, subscriber_guid, timestamp) VALUES (?, ?, ?)";
$statement = "UPDATE subscription_requests
SET accepted = ?
WHERE publisher_guid = ?
AND subscriber_guid = ?";
$values = [
new Boolean($subscriptionRequest->getAccepted()),
new Bigint($subscriptionRequest->getPublisherGuid()),
new Bigint($subscriptionRequest->getSubscriberGuid()),
new Timestamp($subscriptionRequest->getTimestamp() ?? round(microtime(true) * 1000)),
];
$prepared = new Custom\Prepared();
$prepared = new Prepared\Custom();
$prepared->query($statement, $values);
return (bool) $this->db->request($prepared);
......@@ -143,7 +172,7 @@ class Repository
new Bigint($subscriptionRequest->getSubscriberGuid()),
];
$prepared = new Custom\Prepared();
$prepared = new Prepared\Custom();
$prepared->query($statement, $values);
return (bool) $this->db->request($prepared);
......
<?php
namespace Minds\Core\Subscriptions\Requests;
use Exception;
class SubscriptionRequestAlreadyCompletedException extends Exception
{
/** @var string */
protected $message = "A subscription request was accepted/declined but has already been actioned.";
}
<?php
namespace Minds\Core\Subscriptions\Requests;
use Exception;
class SubscriptionRequestChannelDoesntExist extends Exception
{
/** @var string */
protected $message = "A subscription request was made to a channel that doesnt exist";
}
<?php
namespace Minds\Core\Subscriptions\Requests;
use Exception;
class SubscriptionRequestDoesntExistException extends Exception
{
/** @var string */
protected $message = "A subscription request currently does not exist, but was interacted with";
}
<?php
namespace Minds\Core\Subscriptions\Requests;
use Exception;
class SubscriptionRequestExistsException extends Exception
{
/** @var string */
protected $message = "A subscription request already exists but tried to be created.";
}
......@@ -6,6 +6,8 @@ use Minds\Core\Subscriptions\Requests\Manager;
use Minds\Core\Subscriptions\Requests\Repository;
use Minds\Core\Subscriptions\Requests\SubscriptionRequest;
use Minds\Core\Subscriptions\Requests\Delegates;
use Minds\Core\EntitiesBuilder;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -14,16 +16,19 @@ class ManagerSpec extends ObjectBehavior
private $repository;
private $notificationsDelegate;
private $subscriptionsDelegate;
private $entitiesBuilder;
public function let(
Repository $repository,
Delegates\NotificationsDelegate $notificationsDelegate,
Delegates\SubscriptionsDelegate $subscriptionsDelegate
Delegates\SubscriptionsDelegate $subscriptionsDelegate,
EntitiesBuilder $entitiesBuilder
) {
$this->beConstructedWith($repository, $notificationsDelegate, $subscriptionsDelegate);
$this->beConstructedWith($repository, $notificationsDelegate, $subscriptionsDelegate, $entitiesBuilder);
$this->repository = $repository;
$this->notificationsDelegate = $notificationsDelegate;
$this->subscriptionsDelegate = $subscriptionsDelegate;
$this->entitiesBuilder = $entitiesBuilder;
}
public function it_is_initializable()
......@@ -36,6 +41,9 @@ class ManagerSpec extends ObjectBehavior
$subscriptionRequest = new SubscriptionRequest();
$subscriptionRequest->setPublisherGuid(123)
->setSubscriberGuid(456);
$this->entitiesBuilder->single(123)
->willReturn(new User);
$this->repository->get("urn:subscription-request:123-456")
->willReturn(null);
......
......@@ -3,13 +3,141 @@
namespace Spec\Minds\Core\Subscriptions\Requests;
use Minds\Core\Subscriptions\Requests\Repository;
use Minds\Core\Subscriptions\Requests\SubscriptionRequest;
use Minds\Core\Data\Cassandra\Client;
use Spec\Minds\Mocks\Cassandra\Rows;
use Cassandra\Timestamp;
use Cassandra\Bigint;
use Cassandra\Boolean;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
{
private $db;
public function let(Client $db)
{
$this->beConstructedWith($db);
$this->db = $db;
}
public function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
}
public function it_should_get_a_request_from_a_urn()
{
$this->db->request(Argument::that(function ($prepared) {
$query = $prepared->build();
return $query['values'][0] === '123'
&& $query['values'][1] === '456';
}))
->willReturn(new Rows([
[
'publisher_guid' => '123',
'subscriber_guid' => '456',
'timestamp' => new Timestamp(time()),
'accepted' => null,
]
], 'next-page-token'));
$subscriptionRequest = $this->get("urn:subscription-request:123-456");
$subscriptionRequest->getPublisherGuid()
->shouldBe('123');
$subscriptionRequest->getSubscriberGuid()
->shouldBe('456');
}
public function it_should_get_a_list_of_requests()
{
$this->db->request(Argument::that(function ($prepared) {
$query = $prepared->build();
return $query['values'][0] === '123';
}))
->willReturn(new Rows([
[
'publisher_guid' => new Bigint(123),
'subscriber_guid' => new Bigint(456),
'timestamp' => new Timestamp(time()),
'accepted' => null,
],
[
'publisher_guid' => new Bigint('1789'),
'subscriber_guid' => new Bigint('1123'),
'timestamp' => new Timestamp(time()),
'accepted' => new Boolean(true),
]
], 'next-page-token'));
$response = $this->getList([
'publisher_guid' => '123',
]);
$response[0]->getPublisherGuid()
->shouldBe('123');
$response[0]->getSubscriberGuid()
->shouldBe('456');
$response[1]->getPublisherGuid()
->shouldBe('1789');
$response[1]->getSubscriberGuid()
->shouldBe('1123');
$response[1]->isAccepted()
->shouldBe(true);
}
public function it_should_add_to_repository()
{
$subscriptionRequest = new SubscriptionRequest();
$subscriptionRequest->setPublisherGuid('123')
->setSubscriberGuid('456')
->setTimestampMs(1568711904123);
$this->db->request(Argument::that(function ($prepared) {
$values = $prepared->build()['values'];
return $values[0]->value() === '123'
&& $values[1]->value() === '456'
&& $values[2]->time() === 1568711904123;
}))
->willReturn(true);
$this->add($subscriptionRequest)
->shouldReturn(true);
}
public function it_should_update_a_request()
{
$subscriptionRequest = new SubscriptionRequest();
$subscriptionRequest->setPublisherGuid('123')
->setSubscriberGuid('456')
->setAccepted(true);
$this->db->request(Argument::that(function ($prepared) {
$values = $prepared->build()['values'];
return $values[0]->value() == true
&& $values[1]->value() === '123'
&& $values[2]->value() === '456';
}))
->willReturn(true);
$this->update($subscriptionRequest)
->shouldReturn(true);
}
public function it_should_delete_a_request()
{
$subscriptionRequest = new SubscriptionRequest();
$subscriptionRequest->setPublisherGuid('123')
->setSubscriberGuid('456');
$this->db->request(Argument::that(function ($prepared) {
$values = $prepared->build()['values'];
return $values[0]->value() === '123'
&& $values[1]->value() === '456';
}))
->willReturn(true);
$this->delete($subscriptionRequest)
->shouldReturn(true);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment