Page tree
Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

<?php
// Fetch blog data from database then create blog resource
$blog = \Phpfox::getService("blog")->get($blogId);
$blogResource = new BlogResource($blog)Restful Mobile API design & Implementation

On this section, we will go through RESTful API design concepts, how to use and extend.

Requirement

2 following phpFox apps are required:

  • RESTful API app (version 4.2.2 or later)
  • Mobile API app (version 4.2.0 or later)

RESTful API design concepts & convention

The fundamental concept in any RESTful API is the Resource. A Resource is an object with a type, associated data, relationships to other Resource objects, and a set of methods.
RESTful API naming convention follows resource-based way and supports standard HTTP GET, POST, PUT and DELETE methods.

In this article, we will use the Blog app as an example.

Resources

Blog app we have defined 2 resources:

  • "blog" resource refers to a Blog entry
  • "blog-category" resource refers to Blog Category entry
  • "blog" resource has a many-to-many relationship with "blog-category" resource

Resource Relationship

A Resource can have either one-to-many or many-to-many relationships with other Resources using property. The Class diagram below shows the relationship of resources in the Blog app

API to access Blog resource

Each resource has the following standard APIs:

  • Listing & search resource
    • Route: "/blog" 
    • Method: "GET"
  • Create new resource
    • Route: "/blog" 
    • Method: "POST"
    • Request data in JSON format
  • Update resource
    • Route: "/blog/:id"
    • Method: "PUT"
    • Request data in JSON format
  • Get Form structure for creating/updating
    • Route: "/blog/form/" + 
    • Method: "GET"
    • Parameters: use "id" param for edit case
  • Delete resource
    • Route: "/blog/:id"
    • Method: "DELETE"
  • Make as featured
    • Route: "/blog/feature/:id
    • Method: "PUT"
  • Make as sponsor
    • Route: "/blog/sponsor/:id
    • Method: "PUT"

Blog Category resource and other resources also have similar APIs

Resource response in JSON

A JSON presentation of a resource looks like an example below.
Blog Resource Response Example

{
	"status": "success",
	"data": {
		"resource_name": "blog",
		"module_name": "blog",
		"title": "Et a dolor eum libero nostrum cumque.",
		"description": "Esse beatae voluptas officiis...",
		"module_id": "blog",
		"item_id": 0,
		"is_approved": true,
		"is_sponsor": false,
		"is_featured": false,
		"is_liked": false,
		"is_friend": false,
		"is_pending": false,
		"post_status": 1,
		"text": "Esse beatae voluptas officiis ratione ",
		"image": null,
		"statistic": {
			"total_like": 0,
			"total_comment": 0,
			"total_view": 1,
			"total_attachment": 0
		},
		"privacy": 0,
		"user": {
			"full_name": "Sheridan Hahn",
			"avatar": null,
			"id": 841
		},
		"categories": [
			{
				"id": 4,
				"name": "Family & Home",
				"subs": null
			},
			{
				"id": 9,
				"name": "Sports",
				"subs": null
			}
		],
		"tags": [
			{
				"tag_text": "tag me",
				"id": 3
			}
		],
		"attachments": [],
		"id": 1,
		"creation_date": "2016-06-21T04:53:48+00:00",
		"modification_date": null,
		"link": "http://localhost:7788/blog/1/et-a-dolor-eum-libero-nostrum-cumque/",
		"extra": {
			"can_view": true,
			"can_like": true,
			"can_share": true,
			"can_delete": true,
			"can_report": true,
			"can_add": true,
			"can_edit": true,
			"can_comment": true,
			"can_publish": false,
			"can_feature": true,
			"can_approve": false,
			"can_sponsor": true,
			"can_sponsor_in_feed": false,
			"can_purchase_sponsor": true
		},
		"self": null,
		"links": {
			"likes": {
				"ref": "mobile/like?item_type=blog&item_id=1"
			},
			"comments": {
				"ref": "mobile/comment?item_type=blog&item_id=1"
			}
		},
		"feed_param": {
			"item_id": 1,
			"comment_type_id": "blog",
			"total_comment": 0,
			"like_type_id": "blog",
			"total_like": 0,
			"feed_title": "Et a dolor eum libero nostrum cumque.",
			"feed_link": "http://localhost:7788/blog/1/et-a-dolor-eum-libero-nostrum-cumque/",
			"feed_is_liked": false,
			"feed_is_friend": false,
			"report_module": "blog"
		}
	},
	"error": null
}


Control Resource API Response

In the new Core Mobile API app, we define ResourceBase class for controlling resource output, mapping data fields and generating routes for Resource API.
Every new Resource class should extend from this base class and use the list of core resources for its relationship when building APIs. This way will help to reduce the code to build the API

Reusable core resources and common use cases

All resource classes are defined in the folder "PF.Site/Apps/core-mobile-api/Api/Resource" under name Space "\Apps\Core_MobileApi\Api\Resource"

Core's Resource

Description

Use Case

UserResource

Presentation of a Phpfox's User

  • Use for post's Author

TagResource

Tag feature in Phpfox

  • List of tags of a post (blog, event, listing)

NotificationResource

Core notification feature

  • Reuse notification feature for other resources

LikeResource

Core like feature

  • Reuse like feature for other resources

CommentResource

Core comment feature

  • Reuse comment feature for another resource

FriendResource

 

 

FileResource

 

 

FeedResource

 

 

AttachmentResource

 

 


Reusable Core Objects

Unlike resource, Core Object is used to group related properties of the Resource into an object. List of reusable objects are listed in the following table:

Object Class

Use Case

Image

Presentation as an Image of an Item (blog, marketplace listing)

Privacy

Privacy of an item

FeedParam

Feed parameter of an Item

Statistic

Total Like, Total Comment, Total View of an Item

Mapping property, related resource and object

To create a Resource object, we can fetch data from the database as an Array and then populate to the Resource. Below is the sample code to creating a Resource from PHP Array

<?php
// Fetch blog data from database then create blog resource
	$blog = \Phpfox::getService("blog")->get($blogId);
	$blogResource = new BlogResource($blog)


We define some of convention way to mapping from a data source to resource's properties
Map resource's properties by naming convention:
If the data source has key same with the property name. It's auto mapped
Map with User Resource
If the data source has a set of "user_" prefix key and `user` property, It auto combine field to UserResource and Map to user property of the current resource

Manual mapping
Use can override or manual mapping use Setter and Getter methods. Setter method uses to control input data, Getter control the output.
All field query from the database has a String data type. But Native App requires to return exactly data type in JSON.
Override "loadMetadataSchema" method of resource to control the response data type.
Following is an example:
<?php

