Loopback 4 The swagger generated filters don't seem to work - loopbackjs

I set up a basic model and controller connected to mysql database. Everything looks good and is working.
My Definition Model looks like this.
import { Entity, model, property } from '#loopback/repository';
#model({
settings: {
mysql: {
table: 'definition'
}
}
})
export class Definition extends Entity {
#property({
type: 'number',
id: true,
generated: true,
forceId: true,
required: false,
description: 'The unique identifier for a definition',
})
id: number;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
required: true,
})
process_id: string;
constructor(data?: Partial<Definition>) {
super(data);
}
}
export interface DefinitionRelations {
// describe navigational properties here
}
export type DefinitionWithRelations = Definition & DefinitionRelations;
When I have it up and running and click on the explorer to test it out.
I click on "Try it out" for get /definitions and the only editable field that gets enabled is the filter field.
It is repopulated with a value like this...
{
"where": {},
"fields": {
"id": true,
"name": true,
"process_id": true
},
"offset": 0,
"limit": 0,
"skip": 0,
"order": [
"string"
]
}
I have to clear that to get it to work which is fine. When I run it with no filter it returns these results.
[
{
"id": 10,
"name": "Place Manual Payoff Order Process Config",
"process_id": "Process_PlaceManualPayoffOrderProcessConfig"
},
{
"id": 11,
"name": "test",
"process_id": "Test"
},
{
"id": 12,
"name": "test2",
"process_id": "test2"
}
]
I am trying to use the filter expression to only return ones with a specific process_id field. So I change the filter to look like this.
{
"where": {"process_id": "test2"}
}
And it still returns the same results.
[
{
"id": 10,
"name": "Place Manual Payoff Order Process Config",
"process_id": "Process_PlaceManualPayoffOrderProcessConfig"
},
{
"id": 11,
"name": "test",
"process_id": "Test"
},
{
"id": 12,
"name": "test2",
"process_id": "test2"
}
]
Do filters currently work in Loopback 4 or am I using them incorrectly?
EDIT: if I post the filters in the URL string they work. It seems as though the openapi ui isn't generating that part of the filter Into the URL string.

