From 67db2c1d255cf9596127fa897b80bf573cd843f8 Mon Sep 17 00:00:00 2001 From: drigato Date: Wed, 16 Sep 2015 14:22:13 -0400 Subject: [PATCH] SAAS-1063: REST API for podcasts Hierarchy routing is working Basic implentation of podcast INDEX and POST actions done --- airtime_mvc/application/configs/ACL.php | 4 + airtime_mvc/application/configs/constants.php | 4 + .../application/models/airtime/Podcast.php | 71 +++++++++++- .../application/modules/rest/Bootstrap.php | 30 ++++- .../rest/controllers/PodcastController.php | 69 ++++++++++- .../controllers/PodcastEpisodesController.php | 76 ++++++++++++ .../rest/controllers/RouteController.php | 108 ++++++++++++++++++ .../application/services/PodcastService.php | 34 ++++++ airtime_mvc/public/index.php | 4 + 9 files changed, 391 insertions(+), 9 deletions(-) create mode 100644 airtime_mvc/application/modules/rest/controllers/PodcastEpisodesController.php create mode 100644 airtime_mvc/application/modules/rest/controllers/RouteController.php create mode 100644 airtime_mvc/application/services/PodcastService.php diff --git a/airtime_mvc/application/configs/ACL.php b/airtime_mvc/application/configs/ACL.php index 843cfff30..5473dcba6 100644 --- a/airtime_mvc/application/configs/ACL.php +++ b/airtime_mvc/application/configs/ACL.php @@ -35,6 +35,8 @@ $ccAcl->add(new Zend_Acl_Resource('library')) ->add(new Zend_Acl_Resource('downgrade')) ->add(new Zend_Acl_Resource('rest:media')) ->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('thank-you')) ->add(new Zend_Acl_Resource('provisioning')) @@ -61,6 +63,8 @@ $ccAcl->allow('G', 'index') ->allow('G', 'downgrade') ->allow('G', 'rest:show-image', 'get') ->allow('G', 'rest:media', 'get') + ->allow('G', 'rest:podcast', 'get') + ->allow('G', 'rest:podcast-episodes', 'get') ->allow('G', 'setup') ->allow('G', 'embeddablewidgets') ->allow('H', 'soundcloud') diff --git a/airtime_mvc/application/configs/constants.php b/airtime_mvc/application/configs/constants.php index c1b1edca1..868e4d5ca 100644 --- a/airtime_mvc/application/configs/constants.php +++ b/airtime_mvc/application/configs/constants.php @@ -121,3 +121,7 @@ define('CELERY_FAILED_STATUS', 'FAILED'); // Celery Services define('SOUNDCLOUD_SERVICE_NAME', 'soundcloud'); + +// Podcast Types +define('STATION_PODCAST', 0); +define('IMPORTED_PODCAST', 1); diff --git a/airtime_mvc/application/models/airtime/Podcast.php b/airtime_mvc/application/models/airtime/Podcast.php index 43fdede8a..86d961ae5 100644 --- a/airtime_mvc/application/models/airtime/Podcast.php +++ b/airtime_mvc/application/models/airtime/Podcast.php @@ -1,18 +1,77 @@ 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()); + } + } } diff --git a/airtime_mvc/application/modules/rest/Bootstrap.php b/airtime_mvc/application/modules/rest/Bootstrap.php index af4f0ba98..4d6603093 100644 --- a/airtime_mvc/application/modules/rest/Bootstrap.php +++ b/airtime_mvc/application/modules/rest/Bootstrap.php @@ -1,5 +1,7 @@ getRouter(); $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)); + $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 **/ $downloadRoute = new Zend_Controller_Router_Route( 'rest/media/:id/download', diff --git a/airtime_mvc/application/modules/rest/controllers/PodcastController.php b/airtime_mvc/application/modules/rest/controllers/PodcastController.php index a83197aed..b062f588c 100644 --- a/airtime_mvc/application/modules/rest/controllers/PodcastController.php +++ b/airtime_mvc/application/modules/rest/controllers/PodcastController.php @@ -12,11 +12,39 @@ class Rest_PodcastController extends Zend_Rest_Controller 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() { + Logging::info("podcasts get"); $id = $this->getId(); if (!$id) { return; @@ -32,17 +60,47 @@ class Rest_PodcastController extends Zend_Rest_Controller 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() { - + Logging::info("podcast put"); } public function deleteAction() { - + Logging::info("delete podcast"); } private function getId() @@ -56,4 +114,11 @@ class Rest_PodcastController extends Zend_Rest_Controller return $id; } + private function unknownErrorResponse() + { + $resp = $this->getResponse(); + $resp->setHttpResponseCode(400); + $resp->appendBody("An unknown error occurred."); + } + } diff --git a/airtime_mvc/application/modules/rest/controllers/PodcastEpisodesController.php b/airtime_mvc/application/modules/rest/controllers/PodcastEpisodesController.php new file mode 100644 index 000000000..a9040de94 --- /dev/null +++ b/airtime_mvc/application/modules/rest/controllers/PodcastEpisodesController.php @@ -0,0 +1,76 @@ +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."); + } + +} diff --git a/airtime_mvc/application/modules/rest/controllers/RouteController.php b/airtime_mvc/application/modules/rest/controllers/RouteController.php new file mode 100644 index 000000000..ba3bddff9 --- /dev/null +++ b/airtime_mvc/application/modules/rest/controllers/RouteController.php @@ -0,0 +1,108 @@ +_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; + + } + +} \ No newline at end of file diff --git a/airtime_mvc/application/services/PodcastService.php b/airtime_mvc/application/services/PodcastService.php new file mode 100644 index 000000000..dac1513ca --- /dev/null +++ b/airtime_mvc/application/services/PodcastService.php @@ -0,0 +1,34 @@ +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; + } + +} \ No newline at end of file diff --git a/airtime_mvc/public/index.php b/airtime_mvc/public/index.php index 5ddad8bb4..52c063be8 100644 --- a/airtime_mvc/public/index.php +++ b/airtime_mvc/public/index.php @@ -29,11 +29,15 @@ define('SETUP_PATH', BUILD_PATH . 'airtime-setup/'); define('APPLICATION_PATH', ROOT_PATH . 'application/'); define('CONFIG_PATH', APPLICATION_PATH . 'configs/'); 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', 'airtime.conf'); +//Rest Module Controllers - for custom Rest_RouteController.php +set_include_path(REST_MODULE_CONTROLLER_PATH . PATH_SEPARATOR . get_include_path()); + //Vendors (Composer) set_include_path(VENDOR_PATH . PATH_SEPARATOR . get_include_path());