/* A lot of code above */
class PostResource extends ResourceBase
{
/* ... */
protected function loadMetadataSchema(ResourceMetadata $metadata = null)
{
parent::loadMetadataSchema($metadata);
$this->metadata
->mapField('title', ['type' => ResourceMetadata::STRING])
->mapField('description', ['type' => ResourceMetadata::STRING])
->mapField('item_id', ['type' => ResourceMetadata::INTEGER])
->mapField('module_id', ['type' => ResourceMetadata::STRING])
->mapField('is_approved', ['type' => ResourceMetadata::BOOL])
->mapField('is_sponsor', ['type' => ResourceMetadata::BOOL])
->mapField('is_featured', ['type' => ResourceMetadata::BOOL])
->mapField('is_liked', ['type' => ResourceMetadata::BOOL])
->mapField('is_friend', ['type' => ResourceMetadata::BOOL])
->mapField('post_status', ['type' => ResourceMetadata::INTEGER]);
}
}

Develop restful resource APIs

Following section, we base on an app named Posts clone from the Core Blog app to write example code.
The features of Posts app are the same as the Blog app, just change routing from "blog" to "post". Features included:

  1. Posts listing, search, my posts, friend's post, browse by category
  2. Create or Edit a Post
  3. Delete a Post
  4. Categories
  5. Tags to a Post

Posts app information:

  • App's ID: "Posts"
  • App's Dir: "PF.Site/Apps/Posts"
  • App's Alias: "post"
  • App's Routing: "post/*"
  • App's Namespace: "\Apps\Posts"

Step by step to extend APIs:

  1. Create a resource class for each resource of the App
  2. Create a Service to handle API request for each resource
  3. Hook services to register new APIs

Structure overview


The picture above shows the basic structure to extend your app to support Core Mobile API. Take a look at highlighted items and let go to the detail of each section

Section

Mission

Api/Resource

This section defines all the resources in your app. Each resource has fields and method to control the application login and API response result.
Core Mobile API app provide ResourceBase class for extendable and make it easy to help create standard resource APIs for Native Mobile Application

Api/Form

Create form extend from Mobile API app to help generate form structure that can understand by Mobile Application

Api/Security

The convenient way to control permission to access APIs

Service

Handle your API request and return the response

hooks

Register your APIs or Extent core API

 

Restful API routing & service handler

Each API routing contains information on how to map HTTP request with a service method handler
API Url pattern:  url/restful_api/mobile/route_path?access_token=token

  • url : your PHPFOX website
  • token: access token generate by AUTH API use to access API
  • route_path : Define by your new application

We can define new restful API routing in several ways. Because restful API is closely related to a resource, it should be generated from resource class or manual definition in case your app has more APIs to implement.

By extends ResourceBase class, PostResource able to generate resource API routes and links to PostApi service implementation as service handler automatically.
Manual routes can be add via PostApi:__naming() method. See example:
PostApi.php
<?php
class PostApi extends AbstractResourceApi implements ActivityFeedInterface, MobileAppSettingInterface
{
/.../
public function __naming()
{
return [
/* API route `/restful_api/mobile/post/search-form` will map with PostApi::searchForm() method */
'post/search-form'=>[
'get'=>'searchForm'
],
];
}
}
 
The final step to help system understands new routes by implement hook "mobile_api_routing_registration.php" (example implement show in the section below).
Following table show detail Resource API mapping were generated from PostResource and PostApi implementation

Route path pattern

Request Method

Map Method

/post

GET

PostApi::findAll()

/post/:id

GET

PostApi::findOne()

/post/form

GET

PostApi::form()

/post/form/:id

GET

PostApi::form()

/post

POST

PostApi::create()

/post/:id

PUT

PostAPI::update()

/post/:id

DELETE

PostApi::delete()

/post/feature/:id

PUT

PostApi::feature()

/post/approve/:id

PUT

PostApi::approve()

/post/sponsor/:id

PUT

PostApi::sponsor()

 

Define your resources

Post app has two resources post and post-category, User can be only able to create post resource
Now let create your post resource. Create new class PostResource extend from ResourceBase. 
The resource's properties are auto map base on same name rule
PostResource.php  Expand source
<?php

namespace Apps\Posts\Api\Resource;
/* ... */
class PostResource extends ResourceBase
{
/**

  • Define the unique resource name
    */
    const RESOURCE_NAME = "post";
    const TAG_CATEGORY = 'post';
    public $resource_name = self::RESOURCE_NAME;
    public $module_name = 'post';
    /**
  • @var string Post's title mapping
    */
    public $title;
    /**
  • @var string Post's description mapping
    */
    public $description;
    /**
  • @var string Post's parent module id
    */
    public $module_id;
    /**
  • @var int Post's parent id
    */
    public $item_id;
    /**
  • @var bool Status of the post
    */
    public $is_approved;
    /**
  • @var bool sponsor status of the post
    */
    public $is_sponsor;
    /**
  • @var bool sponsor status of the post
    */
    public $is_featured;
    public $is_liked;
    public $is_friend;
    public $post_status;
    public $text;
     
    Override ResourceBase::loadMetadataSchema() method to define database for each field when mapping
    <?php
     
    /* A lot of code above */
    class PostResource extends ResourceBase
    {
    /* ... */
    protected function loadMetadataSchema(ResourceMetadata $metadata = null)
    {
    parent::loadMetadataSchema($metadata);
    $this->metadata
    ->mapField('title', ['type' => ResourceMetadata::STRING])
    ->mapField('description', ['type' => ResourceMetadata::STRING])
    ->mapField('item_id', ['type' => ResourceMetadata::INTEGER])
    ->mapField('module_id', ['type' => ResourceMetadata::STRING])
    ->mapField('is_approved', ['type' => ResourceMetadata::BOOL])
    ->mapField('is_sponsor', ['type' => ResourceMetadata::BOOL])
    ->mapField('is_featured', ['type' => ResourceMetadata::BOOL])
    ->mapField('is_liked', ['type' => ResourceMetadata::BOOL])
    ->mapField('is_friend', ['type' => ResourceMetadata::BOOL])
    ->mapField('post_status', ['type' => ResourceMetadata::INTEGER]);
    }
    }
     
    If mapping base on the field's name and data type are not enough. We can override the property's value via getting/setting method
    <?php

