Create custom API Handler ver 3.5

Describe the problem you would like to solve

We are currently working at a open peer review platform for preprints. For this we developed a set of 3 plugins for OPS which depend on a custom API:

1. Open Peer Review plugin (not published yet)
1. customMetadata plugin (GitHub - felixhelix/customMetadata)
2. userComments plugin (not published yet)

The first two plugins use an API to allow editing of data in the submission and publication stage, the third one to allow user interaction on the preprint details page.

Both are essential for our open peer review service to work. However, in OPS 3.5 the option to create custom APIs is discontinued.

Describe the solution you’d like
Touhidur Rahman already created a proof of concept solution Issues · pkp/pkp-lib · GitHub

Who is asking for this feature?
As stated above, the feature is essential for our service to work. The open peer review platform is a project by the University Library of Cologne, funded by the German Research Foundation and realized in cooperation with several associations of social scientists.

@felixhelix after some discussion about this with other dev team members, we have decided to implement this feature and move forward with already created proof of concept . Please keep an eye at github issue Allow plugins to construct and expose own API endpoints · Issue #9434 · pkp/pkp-lib · GitHub for it to get implemented and merged to the core .

Regards
Touhidur Rahman
PKP Team

Thanks so much for your help with that :))

A bit more background details about this feature

This feature/behaviour already exists in the 3.3 and 3.4 release and only been removed in the current main which is upcoming 3.5 . As we refactor all the API routes to use the Laravel routing toolset and removed the Slim framework, the dependent code of this functionality also removed in the current main branch . We had the proof of concept ready immediately but did not find any use case of this functionality, so we did not move forward with it .

After the original forum post at Create API Handler OPS 3.5 which lead to this feature request, we had some discussion within the dev group and quite a few member was interested to bring this back and keep it in the upcoming main . That lead to the decision to bring back and reimplement this functionality which was kind of removed because of a regression.

@felixhelix the work is done but before merging, we would like to get some feedback based on real use case. Can you please check the example plugin at GitHub - touhidurabir/apiExample: An example plugin to show how to implement plugin level API endpoints for OJS/OMP/OPS. Only compatible if https://github.com/pkp/pkp-lib/issues/9434 merged . if that cover up all the use cases that your plugins can have ? Also if interested, check the actual implementation at Allow plugins to add/modify API endpoints of existing entity · Issue #9434 · pkp/pkp-lib · GitHub

Regards
Touhidur Rahman
PKP Team

Thanks @touhidur !
I managed to update our comments plugin, so that the API now works with 4.5 (cherry-picking your commit) :slight_smile:
I did not start with updating our other plugins, however, but I think I can apply this procedure to the other plugins as well.

I also found two other plugins, that make use of a custom API:

One question: It seems to me that you can only override / extend an existing API controller, such as user or submission, but not create your own? Or did you just use one of those for simplicity?

Yours
Felix

One question: It seems to me that you can only override / extend an existing API controller, such as user or submission, but not create your own? Or did you just use one of those for simplicity?

@felixhelix To clarify this part if I understand correctly

What is possible :

  1. Override/Modify an existing api routes on existing entity (e.g. users, submissions, etc) to provide different response or execute different actions . For example, http://BASE_URL/index.php/CONTEXT_PATH/api/v1/users/report provide a report data in CSV format but possible to override that behaviour to get different response or action .

  2. Add one or multiple new route/s on existing entity (e.g. users, submissions, etc) that can handle different actions. For example, the example plugin provide such example like a totally new route on users entity as http://BASE_URL/index.php/CONTEXT_PATH/api/v1/users/testing/routes/add . Note that testing/routes/add does not exist on users entity by default .

What is not possible :
Plugins to have their very plugin specific own routes like http://BASE_URL/index.php/CONTEXT_PATH/plugins/generic/apiExample/api/v1/tests . see that part plugins/generic/apiExample which follows the plugin level convention of PLUGIIN_PREFIX/PLUGIN_CATEGORY/PLUGIN_NAME . We initially have this one included in the initial proof of concept implementation but removed it as it adds a lot of complexity to api router and handler .

