Overwrite API endpoint by addon

Hi, is it possible to overwrite collection API endpoint for one method (get)?
I mean I want to have my own behavior in this api endpoint:

http://localhost:8080/api/collections/get/test?token=xxx

I have created new addon called languages and put this in bootstrap:

if (COCKPIT_API_REQUEST) {
    $this->on('cockpit.rest.init', function ($routes) {
        $routes['collections.get'] = 'languages\\Controller\\RestApi';
    });
}

I also created my own RestApi controller and there I have get method:

namespace languages\Controller;

class RestApi extends \LimeExtra\Controller {

    /**
     * Deprecated! use /entries instead
     */
    public function get($collection = null) {
        return 'test';
        return $this->entries($collection);
    }

Now when I go to: http://localhost:8080/api/collections/get/test?token=xxx
I expect to get to return string ‘test’ from my get method from addon but it not works. Anyone know how can I overwtire default api methods?

You can create complete custom api end points making the overriding of the default api unnecessary.

This is an example for creating an custom endpoint that combines the result of two different collections (references and jobs) to one single call:

  1. create a php file under //config/api. (e.g. “news-jobs-refs.php”)
  2. create an API key for the custom route (https://cockpit-install-url.com/restadmin/index)
  3. Example code for the news-jobs-refs.php:
<?php
 
$data = [];
 
// get collections
foreach (['references', 'jobs'] as $collection_name) {
    $data[$collection_name] = cockpit('collections')->find($collection_name, [
        'filter' => function($doc) { // make only published entries being used
            return $doc['published'];
        },
        'sort' => [
            '_o' => 1
        ]
    ]);
}
 
return $data;
?>

Now your consumer can do something like this:

$data = json_decode(file_get_contents('https://cockpit-install-url.com/api/news-jobs-refs?token=<API TOKEN YOU HAVE GENERATED HERE>'), true);
var_dump($data);

PS. the API token def. (for https://cockpit-install-url.com/restadmin/index) would need to look like this:

Thank you, so there is no chance to overwrite default API endpoint by addons without changes in cockpi core.

I still don’t get why you should want this if you are able to create custom routes. Why can’t you just leave the default api untouched?

Sorry to pick up this old thread but I want to specify a reason and maybe get help doing it.
So, what I’m aiming for is to display results that only the user requesting created. And also targeting roles…

For example a “User” can only see items that himself created but a “Manager” and “Admin” could see the items of every “User”…

1 Like

Yes, this would be extremely useful. To expand: it would first check the the user’s role (if admin/manager then allow), else check user’s ID against the content author’s ID (if true then allow), else block. I will be implementing this myself soon as it looks like nobody here is active anymore to help. I’ll reply with my solution if I ever am successful.

Hey there!

I was able to solve mine by replacing instead of extending.
I disabled the CRUD on all roles and implemented the CRUD creating a folder structure to replace it inside config folder. Like this:

config/
–api/
----content/
------examples.get.php
------example.post.php
------example.get.php
------example/
--------[…all].get.php
--------[…all].delete.php

Please mind the usage of plural and singular wording but that was just for my case. I believe it’s possible to use the same model name changing accordingly!

Also, dont forget to create a Public role (no permissions needed) and assign it to the Public API in “Settings → API & Security → Public API access permissions for unauthenticated requests.”

<----------------->

When a URI parameter is necessary, a folder must be created with php files for the REST verbs inside it.
You may realize that I made two GET files for the same model: “example.get.php” and “example/[…all].get.php”.
That is to completly mimic the original code which replied with the last created item if no ID is provided on the URI parameter.

The code for each file is basically the same as the one on core code (/api/modules/Content/api.php) but with small changes concerning the security I intended.
Some were removed (verification that user can access model data using “isAllowed”) and some were added…

To access the query parameters, I placed the following on the top of the php file:

parse_str($_SERVER[‘QUERY_STRING’], $params);

With that, $params will be an array with all the parameters after the “?” on the URL requested

For the singular model, we also need to use this:

$id = $API_ARGS[0];

This will get you the ID when you provide it in the URI string, before the “?” if used

<----------------->

To filter the response only to the items that were created by the user requesting if they have a “basic” role, I placed the following code right before the system call to the default results:

if ($role == ‘basic’) {
$options[‘filter’][‘_cby’] = $by;
//or this:
//$filter[‘_cby’] = $by;
//depending if is plural or singular model
//analyze the code you copy to be sure
}

That “$by” variable had to be assigned in a differente way because “$app->helper(‘auth’)->getUser()” object didnt provide the user ID.
So, I used this piece of code also on the start of the php file:

$username = $app->helper(‘auth’)->getUser()[‘user’];
$user = $app->dataStorage->findOne(‘system/users’, [‘user’ => $username]);
$by = $user[‘_id’];
$role = $app->helper(‘auth’)->getUser(‘role’);

<----------------->

Because getUser() didnt provide user ID, I also had to change something important on the POST php file (example.post.php).
On the system request from the core code, it goes like this:
$item = $app->module(‘content’)->saveItem($model[‘name’], $data, [‘user’ => $app->helper(‘auth’)->getUser()]);

Since getUser() doesnt have the “_id” field, we have to provide a complete user object like this:
$item = $app->module(‘content’)->saveItem($model[‘name’], $data, [‘user’ => $user]);

With this, the content created will have the “_cby” field correctly filled to be compared on the GETs.

<----------------->

Important to note that, if you copy from the core files, you also need to place this at the start of the php file:

$app = $this;
$model = “examples”;

Because the code on core files is using similar variables on the rest of the code and instead of changing it, we can reuse it :wink:

<----------------->

I also created an auth.php on the root of config/api to handle the login with user/password or API key.
It works good but in terms of security it needs work because the API key wont change regularly unless changed manually.
Probably I can do it programatically at each login/request but for now, I didnt implemented it because of time and being small project without many security concerns.

I saw some JWT code on the core files that indicate that something may be worked on but no implementation for now…

In conclusion, it would be great to have that extend functionality, login through API with user/pass and JWT tokens (and/or cookies) but its also doable with this solutions.

Im sorry for the long post and hope to help somebody that come accross this…

You can contact or reply if you need some help making it happen to your situation!

1 Like