    /* A lot of code above */
    class PostResource extends ResourceBase
    {
    /* ... */
    /**
  • Get detail url
  • @return string
    */
    public function getLink()
    {
    return \Phpfox::permalink('post', $this->id, $this->title);
    }
    public function getImage()
    {
    return Image::createFrom([
    'file' => $this->rawData['image_path'],
    'server_id' => $this->rawData['server_id'],
    'path' => 'post.url_photo',
    'suffix' => '_1024'
    ]);
    }
    /**
  • @return array
  • @throws \Exception
    */
    public function getCategories()
    {
    return $this->categories;
    }
    public function getTags()
    {
    return $this->tags;
    }
    public function getText()
    {
    if (empty($this->text) && !empty($this->rawData['text'])) {
    $this->text = $this->rawData['text'];
    }
    TextFilter::pureHtml($this->text, true);
    return $this->text;
    }
    /* ... */
    }
     
     

    Define API service to handle API requests

    API Service is similar to PHPFOX App's Service, you can extend from the parent class to minimal and reuse code
    Create PostApi.php within Service folder
    PostApi.php
    <?php
    namespace Apps\Posts\Service;
    /* ... */
    class PostApi extends AbstractResourceApi implements ActivityFeedInterface, MobileAppSettingInterface
    {
    /**
  • @var Post
    */
    private $postService;
    /**
  • @var Process
    */
    private $processService;
    /**
  • @var Category
    */
    private $categoryService;
    /**
  • @var \User_Service_User
    */
    private $userService;

    public function __construct()
    {
    parent::__construct();
    $this->postService = Phpfox::getService("post");
    $this->categoryService = Phpfox::getService('post.category');
    $this->processService = Phpfox::getService('post.process');
    $this->userService = Phpfox::getService('user');
    }
    /* .. */
    }
     
    In example above
  • The class extent AbstractResourceApi to reuse resource base feature
  • Implement ActivityFeedInterface to able display the resource to Activity Feed page
  • Implement MobileAppSettingInterface to register more screens and actions to Mobile App without change code

Now you need to implement all abstract method from parent class and interfaces.
The initial code of your service would look like bellow:
PostApi.php  Expand source
<?php

namespace Apps\Posts\Service;
/* ... */
class PostApi extends AbstractResourceApi implements ActivityFeedInterface, MobileAppSettingInterface
{
/**

  • Get list post
    *
  • @param array $params
  • @return array|mixed
  • @throws ValidationErrorException
    */
    function findAll($params = [])
    {
    /* ... */
    $aItems = $this->browse()->getRows();
    if ($aItems) {
    $this->processRows($aItems);
    }
    return $this->success($aItems);
    }
    /**
  • @param $params
  • @return array|bool
    */
    function findOne($params)
    {
    $id = $this->resolver->resolveId($params);
    /* ... */
    return $this->success($resource->loadFeedParam()->toArray());
    }

    public function delete($params)
    {
    $id = $this->resolver->resolveId($params);
    $result = Phpfox::getService('post.process')->delete($id);
    if ($result !== false) {
    return $this->success([
    'id' => $id
    ]);
    }
    return $this->error('Cannot delete post');
    }
    /**
  • Get Create/Update document form
  • @param array $params
  • @return mixed
  • @throws \Exception
    */
    public function form($params = [])
    {
    /** @var PostForm $form */
    $form = $this->createForm(PostForm::class, [
    'title' => 'adding_a_new_post',
    'method' => 'post',
    'action' => UrlUtility::makeApiUrl('post')
    ]);
    /* ... */
    return $this->success($form->getFormStructure());
    }
    /**
  • Create a new Post API
  • @param array $params
  • @return array|bool|mixed
    */
    public function create($params = [])
    {
    /* ... */
    }
    /**
  • Update a post
    *
  • @param $params
  • @return mixed
    */
    public function update($params)
    {
    /* ... */
    }

    /**
  • @param $id
  • @param bool $returnResource
  • @return array|PostResource
    */
    function loadResourceById($id, $returnResource = false)
    {
    $item = Phpfox::getService("post")->getPost($id);
    if (empty($item['post_id'])) {
    return null;
    }
    if ($returnResource) {
    return $this->processOne($item);
    }
    return $item;
    }
    /**
  • Update multiple document base on document query
    *
  • @param $params
  • @return mixed
  • @throws \Exception
    */
    public function patchUpdate($params)
    {
    /* ... */
    }


    /**
  • Get for display on activity feed
  • @param array $feed
  • @param array $item detail data from database
  • @return array
    */
    public function getFeedDisplay($feed, $item)
    {
    /* ... */
    }

    /**
  • Create custom access control layer
    */
    public function createAccessControl()
    {
    $this->accessControl =
    new PostAccessControl($this->getSetting(), $this->getUser());
    /* ... */
    }
    /**
  • @param array $params
  • @return mixed
    */
    function searchForm($params = [])
    {
    $this->denyAccessUnlessGranted(PostAccessControl::VIEW);
    /** @var PostSearchForm $form */
    $form = $this->createForm(PostSearchForm::class, [
    'title' =>'search',
    'method' => 'GET',
    'action' => UrlUtility::makeApiUrl('post')
    ]);
    return $this->success($form->getFormStructure());
    }

    public function getRouteMap()
    {
    /* ... */
    }
    /**
  • @param $param
  • @return MobileApp
    */
    public function getAppSetting($param)
    {
    /* ... */
    }
    }
     
     

    Register services and resources

    After completed create required class and implementation, you need to register your service and resources
    Open start.php in your app and add the following row to register service.
    start.php
    <?php
     
    /* ... */
     
    Phpfox::getLib('module')
    ->addAliasNames('post', 'Posts')
    ->addServiceNames([
    // New API service register here
    'mobile.post_api' => Service\PostApi::class,
    'mobile.post_category_api' => Service\PostCategoryApi::class,
    // Other Services of the app
    'post.category' => Service\Category\Category::class,
    'post.category.process' => Service\Category\Process::class,
    'post.api' => Service\Api::class,
    'post' => Service\Posts::class,
    'post.browse' => Service\Browse::class,
    'post.cache.remove' => Service\Cache\Remove::class,
    'post.callback' => Service\Callback::class,
    'post.process' => Service\Process::class,
    'post.permission' => Service\Permission::class,
    ]);
    /* ... */
     