If your question related to section What is not possible, then yes, thats not possible right now and removed from initial proof of concept . But if there is any use case which can be highly benefitted from it, we would love to have know about it .

Regards
Touhidur Rahman
PKP Team

@touhidur Thanks for your detailed reply! Yes, my question related to the section “what is not possible”: If I have a use case for this I will let you know :slight_smile:
Yours,
Felix

@felixhelix We can assume that the current proposed implementation at https://github.com/pkp/pkp-lib/issues/9434 satisfies the all the use cases, right ?

@touhidur As far as I can see, yes :slight_smile:

Dear @touhidur , I just tested the userComment plugin with the stable 3.5 version and was surprised by the fact that the api override mechanism for plugins has been removed with commit pkp/pkp-lib#11609 Remove API controller override mechanism for plugin… · pkp/pkp-lib@9479118 · GitHub

Why? And is the way to go to add routes on the fly - or will that option also be removed?

Yours,

Felix

Hi @felixhelix , yes the ability to override the API controller itself has been removed , details is at Remove API controller override mechanism for plugin · Issue #11609 · pkp/pkp-lib · GitHub . Right now the only way to add routes via the closure based approach e.g.

Hook::add('APIHandler::endpoints::users', function(string $hookName, PKPBaseController $apiController, APIHandler $apiHandler): bool {

    $apiHandler->addRoute(
        'GET/POST/PUT/PATCH/DELETE', // HTTP Request METHOD
        'some/route/path/to/add',   // The route uri
        function (IlluminateRequest $request): JsonResponse { // The closure/callback of route action handler when the route url got hit
            return response()->json([
                'message' => 'A new route added successfully',
            ], Response::HTTP_OK);
        },
        'name.of.the.route', // Name of the route
        [Role::ROLE_ID_..., Role::ROLE_ID_..., ...] // The route accessable role from `Role::ROLE_ID_*`
    );
    
    return Hook::CONTINUE;
});

one downside of this approach is that is no way to add authorization policies which has been addressed at Add ability for plugin to attach authorization policies to API controller · Issue #11631 · pkp/pkp-lib · GitHub and will be available with 3.5.0-2 release .

so for now, only can add API routes via injecting into the current routes stack via `$apiHandler->addRoute` but without any authorization policies and that will be available from 3.5.0-2 .

Regards

PKP Dev Team

Thanks for your reply :slight_smile: I will change the code accordingly.

Hey @touhidur,

I am helping developing a plugin for OJS 3.5.x and have the same need for a custom API endpoint with sub-endpoints. So in short we would like to have something like api/v1/ourPlugin/someSubRoute/. As far as I understand it here, a very own custom API endpoint like this is not possible to achieve in the current version of OJS 3.5.x. Only something like e.g. api/v1/users/ourPlugin/someSubRoute/ or api/v1/submission/ourPlugin/someSubRoute/.
Before I got to know that this works, I developed a workaround to have our very own API route with a LoadHandler that displays different JSON data on a page, based on what route it was called on. Therefore we are able to have our own API routes like e.g. .../index.php/ourPlugin-api/someSubRoute/. The problem with this workaround is that while it works, we are unsure if we are allowed to use it as it e.g. doesn’t restrict access to specific API routes to specific Roles.

Now for our question. We understand, that having an own API endpoint is not really possible in OJS 3.5.x but we would prefer it over having something like users/ourRoute or submission/ourRoute. We however don’t want to have our plugin criticised in later stages of development for using our own custom API workaround. That’s why we want to decide on which approach we use as soon as possible, as we are still in the early stages of development. Therefore I would like to know if it is good to continue with our own workaround for our own API endpoint or if we should switch to the way it is described in this forum topic and thus use something like users/ourRoute or submission/ourRoute?

