Type of return data in API platform - api-platform.com

I have a problem of changing types of returned data in API platform:
I have an entity:
final class ModelClass
{
/**
* #var float
*/
public $total;
}
And a configuration:
ModelClass:
properties:
total:
attributes:
swagger_context:
type: float
And Controller:
public function __invoke(CustomRequest $request): Paginator
{
return $this->service->getTotals($request);
}
The return of this is Paginator, which holds custom doctrine query, result of which looks like this:
{
"#type": "hydra:Collection",
"hydra:member": [
{
"id": 1,
"total": "120.00",
},
]
}
As you see, total is a string (because in the result of query it is a string). What i want it to be: a float: "total": 120.00. And what i would also like to be able to do, is to format it differently, for example separator sign ',' instead '.'
I didnt find in documentation how to do it. Is it a missing documentation or missing feature?
I would expect that API platform reads DocBlock to understand the type of Model. And i think that there is some intercept mechanism after query is executed, but before response is sent back to client, so i could change format/type.
Thank you.

Related

Error in mutation nuwave/lighthouse:^5.0 and input

I am using nuwave/lighthouse:^5.0, and I am trying to create a mutation for an entity which have a belongsTo relationship. The thing is that in my input I am using a sanitizer directive to transform from string to id, but after that when Laravel gets the properties, it shows errors with the validation of the class. In addition, I debug the directive code and it works correctly.
Error
"errors": [
{
"message": "The given data was invalid.",
"extensions": {
"validation": {
"content_type_id": [
"The content type id field is required."
]
},
"category": "validation"
},
Input
input CreateContentInput {
content_type: CreateContentTypeBelongsTo!
.....
input CreateContentTypeBelongsTo {
connect: ID! #typeuuid(model: "App\\ContentType")
create: CreateContentTypeInput
update: UpdateContentTypeInput
}
Model
class Content extends Model
{
protected $rules = [
'content_type_id' => 'required|integer|is_main_content_type',
];
/**
* #return BelongsTo
*/
public function contentType(): BelongsTo
{
return $this->belongsTo(ContentType::class);
}
Any idea will be appreciated
Finally, after some days, I found the issue.
The error come from the main Input definition:
input CreateContentInput {
content_type: CreateContentTypeBelongsTo!
}
I was following a company standard that says that we need to use the properties always in ** snake case ** although they are relationships. So looks like Lighthouse uses always ** camel case ** for relationships.
The solution was add the ** rename ** property to the input. So the right input should be:
input CreateContentInput {
content_type: CreateContentTypeBelongsTo! #rename (attribute: "contentType")
}
I hope this could help someone else.

Is there a way to get a structure of a Strapi CMS Content Type?

A content-type "Product" having the following fields:
string title
int qty
string description
double price
Is there an API endpoint to retrieve the structure or schema of the "Product" content-type as opposed to getting the values?
For example: On endpoint localhost:1337/products, and response can be like:
[
{
field: "title",
type: "string",
other: "col-xs-12, col-5"
},
{
field: "qty",
type: "int"
},
{
field: "description",
type: "string"
},
{
field: "price",
type: "double"
}
]
where the structure of the schema or the table is sent instead of the actual values?
If not in Strapi CMS, is this possible on other headless CMS such as Hasura and Sanity?
You need to use Models, from the link:
Link is dead -> New link
Models are a representation of the database's structure. They are split into two separate files. A JavaScript file that contains the model options (e.g: lifecycle hooks), and a JSON file that represents the data structure stored in the database.
This is exactly what you are after.
The way I GET this info is by adding a custom endpoint - check my answers here for how to do this - https://stackoverflow.com/a/63283807/5064324 & https://stackoverflow.com/a/62634233/5064324.
For handlers you can do something like:
async getProductModel(ctx) {
return strapi.models['product'].allAttributes;
}
I needed the solution for all Content Types so I made a plugin with /modelStructure/* endpoints where you can supply the model name and then pass to a handler:
//more generic wrapper
async getModel(ctx) {
const { model } = ctx.params;
let data = strapi.models[model].allAttributes;
return data;
},
async getProductModel(ctx) {
ctx.params['model'] = "product"
return this.getModel(ctx)
},
//define all endpoints you need, like maybe a Page content type
async getPageModel(ctx) {
ctx.params['model'] = "page"
return this.getModel(ctx)
},
//finally I ended up writing a `allModels` handler
async getAllModels(ctx) {
Object.keys(strapi.models).forEach(key => {
//iterate through all models
//possibly filter some models
//iterate through all fields
Object.keys(strapi.models[key].allAttributes).forEach(fieldKey => {
//build the response - iterate through models and all their fields
}
}
//return your desired custom response
}
Comments & questions welcome
This answer pointed me in the right direction, but strapi.models was undefined for me on strapi 4.4.3.
What worked for me was a controller like so:
async getFields(ctx) {
const model = strapi.db.config.models.find( model => model.collectionName === 'clients' );
return model.attributes;
},
Where clients is replaced by the plural name of your content-type.

How to pass GraphQLEnumType in mutation as a string value

I have following GraphQLEnumType
const PackagingUnitType = new GraphQLEnumType({
name: 'PackagingUnit',
description: '',
values: {
Carton: { value: 'Carton' },
Stack: { value: 'Stack' },
},
});
On a mutation query if i pass PackagingUnit value as Carton (without quotes) it works. But If i pass as string 'Carton' it throws following error
In field "packagingUnit": Expected type "PackagingUnit", found "Carton"
Is there a way to pass the enum as a string from client side?
EDIT:
I have a form in my front end, where i collect the PackagingUnit type from user along with other fields. PackagingUnit type is represented as a string in front end (not the graphQL Enum type), Since i am not using Apollo Client or Relay, i had to construct the graphQL query string by myself.
Right now i am collecting the form data as JSON and then do JSON.stringify() and then remove the double Quotes on properties to get the final graphQL compatible query.
eg. my form has two fields packagingUnitType (An GraphQLEnumType) and noOfUnits (An GraphQLFloat)
my json structure is
{
packagingUnitType: "Carton",
noOfUnits: 10
}
convert this to string using JSON.stringify()
'{"packagingUnitType":"Carton","noOfUnits":10}'
And then remove the doubleQuotes on properties
{packagingUnitType:"Carton",noOfUnits:10}
Now this can be passed to the graphQL server like
newStackMutation(input: {packagingUnitType:"Carton", noOfUnits:10}) {
...
}
This works only if the enum value does not have any quotes. like below
newStackMutation(input: {packagingUnitType:Carton, noOfUnits:10}) {
...
}
Thanks
GraphQL queries can accept variables. This will be easier for you, as you will not have to do some tricky string-concatenation.
I suppose you use GraphQLHttp - or similar. To send your variables along the query, send a JSON body with a query key and a variables key:
// JSON body
{
"query": "query MyQuery { ... }",
"variables": {
"variable1": ...,
}
}
The query syntax is:
query MyMutation($input: NewStackMutationInput) {
newStackMutation(input: $input) {
...
}
}
And then, you can pass your variable as:
{
"input": {
"packagingUnitType": "Carton",
"noOfUnits": 10
}
}
GraphQL will understand packagingUnitType is an Enum type and will do the conversion for you.

Structuring GraphQL types

I've run into an issue while trying to extend my API to include a GraphQL endpoint. The application I'm working on is a kind of forum with Messages. A message can contain comments of type Message. If a message is a comment it has a parent of type Message. Simplified, the schema looks like this:
type Message {
id: String
content: String
comments: [Message]
parent: Message
}
type RootQuery {
message(id: String): Message
messages: [Message]
}
The problem with this schema is that it allows for queries like this:
{
messages {
comments {
parent {
comments {
parent {
comments {
parent {
id
content
}
}
}
}
}
}
}
}
Keep in mind that I may want to allow for arbitrarily deep nesting of comments. In that case the following query should be allowed:
{
messages {
comments {
comments {
comments {
id
content
}
}
}
}
}
So, my question is this: Should I introduce a new type - Comment - to the API that do not know of its parent? Or are there any other ways of restricting this kind of unwanted behaviour?
Also, would the use of a Comment-type prohibit me from using the fragment messageFields on Message syntax in my queries? Perhaps this is the time to introduce interfaces to the schema?
Suggestion to a solution if I introduce the type Comment (I have not tried this):
interface Message {
id: String
content: String
comments: [Message]
}
type DefaultMessage : Message {
id: String
content: String
comments: [Comment]
parent: Message
}
type Comment : Message {
id: String
content: String
comments: [Message]
}
type RootQuery {
message(id: String): Message
messages: [Message]
}
Just in case anyone else ends up here wondering how to do recursive types in graphql-js, there's a useful hint in graphql-js's code:
* When two types need to refer to each other, or a type needs to refer to
* itself in a field, you can use a function expression (aka a closure or a
* thunk) to supply the fields lazily.
*
* Example:
*
* var PersonType = new GraphQLObjectType({
* name: 'Person',
* fields: () => ({
* name: { type: GraphQLString },
* bestFriend: { type: PersonType },
* })
* });
*
*/
https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L274
If a message is a comment it has a parent of type Message.
Looks like the parent field should be under type Comment, not DefaultMessage. That still wouldn't prevent a parent - comments - parent query but if you're worried about this for a DDOS reason, there are many other types of requests that are difficult to compute, even with REST APIs, and you should have other measures to detect such an attack.
Recursive nodes
However, you pose a very interesting question with the nested comments. How would you know how many times you need to nest the comment in the query to get all nested responses? I don't think it's currently possible with GraphQL to specify recursive objects.
I'd probably go around this limitation by fetching each nested comment one by one (or by X levels at a time) starting from the last comment as the node
{
messages {
comments {
id
content
}
}
}
followed by
{
node(commendId) {
comment {
id
content
}
}
}
I suppose you have a depth attribute of the Comment data structure, which should be pretty useful, for example, to limit the max nested depth when the users are posting comments.
So that your problem could be solved like this: in the resolver of the comments attribute, check the depth, return nothing if the depth is going illegal, otherwise fetch the comments and return.

Laravel: how to parse string to json

I've created this code from laravel:
public function findConfig($id)
{
$config = DB::table('configuration')
->join('model', 'model.configuration_id','=', 'configuration.id')
->select('configuration.id','configuration.description', 'model.name','configuration.price')
->where('configuration.id','=', $id)
->get();
$encode = json_encode($config, JSON_UNESCAPED_SLASHES);
$response = Response::make($encode, 200);
$response->header('Content-Type', 'application/json');
return $response;
}
then the return is somehow like this
[{
"id": "1",
"description": "{\"item\":[{'colours\":[\"red\",\"blue\",\"green\"]},{\"motors\":[ {\"name\":\"450W/48V\",\"price\":\"2,000\"},{\"name\":\"550W/48V\", \"price\":\"3,000\" }] } ]}",
"name": "k5-A",
"price": "300000"
},
{
"id": "1",
"description": "{\"item\":[{'colours\":[\"red\",\"blue\",\"green\"]},{\"motors\":[ {\"name\":\"450W/48V\",\"price\":\"2,000\"},{\"name\":\"550W/48V\", \"price\":\"3,000\" }] } ]}",
"name": "r-A",
"price": "300000"
}
]
How can I remove the slashes and instead of string as return type, it should be in JSON?
As lukasgeiter said, generally it isn't a good idea to store json in a db. It may get difficult to filter by that field.
If you decide to do so, and need to get the decoded data, you can use an accessor in the model. I don't know if it is the best practice. If the description is saved in the db as a json you can do this:
For the "configuration" table you may have a "Configuration" model (The official Laravel website recommends to name the table in plural, and the model in it's singular, like: table -> configurations and the model configuration). In that file you can add this:
public function getDescriptionAttribute($value)
{
return json_decode($value, true);
}
Now, the description field is returned as an array.
You can see more about accessors and mutators here: http://laravel.com/docs/4.2/eloquent#accessors-and-mutators

Resources