    Next step, you need to create hook "mobile_api_routing_registration.php" in the hooks folder.
    mobile_api_routing_registration.php
    <?php
    /**
  • Define RestAPI services. Note the name must be same as definition in start.php file
    */
    $this->apiNames['mobile.post_api'] = \Apps\Posts\Service\PostApi::class;
    $this->apiNames['mobile.post_category_api'] = \Apps\Posts\Service\PostCategoryApi::class;
    /**
  • Register Resource Name, This help auto generate routing for the resource
  • Note: resource name must be mapped correctly to resource api
    */
    $this->resourceNames[\Apps\Posts\Api\Resource\PostResource::RESOURCE_NAME] = 'mobile.post_api';
    $this->resourceNames[\Apps\Posts\Api\Resource\PostCategoryResource::RESOURCE_NAME] = 'mobile.post_category_api';
     

    Extends routing to support more APIs

    After complete register API step, your Phpfox site now extend more restful APIs follow naming convention rules as mentions above
    If you would like to build more APIs that Resource Naming Convention rules has not supports. In the API service, there a magic function called "__naming()" able to do that
    PostApi.php
    <?php
     
    class PostApi extends AbstractResourceApi implements ActivityFeedInterface, MobileAppSettingInterface
    {
    /**
  • This method allow you add custom route for APIs
  • Return an array with key is routing rule and mapping condition
  • In this case, 'mobile/post/search-form' map to `searchForm` method
    */
    public function __naming()
    {
    return [
    'post/search-form'=> [
    'get'=>'searchForm'
    ]
    ];
    }
     
    /**
  • @param array $params
  • @return mixed
    */
    function searchForm($params = [])
    {
    $this->denyAccessUnlessGranted(PostAccessControl::VIEW);
    /** @var PostSearchForm $form */
    $form = $this->createForm(PostSearchForm::class, [
    'title' =>'search',
    'method' => 'GET',
    'action' => UrlUtility::makeApiUrl('post')
    ]);
    return $this->success($form->getFormStructure());
    }
    }
     
    The Full Code of Post API service
    PostApi.php  Expand source
    <?php

    namespace Apps\Posts\Service;

    use Apps\Core_MobileApi\Adapter\MobileApp\MobileApp;
    use Apps\Core_MobileApi\Adapter\MobileApp\MobileAppSettingInterface;
    use Apps\Core_MobileApi\Adapter\MobileApp\Screen;
    use Apps\Core_MobileApi\Adapter\Utility\UrlUtility;
    use Apps\Core_MobileApi\Api\AbstractResourceApi;
    use Apps\Core_MobileApi\Api\ActivityFeedInterface;
    use Apps\Core_MobileApi\Api\Exception\ValidationErrorException;
    use Apps\Core_MobileApi\Api\Form\Type\FileType;
    use Apps\Core_MobileApi\Api\Resource\AttachmentResource;
    use Apps\Core_MobileApi\Api\Resource\Object\HyperLink;
    use Apps\Core_MobileApi\Api\Resource\TagResource;
    use Apps\Core_MobileApi\Api\Security\AppContextFactory;
    use Apps\Core_MobileApi\Service\Helper\Pagination;
    use Apps\Core_MobileApi\Service\NameResource;
    use Apps\Core_MobileApi\Service\TagApi;
    use Apps\Posts\Api\Form\PostSearchForm;
    use Apps\Posts\Api\Form\PostForm;
    use Apps\Posts\Api\Resource\PostCategoryResource;
    use Apps\Posts\Api\Resource\PostResource;
    use Apps\Posts\Api\Security\PostAccessControl;
    use Phpfox;
    class PostApi extends AbstractResourceApi implements ActivityFeedInterface, MobileAppSettingInterface
    {
    const ERROR_POST_NOT_FOUND = "Post not found";
    /**
  • @var Post
    */
    private $postService;
    /**
  • @var Process
    */
    private $processService;
    /**
  • @var Category
    */
    private $categoryService;
    /**
  • @var \User_Service_User
    */
    private $userService;