Hi, a bit background, we initially planned to have a plugin level API routes as `http://BASE_URL/index.php/CONTEXT_PATH/plugins/PLUGIN_CATEGORY/PLUGIN_NAME/api/VERSION/ENTITY` as part of Allow plugins to add/modify API endpoints of existing entity · Issue #9434 · pkp/pkp-lib · GitHub but later decided to remove it (see the commit pkp/pkp-lib#9434 Allow plugin level API call by touhidurabir · Pull Request #9451 · pkp/pkp-lib · GitHub where it was removed) as we did not find any usage of it and it also add quite a bit of complexity to API routing implementation.

doesn’t restrict access to specific API routes to specific Roles.

Using the LoadHandler or the LoadComponentHandler, it should be possible to define role level access restriction, for example see the implementation of plagiarism/controllers/PlagiarismIthenticateHandler.php at main · pkp/plagiarism · GitHub . so I am bit confused why it does not work for you (perhaps I am misunderstanding something or actual use case) .

Therefore I would like to know if it is good to continue with our own workaround for our own API endpoint or if we should switch to the way it is described in this forum topic and thus use something like users/ourRoute or submission/ourRoute

for 3.5, if the LoadHandler or the LoadComponentHandler does not work, it suggest to stick with the conventional API routing approach but must make sure to have the proper authorization policies attached to it . Initially we planned to add a API Controller Override approach which later removed as part of issue Remove API controller override mechanism for plugin · Issue #11609 · pkp/pkp-lib · GitHub and we introduced a new mechanism as part of Add ability for plugin to attach authorization policies to API controller · Issue #11631 · pkp/pkp-lib · GitHub which will be available with the release of `3.5.0-2` to have better approach to have authorization policies for API routes . An example plugin I developed at GitHub - touhidurabir/apiExample: An example plugin to show how to implement plugin level API endpoints for OJS/OMP/OPS. about how to use it .

Thank you for your thorough in-depth reply! :grinning_face_with_smiling_eyes:

Using the LoadHandler or the LoadComponentHandler, it should be possible to define role level access restriction, for example see the implementation of plagiarism/controllers/PlagiarismIthenticateHandler.php at main · pkp/plagiarism · GitHub .

I didn’t know it was possible to define role level access restriction so thank you and I will implement this.

for 3.5, if the LoadHandler or the LoadComponentHandler does not work, it suggest to stick with the conventional API routing approach but must make sure to have the proper authorization policies attached to it.

I understand this as follows: If our workaround with the LoadHandler works then we can continue using it and should only switch to the conventional API routing approach if we run into further issues correct?

Thank you again for taking your time and your reply! :slight_smile:

If our workaround with the LoadHandler works then we can continue using it and should only switch to the conventional API routing approach if we run into further issues correct?

that is if need an API route url that does not match for with entity paths like users, submission etc as mentioned .

my suggestion as follow

  1. if need API route and is useable with currently supported entity paths, use that with conventional approach and implementation.
  2. if need API route but does not match with existing entity path or really don’t want to use that , use LoadHandler or the LoadComponentHandler .
  3. for other purpose , use the LoadHandler or the LoadComponentHandler

For API routes, I suggest to stick with No 1 unless it’s really not feasible or really does not match the use case.

Most importantly , make sure that each of the usage case have proper Role/Policy attached to avoid any security/unauthorised issue/access introduced by the plugin .

BTW, we have a discussion open at How should plugins create API endpoints · pkp/pkp-lib · Discussion #11991 · GitHub which is related to plugins getting the ability to have own custom API routes.

Regards

PKP Dev Team

thank you very much :slight_smile:

my suggestion as follow

  1. if need API route and is useable with currently supported entity paths, use that with conventional approach and implementation.
  2. if need API route but does not match with existing entity path or really don’t want to use that , use LoadHandler or the LoadComponentHandler .

I will discuss with my other team members if we will use approach 1 or 2 based on your suggestions!

BTW, we have a discussion open at How should plugins create API endpoints · pkp/pkp-lib · Discussion #11991 · GitHub which is related to plugins getting the ability to have own custom API routes.

That discussion is actually very interesting and useful for us so we will keep an eye out on this, thank you!

Kind Regards

Linus