It seems as though the openapi ui isn't generating that part of the filter Into the URL string.
Yes, this a precise description.
OpenAPI Spec version 3.x does not specify how to serialize deeply-nested values to URL queries and swagger-js, the library powering swagger-ui (which powers LoopBack's REST API Explorer), silently ignores such values.
The issue has been reported and is being discussed here:
swagger-api/swagger-js#1385
OAI/OpenAPI-Specification#1706

Related

MPGS (mastercard): How to tokenize a transaction (how to create token)?

I'm trying to create token with MPGS.
I'm following this guide:
https://sample-sub.domain.mastercard.com/api/documentation/integrationGuidelines/supportedFeatures/pickAdditionalFunctionality/tokenization/tokenization.html?locale=en_US#x_tokenConfiguration
In the section "Token Operations" > "Tokenize", it says:
You can use this operation to create or update a token by storing
payment details against the token. ...
I'm posting this to help people who are frustrating like me with MPGS. I faced series of issues, and pulled my hair off many times. So here's the issues I faced and how to solve them (I'm stuck with issue #4).
Issue #1: Invalid credentials.
Fix: Make sure you're hitting the correct URL.
https://example-subdomain.mastercard.com/..
https://some.other-example.mastercard.com/..
https://MILLION-OTHER-POSSIBILITIES.mastercard.com/..
Even the documentation guide link have these same sub-domains, so make sure you're hitting the correct URL, and make sure you're following the correct documentation link.
Issue #2: Invalid parameters, or server asking for parameters although you've provided them.
Fix: If using Postman, make sure you set the parameters in "Body" > "raw" as JSON, like so:
{
"sourceOfFunds": {
"provided": {
"card": {
"expiry": {
"month": "05",
"year": "21"
},
"number": "5123456789012346"
}
},
"type": "CARD"
}
}
Issue #3: Authorization required
Fix: If using Postman, click on "Authorization", set "Type" it to Basic Auth, for "Username" set it to merchant.YOUR_MERCHANT_ID, for "Password" set it to YOUR_API_PASSWORD
Issue #4 (stuck here): Value '9999999999999999' is invalid. Card token must not be supplied
Method: PUT
URL: https://test-my.sample.gateway.mastercard.com/api/rest/version/46/merchant/MY_MERCHANT_ID/token/9999999999999999
Authorization: set correctly in Authorization tab
Body > raw:
{
"sourceOfFunds": {
"provided": {
"card": {
"expiry": {
"month": "05",
"year": "21"
},
"number": "5123456789012346"
}
},
"type": "CARD"
}
}
Response:
{
"error": {
"cause": "INVALID_REQUEST",
"explanation": "Value '9999999999999999' is invalid. Card token must not be supplied",
"field": "tokenid",
"validationType": "INVALID"
},
"result": "ERROR"
}
Q: Not sure what to do to tokenize the transaction..?! I'm stuck with issue #4.
Ok, finally figured it out. MPGS has 2 ways to create/update tokens:
Tokenization where YOU provide the token (notice: PUT method)
Tokenization where MPGS generate the token for you (notice: POST method)
They're very similar.
I got it working with the 2nd option.
Note: This is POST method !!
Method: POST
URL: https://SUBDOMAIN_YOU_SHOULD_BE_USING.mastercard.com/api/rest/version/50/merchant/YOUR_MERCHANT_ID/token
In postman, set Authorization (as described in the question, in issue #3).
Sample data to send (in postman, this should be in Body > raw):
{
"sourceOfFunds": {
"provided": {
"card": {
"expiry": {
"month": "05",
"year": "21"
},
"number": "5123456789012346"
}
},
"type": "CARD"
}
}
Sample response:
{
"repositoryId": "1000000000002",
"response": {
"gatewayCode": "BASIC_VERIFICATION_SUCCESSFUL"
},
"result": "SUCCESS",
"sourceOfFunds": {
"provided": {
"card": {
"brand": "MASTERCARD",
"expiry": "0521",
"fundingMethod": "CREDIT",
"issuer": "BANCO DEL PICHINCHA, C.A.",
"number": "512345xxxxxx2346",
"scheme": "MASTERCARD"
}
},
"type": "CARD"
},
"status": "VALID",
"token": "9717501974559694",
"usage": {
"lastUpdated": "2019-02-25T09:36:54.928Z",
"lastUpdatedBy": "1015",
"lastUsed": "2019-02-25T09:36:54.928Z"
},
"verificationStrategy": "BASIC"
}

How to make Swashbuckle generate correct OpenAPI schema for a Dictionary parameter?

I have an ASP.NetCore action method that is defined as:
[HttpGet]
public async Task<IActionResult> Get([FromQuery]Dictionary<string,string> values)
{
return Ok(JsonConvert.SerializeObject(values));
}
The expected query would be something like:
http://localhost:36541/api?values[someProperty]=123&values[someOther]=234
When I use Swashbuckle the resulting swagger.json file would end up like:
{
"swagger": "2.0",
"info": {
"version": "v1",
"title": "Test API"
},
"paths": {
"/api/Api": {
"get": {
"tags": [
"Api"
],
"operationId": "ApiApiGet",
"consumes": [],
"produces": [],
"parameters": [{
"name": "values",
"in": "query",
"required": false,
"type": "object"
}],
"responses": {
"200": {
"description": "Success"
}
}
}
}
},
"definitions": {}
}
But this doesn't validate using autorest or http://editor.swagger.io/
The error is:
Schema error at paths['/api/Api'].get.parameters[0]
should NOT have additional properties
additionalProperty: type, name, in, required
Jump to line 14
Schema error at paths['/api/Api'].get.parameters[0].required
should be equal to one of the allowed values
allowedValues: true
Jump to line 14
Schema error at paths['/api/Api'].get.parameters[0].in
should be equal to one of the allowed values
allowedValues: body, header, formData, path
Jump to line 15
Schema error at paths['/api/Api'].get.parameters[0].type
should be equal to one of the allowed values
allowedValues: string, number, boolean, integer, array, file
Jump to line 17
It seems that it's missing the additionalProperties property according to https://swagger.io/docs/specification/data-models/dictionaries/
How can I make this query parameter be a valid OpenAPI / Swagger definition?
Following up on the comment from #Helen
Swashbuckle does not support the OpenAPI Specification (OAS) 3.0 yet, so you should not have objects (such as dictionaries) on the query string.
My recommendation change that action to a Post and get the values from the Body, also since your response is IActionResult consider using SwaggerResponse something like this:
[HttpPost]
[SwaggerResponse(200, typeof(Dictionary<string, string>), "This returns a dictionary.")]
public async Task<IActionResult> Post([FromBody]Dictionary<string,string> values)
{
return Ok(JsonConvert.SerializeObject(values));
}
Update:
I was also looking into the possibility of sending the GET with a body, and there seems to be a lot of debate about it:
HTTP GET with request body

AppSync loading incorrect resolver for field

AppSync appears to be loading the incorrect resolver template for some fields of nested objects. Also, it appears to only happen when the nested object has a field with the same name as a field on the parent object.
I've included an example below because I think that might be the best way to explain the issue. As you can see, the id fields for the nested objects are not being resolved correctly.
Each type, Task, User, List, and Tag, have a resolver for their id field because the data for each has a prefix on the id field. For example, Task.id has a resolver that returns $context.source.task_id and User.id has a resolver that returns $context.source.user_id. Same for List and Tag.
What appears to be happening is AppSync is loading the id resolver template for the parent type. You can see that this is the case for task.owner.id, where owner is a User but the id gets resolved as "$context.source.task_id". Same for task.list.id where list is a List. Again we can see this for task.tags[0].owner.id. owner is once again a User except this time the parent is a Tag so task.tags[0].owner.id is resolved as "$context.source.tag_id". These three example show that the problem is not with a particular type since User and List are behaving the same when they are nested in a Task. Also, we can see that the issue is not with Task since User is behaving similarly when nested in a Tag. Lastly, we can see that task.tags[1].owner.id actually behaves correctly. This indicates that the issue only presents itself on first execution.
At this point I strongly suspect this is a bug with AppSync however, I'm not 100% on that. Has anyone else experienced this issue? Am I doing something terribly wrong?
Example
Query
{
task(id: "task-123") {
id,
title,
owner {
id,
username,
},
list {
id,
name,
},
tags {
id,
name,
owner {
id,
username,
},
},
},
}
Result
{
"data": {
"task": {
"id": "task-123",
"title": "First Task",
"owner": {
"id": "$context.source.task_id",
"username": "tom"
},
"list": {
"id": "$context.source.task_id",
"name": "Inbox"
},
"tags": [
{
"id": "tag-123",
"name": "one",
"owner": {
"id": "$context.source.tag_id",
"username": "tom"
}
},
{
"id": "tag-234",
"name": "two",
"owner": {
"id": "user-123",
"username": "tom"
}
}
]
}
}
}
Task Schema
type Task {
id: ID!
title: String!
owner: User!
list: List
tags: [Tag]
}
User Schema
type User {
id: ID!
username: String!
}
List Schema
type List {
id: ID!
name: String!
}
Tag Schema
type Tag {
id: ID!
name: String!
owner: User!
}
Task Data
{
task_id: "task-123",
title: "First Task",
owner_id: "user-123",
list_id: "list-123",
tags: [
"tag-123",
"tag-234"
]
}
User Data
{
user_id: "user-123",
username: "tom"
}
List Data
{
list_id: "list-123",
name: "Inbox"
}
Tag Data
{
tag_id: "tag-123",
name: "one",
owner_id: "user-123"
}
{
tag_id: "tag-234",
name: "two",
owner_id: "user-123"
}
Example id resolver (User)
Request Mapping Template
{
"version": "2017-02-28",
"payload": "$context.source.user_id"
}
Response Mapping Template
$util.toJson($context.result)
The id resolvers for the other types are very similar
It may also be worth noting that I created different None Data Sources for each type, Task, User, List, and Tag. The id resolver for each type is using their respective None Data Source.

How to get Ember to Lazy (async) Load hasMany

Possibly I'm misunderstanding how Ember wants to lazy load hasMany sides of relationships but I'll specify what I want and someone can tell me if it is possible and how to configure things.
I'm using the RESTAdapter and I have a parent object with a 1-to-many relationship with several other objects. When I view the parent, I have links for the children objects that I want to show as child outlets.
Examples might be:
// brand.js
export default Model.extend({
name: attr('string'),
description: attr('string'),
dateCreated: attr('date'),
lastUpdated: attr('date'),
regions: hasMany('region', {async: true})
});
// region.js
export default Model.extend({
name: attr('string'),
dateCreated: attr('date'),
lastUpdated: attr('date'),
brand: belongsTo()
});
When I access /api/brands/1 I'm returning the following JSON:
{
"brand": {
"id": 1,
"dateCreated": 1466456255539,
"lastUpdated": 1466456255936,
"name": "Some Brand",
"description": "Voluptates odio nemo corrupti",
}
}
I have my route defined like so:
this.route('brand', {path: '/brands/:brand_id'}, function() {
this.route('regions');
});
So, in the brand detail screen, I have an {{outlet}} and when I click on the link for regions, I need to fetch the regions for that brand, and the URL would be /api/brands/1/regions.
Everything works if I return all the data in one big result, and my regions router looks like this:
export default Ember.Route.extend({
model() {
return this.modelFor('brand').get('regions');
}
});
But I don't know what to do so that regions gets lazily fetched correctly from my API. Or if it is even possible. I read up on side loading and currently, my API won't return ID's only for children.
With your example, ember.js does not know, what regions to load. Your JSON has to be either (non-embedded):
{
"brand": {
"id": 1,
"dateCreated": 1466456255539,
"lastUpdated": 1466456255936,
"name": "Some Brand",
"description": "Voluptates odio nemo corrupti",
"regions": ["5", "9"]
}
}
Which then loads regions with the id 5 and 9. Or you could embed them directly:
{
"brand": {
"id": 1,
"dateCreated": 1466456255539,
"lastUpdated": 1466456255936,
"name": "Some Brand",
"description": "Voluptates odio nemo corrupti",
"regions": [{
"id": "5",
"name": "Hanover",
"dateCreated": "2016-09-25 18:39:18",
"lastUpdated": "2016-09-25 18:39:18",
},{
"id": "9",
"name": "Cologne",
"dateCreated": "2016-01-19 11:59:18",
"lastUpdated": "2016-02-22 12:09:58",
}]
}
}
See here for the latter.

Strongloop with Emberjs

Ember Data's REST Adapter accepts the JSON from the server in this format:
Taken from the documentation: http://guides.emberjs.com/v1.10.0/models/the-rest-adapter/
{
"post": {
"id": 1,
"title": "Node is not omakase",
"comments": [1, 2, 3]
},
"comments": [{
"id": 1,
"body": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"body": "I for one welcome our new omakase overlords"
},
{
"id": 3,
"body": "Put me on the fast track to a delicious dinner"
}]
}
Is it possible to have this kind of JSON format back from strongloop?
Remote methods are not the best solution because they are per model, and thus not DRY.
You can make Ember-data compatible with Strongloop's loopback api by using the DS.RESTAdapter with DS.JSONSerializer like this:
// app/adapters/application.js
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://loopback-api-host',
namespace: 'api',
defaultSerializer: 'JSONSerializer'
});
http://emberjs.com/api/data/classes/DS.JSONSerializer.html
"In Ember Data, the logic for communicating with a backend data store lives in the Adapter. Ember Data's Adapter has some built-in assumptions of how a REST API should look. If your backend conventions differ from these assumptions Ember Data makes it easy to change its functionality by swapping out or extending the default Adapter."
http://guides.emberjs.com/v2.0.0/models/customizing-adapters/
Similar question:
Making Loopback API Ember.js compatible
By default the out-of-box restful api endpoints would return something that looks more like:
{
"id": 1,
"title": "Node is not omakase",
"comments": [
{
"id": 1,
"body": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"body": "I for one welcome our new omakase overlords"
},
{
"id": 3,
"body": "Put me on the fast track to a delicious dinner"
}
]
}
But you can use remote methods to do the same work and then massage the data into the way you want it to be returned. http://docs.strongloop.com/display/public/LB/Remote+methods