    public function __construct()
    {
    parent::__construct();
    $this->postService = Phpfox::getService("post");
    $this->categoryService = Phpfox::getService('post.category');
    $this->processService = Phpfox::getService('post.process');
    $this->userService = Phpfox::getService('user');
    }
    public function __naming()
    {
    return [
    'post/search-form'=>[
    'get'=>'searchForm'
    ],
    ];
    }
    /**
  • Get list post
    *
  • @param array $params
  • @return array|mixed
  • @throws ValidationErrorException
    */
    function findAll($params = [])
    {
    // Resolve and validate parameter from the requests
    $params = $this->resolver
    ->setDefined([
    'view', 'module_id', 'item_id', 'category', 'q', 'sort', 'when', 'profile_id', 'limit', 'page', 'tag'
    ])
    ->setDefault([
    'limit' => Pagination::DEFAULT_ITEM_PER_PAGE,
    'page' => 1
    ])
    ->setAllowedValues("sort", ['latest', 'most_viewed','most_liked','most_discussed'])
    ->setAllowedValues('view', ['my', 'spam', 'pending', 'draft', 'friend'])
    ->setAllowedValues('when', ['all-time', 'today', 'this-week', 'this-month'])
    ->setAllowedTypes('limit', 'int', [
    'min' => Pagination::DEFAULT_MIN_ITEM_PER_PAGE,
    'max' => Pagination::DEFAULT_MAX_ITEM_PER_PAGE
    ])
    ->setAllowedTypes('page', 'int', ['min' => 1])
    ->setAllowedTypes('profile_id','int', ['min' => 1])
    ->setAllowedTypes('item_id', 'int', ['min' => 1])
    ->setAllowedTypes('category', 'int', ['min' => 1])
    ->resolve($params)
    ->getParameters();
    if (!$this->resolver->isValid()) {
    return $this->validationParamsError($this->resolver->getInvalidParameters());
    }
    // Security checking
    $this->denyAccessUnlessGranted(PostAccessControl::VIEW, null, [
    'view' => $params['view']
    ]);

    $sort = $params['sort'];
    $view = $params['view'];
    $parentModule = null;
    if (!empty($params['module_id']) && !empty($params['item_id'])) {
    $parentModule = [
    'module_id' => $params['module_id'],
    'item_id' => $params['item_id'],
    ];
    }
    $user = null;
    $isProfile = $params['profile_id'];
    if ($isProfile) {
    $user = $this->userService->get($isProfile);
    if (empty($user)) {
    return $this->notFoundError("User profile not found");
    }
    $this->search()->setCondition('AND post.user_id = ' . $user['user_id']);
    }
    $browseParams = [
    'module_id' => 'post',
    'alias' => 'post',
    'field' => 'post_id',
    'table' => Phpfox::getT('post'),
    'hide_view' => ['pending', 'my'],
    'service' => 'post.browse',
    ];
    switch ($view) {
    case 'spam':
    $this->search()->setCondition('AND post.is_approved = 9');
    break;
    case 'pending':
    $this->search()->setCondition('AND post.is_approved = 0');
    break;
    case 'my':
    $this->search()>setCondition('AND post.user_id = ' . $this>getUser()->getId());
    break;
    case 'draft':
    $this->search()>setCondition("AND post.user_id = " . (int)$this>getUser()>getId() . " AND post.is_approved IN(" . ($user['user_id'] == $this>getUser()->getId() ? '0,1' : '1')
    . ") AND post.privacy IN(" . (Phpfox::getParam('core.section_privacy_item_browsing') ? '%PRIVACY%' : Phpfox::getService('core')->getForBrowse($user))
    . ") AND post.post_status = 2");
    break;
    default:
    $this->search()->setCondition("AND post.is_approved = 1 AND post.post_status = 1" . (Phpfox::getUserParam('privacy.can_comment_on_all_items') ? ""
    : " AND post.privacy IN(%PRIVACY%)"));
    break;
    }

    if (!empty($params['category'])) {
    if ($aPostCategory = $this->categoryService->getCategory($params['category'])) {
    $this->search()->setCondition('AND post_category.category_id = ' . (int)$params['category'] . ' AND post_category.user_id = ' . ($isProfile ? (int)$user['user_id'] : 0));
    }
    }
    if (isset($parentModule) && isset($parentModule['module_id'])) {
    $this->search()->setCondition('AND post.module_id = \'' . $parentModule['module_id'] . '\' AND post.item_id = ' . (int)$parentModule['item_id']);
    } else {
    if ($parentModule === null) {
    if (($view == 'pending' || $view == 'draft') && Phpfox::getUserParam('post.can_approve_posts')) {
    } else {
    $this->search()->setCondition('AND post.module_id = \'post\'');
    }
    }
    }
    // search query
    if (!empty($params['q'])) {
    $this->search()>setCondition('AND post.title LIKE "' . Phpfox::getLib('parse.input')>clean('%' . $params['q'] . '%') . '"');
    }
    // Search By tag
    if ($params['tag']) {
    if ($aTag = Phpfox::getService('tag')->getTagInfo('post', $params['tag'])) {
    $this->search()>setCondition('AND tag.tag_text = \'' . Phpfox::getLib('database')>escape($aTag['tag_text']) . '\'');
    } else {
    $this->search()->setCondition('AND 0');
    }
    }

    // sort
    switch ($sort) {
    case 'most_viewed':
    $sort = 'post.total_view DESC, post.time_stamp DESC';
    break;
    case 'most_liked':
    $sort = 'post.total_like DESC, post.time_stamp DESC';
    break;
    case 'most_discussed':
    $sort = 'post.total_comment DESC, post.time_stamp DESC';
    break;
    default:
    $sort = 'post.time_stamp DESC, post.time_stamp DESC';
    break;
    }
    // When
    if ($params['when']) {
    $iTimeDisplay = Phpfox::getLib('date')->mktime(0, 0, 0, Phpfox::getTime('m'), Phpfox::getTime('d'), Phpfox::getTime('Y'));
    switch ($params['when']) {
    case 'today':
    $iEndDay = Phpfox::getLib('date')->mktime(23, 59, 0, Phpfox::getTime('m'), Phpfox::getTime('d'), Phpfox::getTime('Y'));
    $this->search()>setCondition(' AND (post.time_stamp >= \'' . Phpfox::getLib('date')>convertToGmt($iTimeDisplay) . '\' AND post.time_stamp < \'' . Phpfox::getLib('date')->convertToGmt($iEndDay) . '\')');
    break;
    case 'this-week':
    $this->search()>setCondition(' AND post.time_stamp >= ' . (int) Phpfox::getLib('date')>convertToGmt(Phpfox::getLib('date')->getWeekStart()));
    $this->search()>setCondition(' AND post.time_stamp <= ' . (int) Phpfox::getLib('date')>convertToGmt(Phpfox::getLib('date')->getWeekEnd()));
    break;
    case 'this-month':
    $this->search()>setCondition(' AND post.time_stamp >= \'' . Phpfox::getLib('date')>convertToGmt(Phpfox::getLib('date')->getThisMonth()) . '\'');
    $iLastDayMonth = Phpfox::getLib('date')>mktime(0, 0, 0, date('n'), Phpfox::getLib('date')>lastDayOfMonth(date('n')), date('Y'));
    $this->search()>setCondition(' AND post.time_stamp <= \'' . Phpfox::getLib('date')>convertToGmt($iLastDayMonth) . '\'');
    break;
    default:
    break;
    }
    }
    $this->search()->setSort($sort)
    ->setLimit($params['limit'])
    ->setPage($params['page']);
    $this->browse()>params($browseParams)>execute();
    $aItems = $this->browse()->getRows();
    if ($aItems) {
    $this->processRows($aItems);
    }
    return $this->success($aItems);
    }
    /**
  • @param $params
  • @return array|bool
    */
    function findOne($params)
    {
    $id = $this->resolver->resolveId($params);
    $item = $this->postService->getPost($id);
    if ((!isset($item['post_id'])) || (isset($item['module_id']) && Phpfox::isModule($item['module_id']) != true)

    ($item['post_status'] == 2 && Phpfox::getUserId() != $item['user_id'])) {
    return $this->notFoundError();
    }
    if (!$this->getAccessControl()->isGrantedSetting('post.can_approve_posts')) {
    if ($item['is_approved'] != '1' && $item['user_id'] != Phpfox::getUserId()) {
    return $this->notFoundError();
    }
    }
    $resource = $this->processOne($item)
    ->lazyLoad(["user"]);
    $this->denyAccessUnlessGranted(PostAccessControl::VIEW, $resource);

    return $this->success($resource->loadFeedParam()->toArray());
    }
    /**

  • Process Detail response
  • @param $item
  • @return PostResource
    */
    public function processOne($item)
    {
    $resource = $this->processRow($item);
    $resource->categories = $this->getCategoryApi()
    >getByPostId($resource>id);
    $resource->tags = $this->getTagApi()
    >getTagsBy(PostResource::TAG_CATEGORY, $resource>id);
    if (isset($item['total_attachment']) && $item['total_attachment'] > 0) {
    $resource->attachments = $this->getAttachmentApi()
    >getAttachmentsBy($resource>getId(), 'post');
    }
    $this->setSelfHyperMediaLinks($resource);
    $this->setLinksHyperMediaLinks($resource);
    return $resource;
    }
    /**
  • Process list of post
  • @param $aRows
    */
    public function processRows(&$aRows)
    {
    /** @var TagApi $tagReducer */
    $tagReducer = $this->getTagApi();
    $tagCond = [
    'category_id' => 'post',
    'item_id' => []
    ];
    /** @var PostCategoryApi $categoryReducer */
    $categoryReducer = $this->getCategoryApi();
    $categoryCond = [
    'post_id' => []
    ];
    foreach ($aRows as $aRow) {
    $tagCond['item_id'][] = $aRow['post_id'];
    $categoryCond['post_id'][] = $aRow['post_id'];
    }
    $tagReducer->reduceFetchAll($tagCond);
    $categoryReducer->reduceFetchAll($categoryCond);
    foreach ($aRows as $key => $aRow) {
    $aRow['tags'] = $tagReducer->reduceQuery([
    'category_id' => PostResource::TAG_CATEGORY,
    'item_id' => $aRow['post_id']
    ]);
    $aRow['categories'] = $categoryReducer->reduceQuery([
    'post_id' => $aRow['post_id']
    ]);
    $aRows[$key] = $this->processRow($aRow)
    ->displayShortFields()
    ->toArray();
    }
    }
    /**
  • Process single row
  • @param array $item
  • @return PostResource|array
    */
    public function processRow($item)
    {
    $resource = $this->populateResource(PostResource::class, $item);
    $resource->setExtra($this->getAccessControl()->getPermissions($resource));
    // Add self Hyper Media Links
    $this->setSelfHyperMediaLinks($resource);

    return $resource;
    }

