SAAS-1063: REST API for podcasts
Hierarchy routing is working Basic implentation of podcast INDEX and POST actions done
This commit is contained in:
parent
a444751397
commit
67db2c1d25
9 changed files with 391 additions and 9 deletions
|
@ -35,6 +35,8 @@ $ccAcl->add(new Zend_Acl_Resource('library'))
|
||||||
->add(new Zend_Acl_Resource('downgrade'))
|
->add(new Zend_Acl_Resource('downgrade'))
|
||||||
->add(new Zend_Acl_Resource('rest:media'))
|
->add(new Zend_Acl_Resource('rest:media'))
|
||||||
->add(new Zend_Acl_Resource('rest:show-image'))
|
->add(new Zend_Acl_Resource('rest:show-image'))
|
||||||
|
->add(new Zend_Acl_Resource('rest:podcast'))
|
||||||
|
->add(new Zend_Acl_Resource('rest:podcast-episodes'))
|
||||||
->add(new Zend_Acl_Resource('billing'))
|
->add(new Zend_Acl_Resource('billing'))
|
||||||
->add(new Zend_Acl_Resource('thank-you'))
|
->add(new Zend_Acl_Resource('thank-you'))
|
||||||
->add(new Zend_Acl_Resource('provisioning'))
|
->add(new Zend_Acl_Resource('provisioning'))
|
||||||
|
@ -61,6 +63,8 @@ $ccAcl->allow('G', 'index')
|
||||||
->allow('G', 'downgrade')
|
->allow('G', 'downgrade')
|
||||||
->allow('G', 'rest:show-image', 'get')
|
->allow('G', 'rest:show-image', 'get')
|
||||||
->allow('G', 'rest:media', 'get')
|
->allow('G', 'rest:media', 'get')
|
||||||
|
->allow('G', 'rest:podcast', 'get')
|
||||||
|
->allow('G', 'rest:podcast-episodes', 'get')
|
||||||
->allow('G', 'setup')
|
->allow('G', 'setup')
|
||||||
->allow('G', 'embeddablewidgets')
|
->allow('G', 'embeddablewidgets')
|
||||||
->allow('H', 'soundcloud')
|
->allow('H', 'soundcloud')
|
||||||
|
|
|
@ -121,3 +121,7 @@ define('CELERY_FAILED_STATUS', 'FAILED');
|
||||||
|
|
||||||
// Celery Services
|
// Celery Services
|
||||||
define('SOUNDCLOUD_SERVICE_NAME', 'soundcloud');
|
define('SOUNDCLOUD_SERVICE_NAME', 'soundcloud');
|
||||||
|
|
||||||
|
// Podcast Types
|
||||||
|
define('STATION_PODCAST', 0);
|
||||||
|
define('IMPORTED_PODCAST', 1);
|
||||||
|
|
|
@ -1,18 +1,77 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
class PodcastLimitReachedException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidPodcastException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skeleton subclass for representing a row from the 'podcast' table.
|
* Skeleton subclass for representing a row from the 'podcast' table.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
*
|
|
||||||
* You should add additional methods to this class to meet the
|
|
||||||
* application requirements. This class will only be generated as
|
|
||||||
* long as it does not already exist in the output directory.
|
|
||||||
*
|
|
||||||
* @package propel.generator.airtime
|
* @package propel.generator.airtime
|
||||||
*/
|
*/
|
||||||
class Podcast extends BasePodcast
|
class Podcast extends BasePodcast
|
||||||
{
|
{
|
||||||
|
/** Creates a Podcast object from an array containing metadata.
|
||||||
|
* This is used by our Podcast REST API
|
||||||
|
* @param $podcastArray An array containing metadata for a Podcast object.
|
||||||
|
*
|
||||||
|
* @return Podcast Array
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function create($podcastArray)
|
||||||
|
{
|
||||||
|
if (Application_Service_PodcastService::podcastLimitReached()) {
|
||||||
|
throw new PodcastLimitReachedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO are we implementing this here??
|
||||||
|
if (!Application_Service_PodcastService::validatePodcastUrl($podcastArray["url"])) {
|
||||||
|
throw new InvalidPodcastException();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$podcast = new Podcast();
|
||||||
|
$podcast->fromArray($podcastArray, BasePeer::TYPE_FIELDNAME);
|
||||||
|
$podcast->setDbOwner(self::getOwnerId());
|
||||||
|
$podcast->setDbType(IMPORTED_PODCAST);
|
||||||
|
$podcast->save();
|
||||||
|
|
||||||
|
//TODO: fetch podcast episodes and return with podcast object
|
||||||
|
|
||||||
|
return $podcast->toArray(BasePeer::TYPE_FIELDNAME);
|
||||||
|
} catch(Exception $e) {
|
||||||
|
$podcast->delete();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO move this somewhere where it makes sense
|
||||||
|
private static function getOwnerId()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (Zend_Auth::getInstance()->hasIdentity()) {
|
||||||
|
$service_user = new Application_Service_UserService();
|
||||||
|
return $service_user->getCurrentUser()->getDbId();
|
||||||
|
} else {
|
||||||
|
$defaultOwner = CcSubjsQuery::create()
|
||||||
|
->filterByDbType('A')
|
||||||
|
->orderByDbId()
|
||||||
|
->findOne();
|
||||||
|
if (!$defaultOwner) {
|
||||||
|
// what to do if there is no admin user?
|
||||||
|
// should we handle this case?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $defaultOwner->getDbId();
|
||||||
|
}
|
||||||
|
} catch(Exception $e) {
|
||||||
|
Logging::info($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
require_once 'RouteController.php';
|
||||||
|
|
||||||
class Rest_Bootstrap extends Zend_Application_Module_Bootstrap
|
class Rest_Bootstrap extends Zend_Application_Module_Bootstrap
|
||||||
{
|
{
|
||||||
protected function _initRouter()
|
protected function _initRouter()
|
||||||
|
@ -8,9 +10,35 @@ class Rest_Bootstrap extends Zend_Application_Module_Bootstrap
|
||||||
$router = $front->getRouter();
|
$router = $front->getRouter();
|
||||||
|
|
||||||
$restRoute = new Zend_Rest_Route($front, array(), array(
|
$restRoute = new Zend_Rest_Route($front, array(), array(
|
||||||
'rest'=> array('media', 'show-image')));
|
'rest'=> array('media', 'show-image', 'podcast', 'podcast-episodes')));
|
||||||
assert($router->addRoute('rest', $restRoute));
|
assert($router->addRoute('rest', $restRoute));
|
||||||
|
|
||||||
|
$route = new Rest_RouteController($front,
|
||||||
|
'rest/podcast/:id/episodes',
|
||||||
|
array(
|
||||||
|
'controller' => 'podcast-episodes',
|
||||||
|
'module' => 'rest'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'id' => '\d+'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$router->addRoute('podcast-episodes-index', $route);
|
||||||
|
|
||||||
|
$route = new Rest_RouteController($front,
|
||||||
|
'rest/podcast/:id/episodes/:episode_id',
|
||||||
|
array(
|
||||||
|
'controller' => 'podcast-episodes',
|
||||||
|
'module' => 'rest'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'id' => '\d+',
|
||||||
|
'episode_id' => '\d+'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$router->addRoute('podcast-episodes', $route);
|
||||||
|
|
||||||
|
|
||||||
/** MediaController Routes **/
|
/** MediaController Routes **/
|
||||||
$downloadRoute = new Zend_Controller_Router_Route(
|
$downloadRoute = new Zend_Controller_Router_Route(
|
||||||
'rest/media/:id/download',
|
'rest/media/:id/download',
|
||||||
|
|
|
@ -12,11 +12,39 @@ class Rest_PodcastController extends Zend_Rest_Controller
|
||||||
|
|
||||||
public function indexAction()
|
public function indexAction()
|
||||||
{
|
{
|
||||||
|
$totalPodcastCount = PodcastQuery::create()->count();
|
||||||
|
|
||||||
|
// Check if offset and limit were sent with request.
|
||||||
|
// Default limit to zero and offset to $totalFileCount
|
||||||
|
$offset = $this->_getParam('offset', 0);
|
||||||
|
$limit = $this->_getParam('limit', $totalPodcastCount);
|
||||||
|
|
||||||
|
//Sorting parameters
|
||||||
|
$sortColumn = $this->_getParam('sort', PodcastPeer::ID);
|
||||||
|
$sortDir = $this->_getParam('sort_dir', Criteria::ASC);
|
||||||
|
|
||||||
|
$query = PodcastQuery::create()
|
||||||
|
->setLimit($limit)
|
||||||
|
->setOffset($offset)
|
||||||
|
->orderBy($sortColumn, $sortDir);
|
||||||
|
|
||||||
|
$queryResult = $query->find();
|
||||||
|
|
||||||
|
$podcastArray = array();
|
||||||
|
foreach ($queryResult as $podcast)
|
||||||
|
{
|
||||||
|
array_push($podcastArray, $podcast->toArray(BasePeer::TYPE_FIELDNAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getResponse()
|
||||||
|
->setHttpResponseCode(200)
|
||||||
|
->setHeader('X-TOTAL-COUNT', $totalPodcastCount)
|
||||||
|
->appendBody(json_encode($podcastArray));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAction()
|
public function getAction()
|
||||||
{
|
{
|
||||||
|
Logging::info("podcasts get");
|
||||||
$id = $this->getId();
|
$id = $this->getId();
|
||||||
if (!$id) {
|
if (!$id) {
|
||||||
return;
|
return;
|
||||||
|
@ -32,17 +60,47 @@ class Rest_PodcastController extends Zend_Rest_Controller
|
||||||
|
|
||||||
public function postAction()
|
public function postAction()
|
||||||
{
|
{
|
||||||
|
//If we do get an ID on a POST, then that doesn't make any sense
|
||||||
|
//since POST is only for creating.
|
||||||
|
if ($id = $this->_getParam('id', false)) {
|
||||||
|
$resp = $this->getResponse();
|
||||||
|
$resp->setHttpResponseCode(400);
|
||||||
|
$resp->appendBody("ERROR: ID should not be specified when using POST. POST is only used for podcast creation, and an ID will be chosen by Airtime");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$requestData = json_decode($this->getRequest()->getRawBody(), true);
|
||||||
|
$podcast = Podcast::create($requestData);
|
||||||
|
|
||||||
|
$this->getResponse()
|
||||||
|
->setHttpResponseCode(201)
|
||||||
|
->appendBody(json_encode($podcast));
|
||||||
|
}
|
||||||
|
catch (PodcastLimitReachedException $e) {
|
||||||
|
$this->getResponse()
|
||||||
|
->setHttpResponseCode(400)
|
||||||
|
->appendBody("ERROR: Podcast limit reached.");
|
||||||
|
}
|
||||||
|
catch (InvalidPodcastException $e) {
|
||||||
|
$this->invalidDataResponse();
|
||||||
|
Logging::error($e->getMessage());
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
$this->unknownErrorResponse();
|
||||||
|
Logging::error($e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function putAction()
|
public function putAction()
|
||||||
{
|
{
|
||||||
|
Logging::info("podcast put");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteAction()
|
public function deleteAction()
|
||||||
{
|
{
|
||||||
|
Logging::info("delete podcast");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getId()
|
private function getId()
|
||||||
|
@ -56,4 +114,11 @@ class Rest_PodcastController extends Zend_Rest_Controller
|
||||||
return $id;
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function unknownErrorResponse()
|
||||||
|
{
|
||||||
|
$resp = $this->getResponse();
|
||||||
|
$resp->setHttpResponseCode(400);
|
||||||
|
$resp->appendBody("An unknown error occurred.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Rest_PodcastEpisodesController extends Zend_Rest_Controller
|
||||||
|
{
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->view->layout()->disableLayout();
|
||||||
|
|
||||||
|
// Remove reliance on .phtml files to render requests
|
||||||
|
$this->_helper->viewRenderer->setNoRender(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function indexAction()
|
||||||
|
{
|
||||||
|
Logging::info("episodes index");
|
||||||
|
$id = $this->getId();
|
||||||
|
Logging::info($id);
|
||||||
|
if (!$id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAction()
|
||||||
|
{
|
||||||
|
Logging::info("episodes get");
|
||||||
|
$id = $this->getId();
|
||||||
|
if (!$id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postAction()
|
||||||
|
{
|
||||||
|
Logging::info("episodes post");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function putAction()
|
||||||
|
{
|
||||||
|
Logging::info("episodes put");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteAction()
|
||||||
|
{
|
||||||
|
Logging::info("delete - episodes");
|
||||||
|
$id = $this->getId();
|
||||||
|
Logging::info($id);
|
||||||
|
if (!$id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getId()
|
||||||
|
{
|
||||||
|
if (!$id = $this->_getParam('episode_id', false)) {
|
||||||
|
$resp = $this->getResponse();
|
||||||
|
$resp->setHttpResponseCode(400);
|
||||||
|
$resp->appendBody("ERROR: No podcast ID specified.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function unknownErrorResponse()
|
||||||
|
{
|
||||||
|
$resp = $this->getResponse();
|
||||||
|
$resp->setHttpResponseCode(400);
|
||||||
|
$resp->appendBody("An unknown error occurred.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Rest_RouteController
|
||||||
|
*
|
||||||
|
* Taken from https://github.com/aporat/Application_Rest_Controller_Route
|
||||||
|
* to enable hierarchy routing
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Rest_RouteController extends Zend_Controller_Router_Route
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Zend_Controller_Front
|
||||||
|
*/
|
||||||
|
protected $_front;
|
||||||
|
|
||||||
|
protected $_actionKey = 'action';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the route for mapping by splitting (exploding) it
|
||||||
|
* to a corresponding atomic parts. These parts are assigned
|
||||||
|
* a position which is later used for matching and preparing values.
|
||||||
|
*
|
||||||
|
* @param Zend_Controller_Front $front Front Controller object
|
||||||
|
* @param string $route Map used to match with later submitted URL path
|
||||||
|
* @param array $defaults Defaults for map variables with keys as variable names
|
||||||
|
* @param array $reqs Regular expression requirements for variables (keys as variable names)
|
||||||
|
* @param Zend_Translate $translator Translator to use for this instance
|
||||||
|
*/
|
||||||
|
public function __construct(Zend_Controller_Front $front, $route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null)
|
||||||
|
{
|
||||||
|
$this->_front = $front;
|
||||||
|
$this->_dispatcher = $front->getDispatcher();
|
||||||
|
|
||||||
|
parent::__construct($route, $defaults, $reqs, $translator, $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches a user submitted path with parts defined by a map. Assigns and
|
||||||
|
* returns an array of variables on a successful match.
|
||||||
|
*
|
||||||
|
* @param string $path Path used to match against this routing map
|
||||||
|
* @return array|false An array of assigned values or a false on a mismatch
|
||||||
|
*/
|
||||||
|
public function match($path, $partial = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
$return = parent::match($path, $partial);
|
||||||
|
|
||||||
|
// add the RESTful action mapping
|
||||||
|
if ($return) {
|
||||||
|
$request = $this->_front->getRequest();
|
||||||
|
$path = $request->getPathInfo();
|
||||||
|
$params = $request->getParams();
|
||||||
|
|
||||||
|
$path = trim($path, self::URI_DELIMITER);
|
||||||
|
|
||||||
|
if ($path != '') {
|
||||||
|
$path = explode(self::URI_DELIMITER, $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Store path count for method mapping
|
||||||
|
$pathElementCount = count($path);
|
||||||
|
|
||||||
|
// Determine Action
|
||||||
|
$requestMethod = strtolower($request->getMethod());
|
||||||
|
if ($requestMethod != 'get') {
|
||||||
|
if ($request->getParam('_method')) {
|
||||||
|
$return[$this->_actionKey] = strtolower($request->getParam('_method'));
|
||||||
|
} elseif ( $request->getHeader('X-HTTP-Method-Override') ) {
|
||||||
|
$return[$this->_actionKey] = strtolower($request->getHeader('X-HTTP-Method-Override'));
|
||||||
|
} else {
|
||||||
|
$return[$this->_actionKey] = $requestMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map PUT and POST to actual create/update actions
|
||||||
|
// based on parameter count (posting to resource or collection)
|
||||||
|
switch( $return[$this->_actionKey] ){
|
||||||
|
case 'post':
|
||||||
|
$return[$this->_actionKey] = 'post';
|
||||||
|
break;
|
||||||
|
case 'put':
|
||||||
|
$return[$this->_actionKey] = 'put';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// if the last argument in the path is a numeric value, consider this request a GET of an item
|
||||||
|
$lastParam = array_pop($path);
|
||||||
|
if (is_numeric($lastParam)) {
|
||||||
|
$return[$this->_actionKey] = 'get';
|
||||||
|
} else {
|
||||||
|
$return[$this->_actionKey] = 'index';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
airtime_mvc/application/services/PodcastService.php
Normal file
34
airtime_mvc/application/services/PodcastService.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Application_Service_PodcastService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* There is maximum of 50 podcasts allowed in the library - to limit
|
||||||
|
* resource consumption. This function returns true if the podcast
|
||||||
|
* limit has been reached.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function podcastLimitReached()
|
||||||
|
{
|
||||||
|
if (PodcastQuery::create()->count() >= 50) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given podcast URL is valid.
|
||||||
|
*
|
||||||
|
* @param $podcastUrl String containing the podcast feed URL
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function validatePodcastUrl($podcastUrl)
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,11 +29,15 @@ define('SETUP_PATH', BUILD_PATH . 'airtime-setup/');
|
||||||
define('APPLICATION_PATH', ROOT_PATH . 'application/');
|
define('APPLICATION_PATH', ROOT_PATH . 'application/');
|
||||||
define('CONFIG_PATH', APPLICATION_PATH . 'configs/');
|
define('CONFIG_PATH', APPLICATION_PATH . 'configs/');
|
||||||
define('VENDOR_PATH', ROOT_PATH . '../vendor/');
|
define('VENDOR_PATH', ROOT_PATH . '../vendor/');
|
||||||
|
define('REST_MODULE_CONTROLLER_PATH', APPLICATION_PATH . 'modules/rest/controllers/');
|
||||||
|
|
||||||
define("AIRTIME_CONFIG_STOR", "/etc/airtime/");
|
define("AIRTIME_CONFIG_STOR", "/etc/airtime/");
|
||||||
|
|
||||||
define('AIRTIME_CONFIG', 'airtime.conf');
|
define('AIRTIME_CONFIG', 'airtime.conf');
|
||||||
|
|
||||||
|
//Rest Module Controllers - for custom Rest_RouteController.php
|
||||||
|
set_include_path(REST_MODULE_CONTROLLER_PATH . PATH_SEPARATOR . get_include_path());
|
||||||
|
|
||||||
//Vendors (Composer)
|
//Vendors (Composer)
|
||||||
set_include_path(VENDOR_PATH . PATH_SEPARATOR . get_include_path());
|
set_include_path(VENDOR_PATH . PATH_SEPARATOR . get_include_path());
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue