How to design API around dynamic endpoints? - node.js

I am designing an API wrapper based off of an existing wrapper for to interface with a third-party service.
There are two methods for public and private requests, and to determine which to use the API method checks to see if the endpoint is in either the public or private array of the methods object:
function api(method, params, callback) {
var methods = {
public: ['countrycodes', 'payment_methods/CA'],
private: ['ad-get', 'ad-get/ad_id', 'myself',
'dashboard', 'dashboard/released', 'dashboard/canceled', 'dashboard/closed',
'dashboard/released/buyer', 'dashboard/canceled/buyer', 'dashboard/closed/buyer',
'dashboard/released/seller', 'dashboard/canceled/seller', 'dashboard/closed/seller',
'wallet-send'
]
};
if(methods.public.indexOf(method) !== -1) {
return publicMethod(method, params, callback);
}
else if(methods.private.indexOf(method) !== -1) {
return privateMethod(method, params, callback);
}
The problem I am running into is that there are parameters that need to be added to the URL, for example you can see : 'payment_methods/CA'. If I added all the countries there would be 50.
Also, you can see with the 'dashboard/canceled/buyer' methods, this is not a very elegant design.
I have a couple ideas, but they are not very straightforward. What's a logical and elegant way to allow parameters to be added to the path but still check that the method is valid?

Related

Automatically parse query parameter to object when defined in NestJS

I am writing a NestJS application. Some of the endpoints support sorting e.g. http://127.0.0.1:3000/api/v1/members?sort=-id&take=100 Which means sort by id descending.
This parameter arrives as a #Query parameter and is passed to my service. This service transforms it into an object which is used by TypeORM:
{
id: 'DESC'
}
I don't want to call this conversion method manually every time I need sorting.
I've tried an intercepter but this one could not easily change the request parameters into the desired object.
A pipe worked but then I still need to add #Query(new SortPipe()) for every endpoint definition.
Another option is in the repository itself. The NestJS documentation is very well written, but misses guidance in where to put what.
Is there someone who had a similar issue with converting Query parameters before they are used in NestJS, and can explain what approach is the best within NestJS?
This question might look like an opinion based question, however I am looking for the way it is supposed to be done with the NestJS philosophy in mind.
Pipes are probably the easiest way to accomplish this. Instead of adding your pipe for every endpoint definition you can add a global pipe that will be called on every endpoint. In your main.ts:
async function bootstrap() {
...
app.useGlobalPipes(new SortPipe());
...
}
You can then create a pipe like this:
import { PipeTransform, Injectable, ArgumentMetadata } from '#nestjs/common';
#Injectable()
export class SortPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const { type } = metadata;
// Make sure to only run your logic on queries
if (type === 'query') return this.transformQuery(value);
return value;
}
transformQuery(query: any) {
if (typeof query !== 'object' || !value) return query;
const { sort } = query;
if (sort) query.sort = convertForTypeOrm(sort);
return query;
}
}
If you do not want sort value on ALL endpoints to be automatically converted, you can pass custom parameter to #Query(), for example #Query('sort'). And then:
transform(value: any, metadata: ArgumentMetadata) {
const { type, data } = metadata;
// Make sure to only run your logic on queries when 'sort' is supplied
if (type === 'query' && data === 'sort') return this.transformQuery(value);
return value;
}

Breeze & EFContextProvider - How to properly return $type when using expand()?

I am using Breeze with much success in my SPA, but seem to be stuck when trying to return parent->child data in a single query by using expand().
When doing a single table query, the $type in the JSON return is correct:
$type: MySPA.Models.Challenge, MySPA
However if I use expand() in my query I get the relational data, but the $type is this:
System.Collections.Generic.Dictionary 2[[System.String, mscorlib],[System.Object, mscorlib]]
Because of the $type is not the proper table + namespace, the client side code can't tell that this is an entity and exposes it as JSON and not a Breeze object (with observables, entityAspect, etc.).
At first I was using my own ContextProvider so that I could override the Before/After saving methods. When I had these problems, I reverted back to the stock EFContextProvider<>.
I am using EF5 in a database first mode.
Here's my controller code:
[BreezeController]
public class DataController : ApiController
{
// readonly ModelProvider _contextProvider = new ModelProvider();
readonly EFContextProvider<TestEntities> _contextProvider = new EFContextProvider<TestEntities>();
[HttpGet]
public string Metadata()
{
return _contextProvider.Metadata();
}
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
[HttpGet]
public IQueryable<Challenge> Challenges()
{
return _contextProvider.Context.Challenges;
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
public IQueryable<ChallengeNote> ChallengeNotes()
{
return _contextProvider.Context.ChallengeNotes;
}
}
Here's my BreezeWebApiConfig.cs
public static void RegisterBreezePreStart()
{
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "breeze/{controller}/{action}"
);
}
Is there a configuration setting that I am missing?
Did you try "expanding" on server side? Is it needed to do expand on client side? I tried to do expand before but failed for me as well, did some research and decided I'd rather place it on server:
[HttpGet]
public IQueryable<Challenge> ChallengesWithNotes()
{
return _contextProvider.Context.Challenges.Include("ChallengeNotes");
}
This should be parsed as expected. On client side you would query for "ChallengeNotes" instead of "Challenges" and you wouldn't need to write expand part.
I strongly suspect that the problem is due to your use of the [Queryable] attribute.
You must use the [BreezeQueryable] attribute instead!
See the documentation on limiting queries.
We are aware that Web API's QueryableAttribute has been deprecated in favor of EnableQueryAttribute in Web API v.1.5. Please stick with BreezeQueryable until we've had a chance to write a corresponding derived attribute for EnableQuery. Check with the documentation for the status of this development.