    public function delete($params)
    {
    $id = $this->resolver->resolveId($params);
    $post = $this->loadResourceById($id, true);
    if (!$post) {
    return $this->notFoundError();
    }
    $this->denyAccessUnlessGranted(PostAccessControl::DELETE, $post);

    $mResult = Phpfox::getService('post.process')->delete($id);
    if ($mResult !== false) {
    return $this->success([
    'id' => $id
    ]);
    }
    return $this->error('Cannot delete post');
    }
    /**
  • Get Create/Update document form
  • @param array $params
  • @return mixed
  • @throws \Exception
    */
    public function form($params = [])
    {
    $this->denyAccessUnlessGranted(PostAccessControl::ADD);
    $editId = $this->resolver->resolveSingle($params, 'id');
    /** @var PostForm $form */
    $form = $this->createForm(PostForm::class, [
    'title' => 'adding_a_new_post',
    'method' => 'post',
    'action' => UrlUtility::makeApiUrl('post')
    ]);
    $form->setCategories($this->getCategories());
    if ($editId && ($post = $this->loadResourceById($editId, true))) {
    $this->denyAccessUnlessGranted(PostAccessControl::EDIT, $post);
    $form->setAction(UrlUtility::makeApiUrl('post/:id', $editId))
    ->setTitle('editing_post')
    ->setMethod('put');
    $form->assignValues($post);
    }
    return $this->success($form->getFormStructure());
    }
    /**
  • Create a new Post API
  • @param array $params
  • @return array|bool|mixed
    */
    public function create($params = [])
    {
    // Checking create post permission
    $this->denyAccessUnlessGranted(PostAccessControl::ADD);
    /** @var PostForm $form */
    $form = $this->createForm(PostForm::class);
    $form->setCategories($this->getCategories());
    if ($form->isValid()) {
    $id = $this->processCreate($form->getValues());
    if ($id) {
    return $this->success([
    'id' => $id,
    'resource_name'=> PostResource::populate([])->getResourceName(),
    ]);
    }
    else {
    return $this->error($this->getErrorMessage());
    }
    }
    else {
    return $this->validationParamsError($form->getInvalidFields());
    }
    }
    /**
  • Update a post
    *
  • @param $params
  • @return mixed
    */
    public function update($params)
    {
    $id = $this->resolver->resolveId($params);
    // Get post resource and checking for permission
    $post = $this->loadResourceById($id, true);
    if (empty($post)) {
    return $this->notFoundError();
    }
    $this->denyAccessUnlessGranted(PostAccessControl::EDIT, $post);
    /** @var PostForm $form */
    $form = $this->createForm(PostForm::class);
    $form->setCategories($this->getCategories());
    if ($form->isValid() && ($values = $form->getValues())) {
    $success = $this->processUpdate($id, $values, $post);
    if ($success) {
    return $this->success([
    'id' => $id,
    'resource_name'=> PostResource::populate([])->getResourceName(),
    ]);
    }
    else {
    return $this->error($this->getErrorMessage());
    }
    }
    else {
    return $this->validationParamsError($form->getInvalidFields());
    }
    }

    /**
  • @param $id
  • @param bool $returnResource
  • @return array|PostResource
    */
    function loadResourceById($id, $returnResource = false)
    {
    $item = Phpfox::getService("post")->getPost($id);
    if (empty($item['post_id'])) {
    return null;
    }
    if ($returnResource) {
    return $this->processOne($item);
    }
    return $item;
    }
    /**
  • Update multiple document base on document query
    *
  • @param $params
  • @return mixed
  • @throws \Exception
    */
    public function patchUpdate($params)
    {
    // TODO: Implement updateAll() method.
    }