How to give security voter access to current object

I want to use a Voter to only allow owners to edit a project object in my application.
I have a route /project/42/edit that invokes my action ProjectController.editAction(Project $project). I use a type hint (Project $project) to automatically invoke the ParamConverter to convert the ID 42 from the URI into a project object. This works nicely for the controller action, however it seems to be invoked too late for the voter. Its vote() method gets called with the request as 2nd parameter, not my project.
Is there a way to pass the project to the voter without having to retrieve it from the database again?
UPDATE: I learned that I have to manually call isGranted() on the security context in the edit method. This is very similar in approach to this answer.
Here is my Voter:
namespace FUxCon2013\ProjectsBundle\Security;
use FUxCon2013\ProjectsBundle\Entity\Project;
use Symfony\Component\BrowserKit\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class OwnerVoter implements VoterInterface
{
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function supportsAttribute($attribute)
{
return $attribute == 'MAY_EDIT';
}
public function supportsClass($class)
{
// your voter supports all type of token classes, so return true
return true;
}
function vote(TokenInterface $token, $object, array $attributes)
{
if (!in_array('MAY_EDIT', $attributes)) {
return self::ACCESS_ABSTAIN;
}
if (!($object instanceof Project)) {
return self::ACCESS_ABSTAIN;
}
$user = $token->getUser();
$securityContext = $this->container->get('security.context');
return $securityContext->isGranted('IS_AUTHENTICATED_FULLY')
&& $user->getId() == $object->getUser()->getId()
? self::ACCESS_GRANTED
: self::ACCESS_DENIED;
}
}
I register this in configure.yml so that it gets the service container as parameter:
services:
fuxcon2013.security.owner_voter:
class: FUxCon2013\ProjectsBundle\Security\OwnerVoter
public: false
arguments: [ #service_container ]
tags:
- { name: security.voter }
The last block is to configure the access decision manager in security.yml to unanimous:
security:
access_decision_manager:
# strategy can be: affirmative, unanimous or consensus
strategy: unanimous
allow_if_all_abstain: true
Please have a look at this answer i have written yesterday.
You can easily adapt it to your needs by checking for the owner of your object.
The current object doesn't get passed in the voter if you use the role security handler.
I had to extend the latter to get the former.
Don't hesitate to comment for details.

Why does ServiceStack's New API promote an "object" return type rather than something more strongly typed?

For example, if I have standard request and response DTOs, linked up via IReturn<T>, what are the reasons to have a service method signature like the following, as seen in various online examples (such as this one, although not consistently throughout):
public object Get(DTO.MyRequest request)
rather than:
public IList<DTO.MyResponse> Get(DTO.MyRequest request)
Is an object return type here simply to support service features like gzip compression of the output stream, which results in the output being a byte array? It seems that one would want to have the appropriate stronger return type from these so-called "action" calls, unless I'm missing some common scenario or use case.
It used to be a limitation that the New API only supported an object return type, but that hasn't been the case for a while where all examples on the New API wiki page now use strong-typed responses.
One of the reasons where you might want to return an object return type is if you want to decorate the response inside a HttpResult, e.g:
public object Post(Movie movie)
{
var isNew = movie.Id == null;
Db.Save(movie); //Inserts or Updates
var movie = new MovieResponse {
Movie = Db.Id<Movie>(newMovieId),
};
if (!isNew) return movie;
//Decorate the response if it was created
return new HttpResult(movie) {
StatusCode = HttpStatusCode.Created,
Headers = {
{ HttpHeaders.Location, Request.AbsoluteUri.CombineWith(movieId) }
}
};
}
It's also useful if you want to return different responses based on the request (though it's not something I recommend), e.g:
public object Get(FindMovies request)
{
if (request.Id != null)
return Db.Id<Movie>(movie.Id);
return Db.Select<Movie>();
}
If you do choose to return an object I highly recommend decorating your Request DTO with the IReturn<T> marker to give a hint to ServiceStack what the expected response of the service should be.

ServiceStack - Request Binding JSON encoded parameter

I have an existing application that sends a Request with a parameter named 'filters'. The 'filters' parameter contains a string that is JSON encoded. Example:
[{"dataIndex":"fieldName", "value":"fieldValue"}, {"dataIndex":"field2", "value":"value2"}].
Using ServiceStack, I would like to bind this as a property on a C# object (class Grid). Is there a preferred method to handle this? Here are the options I can think of. I don't think either 'feel' correct.
Option 1:
I do have a 'ServiceModel' project and this would create a dependency on it which I don't really like.
In AppHost.Configure() method add
RequestBinders[typeof(Grid)] => httpReq => {
return new Grid() {
Filters = new ServiceStack.Text.JsonSerializer<IList<Filter>>().DeserializeFromString(httpReq.QueryString["filters"])
}
}
Option 2:
Seems kind of 'hacky'
public class Grid
{
private string _filters;
public dynamic Filters {
get
{
ServiceStack.Text.JsonSerializer<IList<Filter().DeserializeFromString(_filters);
}
set
{
_filters = value;
}
}
}
You can send Complex objects in ServiceStack using the JSV Format.
If you want to send JSON via the QueryString you can access it from inside your Service of Request filters with something like:
public object Any(Request req) {
var filters = base.Request.QueryString["Filters"].FromJson<List<Filter>>();
}
Note: Interfaces on DTOs are bad practice.

Resources