    /**
  • Get for display on activity feed
  • @param array $feed
  • @param array $item detail data from database
  • @return array
    */
    public function getFeedDisplay($feed, $item)
    {
    $categoryCond = [
    'post_id' => []
    ];
    /** @var PostCategoryApi $categoryReducer */
    $categoryReducer = $this->getCategoryApi();
    $categoryCond['post_id'][] = $item['post_id'];
    $categoryReducer->reduceFetchAll($categoryCond);
    $item['categories'] = $categoryReducer->reduceQuery([
    'post_id' => $item['post_id']
    ]);
    return $this->processRow($item)->toArray(['resource_name','id','title','categories','description','image']);
    }
    /**
  • @return PostCategoryApi
    */
    private function getCategoryApi()
    {
    return NameResource::instance()
    ->getApiServiceByResourceName(PostCategoryResource::RESOURCE_NAME);
    }
    /**
  • @return TagApi
    */
    public function getTagApi()
    {
    return NameResource::instance()
    ->getApiServiceByResourceName(TagResource::RESOURCE_NAME);
    }
    private function getCategories()
    {
    return Phpfox::getService('post.category')->getForBrowse();
    }
    /**
  • Create custom access control layer
    */
    public function createAccessControl()
    {
    $this->accessControl =
    new PostAccessControl($this->getSetting(), $this->getUser());
    $moduleId = $this->request()->get('module_id');
    $itemId = $this->request()->get("item_id");
    if ($moduleId && $itemId) {
    $context = AppContextFactory::create($moduleId, $itemId);
    if ($context === null) {
    return $this->notFoundError();
    }
    $this->accessControl->setAppContext($context);
    }
    }
    /**
  • Internal process adding post
  • @param $values
  • @return int
    */
    private function processCreate($values)
    {
    if (!empty($values['file']) && !empty($values['file']['temp_file'])) {
    $values['temp_file'] = $values['file']['temp_file'];
    }
    if (!empty($values['categories'])) {
    $values['selected_categories'] = $values['categories'];
    }
    if (!empty($values['attachment'])) {
    $values['attachment'] = implode(",", $values['attachment']);
    }
    if (!empty($values['tags'])) {
    $values['tag_list'] = $values['tags'];
    }
    if (!empty($values['draft'])) {
    $values['post_status'] = 2;
    }
    else {
    $values['post_status'] = 1;
    }
    $id = $this->processService->add($values);
    return $id;
    }
    /**
  • Internal process update a post
  • @param $id
  • @param $values
  • @param $item PostResource
  • @return bool
    */
    private function processUpdate($id, $values, $item)
    {
    if (!empty($values['file'])) {
    if ($values['file']['status'] == FileType::NEW_UPLOAD || $values['file']['status'] == FileType::CHANGE) {
    $values['temp_file'] = $values['file']['temp_file'];
    }
    elseif ($values['file']['status'] == FileType::REMOVE) {
    $values['remove_photo'] = 1;
    }
    }
    if (!empty($values['categories'])) {
    $values['selected_categories'] = $values['categories'];
    }
    if (!empty($values['attachment'])) {
    $values['attachment'] = implode(",", $values['attachment']);
    }
    if (!empty($values['tags'])) {
    $values['tag_list'] = $values['tags'];
    }
    if (!empty($values['draft'])) {
    $values['post_status'] = $item->post_status;
    }
    else {
    $values['post_status'] = 1;
    }
    $userId = $item->getAuthor()->getId();
    $post = $item->toArray();
    $this->processService->update($id, $userId, $values, $post);
    return true;
    }
    /**
  • @return AttachmentApi
    */
    private function getAttachmentApi()
    {
    return NameResource::instance()
    ->getApiServiceByResourceName(AttachmentResource::RESOURCE_NAME);
    }
    /**
  • @param array $params
  • @return mixed
    */
    function searchForm($params = [])
    {
    $this->denyAccessUnlessGranted(PostAccessControl::VIEW);
    /** @var PostSearchForm $form */
    $form = $this->createForm(PostSearchForm::class, [
    'title' =>'search',
    'method' => 'GET',
    'action' => UrlUtility::makeApiUrl('post')
    ]);
    return $this->success($form->getFormStructure());
    }
    /**
  • @param PostResource $resource
    */
    private function setSelfHyperMediaLinks($resource)
    {
    $resource->setSelf([
    PostAccessControl::VIEW => $this->createHyperMediaLink(PostAccessControl::VIEW,
    $resource,
    HyperLink::GET, 'post/:id',
    ['id' => $resource->getId()]),
    PostAccessControl::EDIT => $this->createHyperMediaLink(PostAccessControl::EDIT,
    $resource,
    HyperLink::GET, 'post/form/:id',
    ['id' => $resource->getId()]),
    PostAccessControl::DELETE => $this->createHyperMediaLink(PostAccessControl::DELETE,
    $resource, HyperLink::DELETE,
    'post/:id',
    ['id' => $resource->getId()])
    ]);
    }
    /**
  • @param PostResource $resource
    */
    private function setLinksHyperMediaLinks($resource)
    {
    $resource->setLinks([
    "likes" => $this->createHyperMediaLink(null,
    $resource,
    HyperLink::GET, 'like',
    ['item_type' => "post", 'item_id' => $resource->getId()]),
    "comments" => $this->createHyperMediaLink(null,
    $resource,
    HyperLink::GET, 'comment',
    ['item_type' => "post", 'item_id' => $resource->getId()])
    ]);
    }
    public function getRouteMap()
    {
    $resource = str_replace('-', '_', PostResource::RESOURCE_NAME);
    $module = 'post';
    return [
    [
    'path' => 'post/:id(/*)',
    'routeName' => ROUTE_MODULE_DETAIL,
    'defaults' => [
    'moduleName' => $module,
    'resourceName' => $resource,
    ]
    ],
    [
    'path' => 'post/category/:category(/*), post/tag/:tag',
    'routeName' => ROUTE_MODULE_LIST,
    'defaults' => [
    'moduleName' => $module,
    'resourceName' => $resource,
    ]
    ],
    [
    'path' => 'post/add',
    'routeName' => ROUTE_MODULE_ADD,
    'defaults' => [
    'moduleName' => $module,
    'resourceName' => $resource,
    ]
    ],
    [
    'path' => 'post(/*)',
    'routeName' => ROUTE_MODULE_HOME,
    'defaults' => [
    'moduleName' => $module,
    'resourceName' => $resource,
    ]
    ]
    ];
    }
    /**
  • @param $param
  • @return MobileApp
    */
    public function getAppSetting($param)
    {
    $l = $this->getLocalization();
    $app = new MobileApp('post' ,[
    'title'=> $l->translate('Posts'),
    'home_view'=>'menu',
    'main_resource'=> new PostResource([]),
    'category_resource'=> new PostCategoryResource([]),
    ]);
    $headerButtons = [
    [
    'icon' => 'list-bullet-o',
    'action' => Screen::ACTION_FILTER_BY_CATEGORY,
    ],
    ];
    if ($this->getAccessControl()->isGranted(PostAccessControl::ADD)) {
    $headerButtons[] = [
    'icon' => 'plus',
    'action' => Screen::ACTION_ADD,
    'params' => ['resource_name'=> (new PostResource([]))->getResourceName()]
    ];
    }
    $app->addSetting('home.header_buttons', $headerButtons);
    return $app;
    }
    }
     
    Now you can start testing your APIs

    Testing Your APIs

     
    Postman is one of a good application for testing and review APIs. 
    The api pattern to access Post App api

    Pattern

    description

    ex. response

    GET url/restful_api/mobile/post?access_token=token

    Get List of Posts

     Expand source
    {
    "status": "success",
    "data": [
    {
    "resource_name": "post",
    "title": "Title of the post",
    "description": "Description of the post",
    "module_id": "post",
    "item_id": 0,
    "is_approved": true,
    "is_sponsor": false,
    "is_featured": false,
    "is_liked": null,
    "is_friend": null,
    "post_status": 1,
    "image": null,
    "statistic": {
    "total_like": 0,
    "total_comment": 0,
    "total_view": 1,
    "total_attachment": 0
    },
    "privacy": 0,
    "user": {
    "full_name": "Cute User",
    "avatar": null,
    "id": 1
    },
    "categories": [
    {
    "id": 6,
    "name": "Recreation"
    }
    ],
    "tags": [],
    "attachments": [],
    "id": 1,
    "creation_date": "2018-11-30T04:44:39+00:00",
    "modification_date": null,
    "link": "http://localhost:7788/post/1/love/",
    "extra": {
    "can_view": true,
    "can_like": true,
    "can_share": true,
    "can_delete": true,
    "can_report": false,
    "can_add": true,
    "can_edit": true,
    "can_comment": true,
    "can_publish": false,
    "can_feature": true,
    "can_approve": true,
    "can_sponsor": true,
    "can_sponsor_in_feed": false,
    "can_purchase_sponsor": true
    },
    "self": null,
    "links": null
    }
    ]
    }

    GET url/restful_api/mobile/post/1?access_token=token

    Get Post Detail

     Expand source
    {
    "status": "success",
    "data": {
    "resource_name": "post",
    "title": "Title of the post",
    "description": "Description of the post",
    "module_id": "post",
    "item_id": 0,
    "is_approved": true,
    "is_sponsor": false,
    "is_featured": false,
    "is_liked": false,
    "is_friend": false,
    "post_status": 1,
    "text": "xxxx",
    "image": null,
    "statistic": {
    "total_like": 0,
    "total_comment": 0,
    "total_view": 1,
    "total_attachment": 0
    },
    "privacy": 0,
    "user": {
    "full_name": "Cute User",
    "avatar": null,
    "id": 1
    },
    "categories": [
    {
    "id": 6,
    "name": "Recreation"
    }
    ],
    "tags": [],
    "attachments": [],
    "id": 1,
    "creation_date": "2018-11-30T04:44:39+00:00",
    "modification_date": null,
    "link": "http://localhost:7788/post/1/love/",
    "extra": {
    "can_view": true,
    "can_like": true,
    "can_share": true,
    "can_delete": true,
    "can_report": false,
    "can_add": true,
    "can_edit": true,
    "can_comment": true,
    "can_publish": false,
    "can_feature": true,
    "can_approve": true,
    "can_sponsor": true,
    "can_sponsor_in_feed": false,
    "can_purchase_sponsor": true
    },
    "self": null,
    "links": {
    "likes": {
    "ref": "mobile/like?item_type=post&item_id=1"
    },
    "comments": {
    "ref": "mobile/comment?item_type=post&item_id=1"
    }
    },
    "feed_param": {
    "item_id": 1,
    "comment_type_id": "post",
    "total_comment": 0,
    "like_type_id": "post",
    "total_like": 0,
    "feed_title": "Love",
    "feed_link": "http://localhost:7788/post/1/love/",
    "feed_is_liked": false,
    "feed_is_friend": false,
    "report_module": "post"
    }
    }
    }

    GET url/restful_api/mobile/post/form/?access_token=token
     

  1. Or get Edit form response
    GET url/restful_api/mobile/post/form/1?access_token=token|Get create post form| Expand source
    {
    "status": "success",
    "data": {
    "title": "Add a new post",
    "description": "",
    "action": "mobile/post",
    "method": "post",
    "sections": {
    "basic": {
    "label": "Basic Info",
    "fields": {
    "title": {
    "name": "title",
    "component_name": "Text",
    "required": true,
    "value": "",
    "returnKeyType": "next",
    "label": "Title",
    "placeholder": "Fill title for post"
    },
    "text": {
    "name": "text",
    "component_name": "TextArea",
    "required": true,
    "value": "",
    "returnKeyType": "default",
    "label": "Post",
    "placeholder": "Add content to post"
    },
    "attachment": {
    "name": "attachment",
    "component_name": "Attachment",
    "label": "attachment",
    "item_type": "post",
    "item_id": null,
    "current_attachments": null,
    "upload_endpoint": "mobile/attachment"
    }
    }
    },
    "additional_info": {
    "label": "Additional Info",
    "fields": {
    "categories": {
    "name": "categories",
    "component_name": "Choice",
    "multiple": true,
    "value": [],
    "label": "Categories",
    "value_type": "array",
    "options": [
    {
    "value": 1,
    "label": "Business"
    },
    {
    "value": 2,
    "label": "Education"
    },
    {
    "value": 3,
    "label": "Entertainment"
    }
    ],
    "suboptions": []
    },
    "tags": {
    "name": "tags",
    "component_name": "Tags",
    "value": "",
    "returnKeyType": "next",
    "label": "Topics",
    "description": "Separate multiple topics with commas."
    },
    "file": {
    "name": "file",
    "component_name": "File",
    "label": "Photo",
    "file_type": "photo",
    "item_type": "post",
    "preview_url": null,
    "upload_endpoint": "mobile/file"
    }
    }
    },
    "settings": {
    "label": "Settings",
    "fields": {
    "privacy": {
    "name": "privacy",
    "component_name": "Privacy",
    "value": 0,
    "returnKeyType": "next",
    "options": [
    {
    "label": "Everyone",
    "value": 0
    },
    {
    "label": "Friends",
    "value": 1
    },
    {
    "label": "Friends of Friends",
    "value": 2
    },
    {
    "label": "Only Me",
    "value": 3
    },
    {
    "label": "Custom",
    "value": 4
    }
    ],
    "label": "Privacy",
    "multiple": false,
    "description": "Control who can see this post"
    }
    }
    }
    },
    "fields": {
    "module_id": {
    "name": "module_id",
    "component_name": "Hidden",
    "value": "post"
    },
    "item_id": {
    "name": "item_id",
    "component_name": "Hidden"
    },
    "draft": {
    "name": "draft",
    "component_name": "Checkbox",
    "value": 0,
    "label": "Save as Draft"
    },
    "submit": {
    "name": "submit",
    "component_name": "Submit",
    "label": "Publish",
    "value": 1
    }
    }
    }
    }|

    DELETE url/restful_api/mobile/post/post_id?access_token=token

    Delete a Post

     

  • No labels