Foreign key mapping in RestKit does not work for nested relationships - restkit

Given 3 entity types: User (a conversation participant), Chat (a text conversation between 2 Users), and VideoCall (always tied to a Chat, where one of the users is a Caller).
If an API endpoint returns a VideoCall object, foreign key mapping to Chat and User works fine.
<DRVideoCall: 0x16f27f00> (entity: DRVideoCall; id: 0x182a1c90 <x-coredata://07D2A791-2CE9-46B1-8060-E288EC2ED4A4/DRVideoCall/p9> ; data: {
caller = "0x16fbbff0 <x-coredata://07D2A791-2CE9-46B1-8060-E288EC2ED4A4/DRDoctor/p1>";
callerId = 307;
chat = "0x1826dca0 <x-coredata://07D2A791-2CE9-46B1-8060-E288EC2ED4A4/DRChat/p185>";
chatId = 2748;
state = calling;
})
But if it's nested into another mapping as a relationship, it does not, chat and caller properties are nil.
<DRVideoCall: 0x191bebb0> (entity: DRVideoCall; id: 0x1918b3d0 <x-coredata:///DRVideoCall/t24460E4B-9B6B-43F3-9981-62FDE53BFD5919> ; data: {
caller = nil;
callerId = 307;
chat = nil;
chatId = 2761;
state = calling;
})
Is it possible with RestKit to make foreign key relationships work for nested objects?
Event mapping
RKEntityMapping *eventMapping = [RKEntityMapping mappingForEntityForName:#"DREvent"
inManagedObjectStore:managedObjectStore];
[eventMapping addAttributeMappingsFromDictionary:#{ #"type" : #"type" }];
RKRelationshipMapping *eventCallMapping =
[RKRelationshipMapping relationshipMappingFromKeyPath:#"call"
toKeyPath:#"call"
withMapping:[DRVideoCall mappingInManagedObjectStore:managedObjectStore]];
[eventMapping addPropertyMapping:eventCallMapping];
Video Call Mapping
RKEntityMapping *videoMapping = [RKEntityMapping mappingForEntityForName:#"DRVideoCall"
inManagedObjectStore:store];
[videoMapping addAttributeMappingsFromDictionary:#{ #"calling_user_id" : #"callerId",
#"chat_id" : #"chatId" }];
[videoMapping addConnectionForRelationship:#"chat" connectedBy:#{ #"chatId" : #"chatId" }];
[videoMapping addConnectionForRelationship:#"caller" connectedBy:#{ #"callerId" : #"userId" }];
UPD: Here's what VideoCall JSON looks like
{
"chat_id":2748,
"calling_user_id":307,
"state":"calling",
"session_id":"...",
"token":"..."
}
And Event JSON:
{
"call":{
"calling_user_id":307,
"chat_id":274,
"session_id" = "...",
"state":"calling",
"token":"..."
},
"type":"call"
}

Related

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.

Apollo - Updating cache when some fields in some results are missing

For the following query, in some objects in the results array, some of the requested fields might not be present in the response (for example photo or address), which causes the data of my useQuery to be undefined (without any error or warning).
people(xyz: { q: $q, offset: $offset, rows: $rows }) {
results {
uri <--- this is a field of type ID!
name
photo
address {
city
country
}
}
}
My fix is to specifically check if the field exists in the incoming data and provide a fallback value, i.e.: pass a type policy for Person to be {keyFields: false} and do this in the merge function:
newItem = {...item};
newItem.photo = item.photo ?? null;
newItem.address = item.address ?? {city: "", country: ""};
Is the reason for having to do this that there's no id field in the Person type (instead, uri is of type ID!)?
Can I handle this in a better way?
Found a better way on Apollo GraphQL's GitHub.
I'd still appreciate a solution where I don't have to go over each type's nullable field in turn, if there is one.
function nullable() {
// Create a generic field policy that allows any field to be null by default:
return {
read(existing = null) {
return existing;
},
};
}
new InMemoryCache({
typePolicies: {
Person: {
fields: {
photo: nullable(),
address: nullable(),
},
},
Address: { // If there's the case of either city or country missing
fields: {
city: nullable(),
country: nullable(),
}
}
},
})

Create complex argument-driven queries from AWS Lambda?

Look for // HERE IS THE PROBLEM PART sentence to find code that is the problem.
I am trying to implement AppSync using AWS Lambda (that connects to RDS Postgres server) as a data source. I want to create puKnowledgeFile query that will update my KnowledgeFile with optional arguments. If the client only provided htmlText and properties as arguments, then my update query should only update these two fields.
type Mutation {
putKnowledgeFile(
id: ID!,
htmlText: String,
plainText: String,
properties: AWSJSON
): KnowledgeFile
}
type KnowledgeFile {
id: ID!
htmlText: String!
plainText: String!
properties: AWSJSON!
lastDateTimeModified: AWSDateTime!
dateTimeCreated: AWSDateTime!
}
Here is an piece of AWS Lambda code:
exports.handler = async (event, context, callback) => {
/* Connecting to Postgres */
let data = null;
let query = ``;
let values = [];
switch (event.info.fieldName) {
case "putKnowledgeFile":
if(event.arguments.htmlText === undefined &&
event.arguments.plainText === undefined &&
event.arguments.properties === undefined) {
callback(`At least one argument except id should be provided in putKnowledgeFile request`);
}
// HERE IS THE PROBLEM PART
query += `update knowledge_file`
query += `
set `;
let index = 0;
for (let fieldName in event.arguments) {
if(arguments.hasOwnProperty(fieldName)) {
const fieldValue = event.arguments[fieldName];
if(index === 0) {
query += `${fieldName}=$${index+1}`
values.push(fieldValue);
} else {
query += `, ${fieldName}=$${index+1}`
values.push(fieldValue);
}
index++;
}
}
query += `
where knowledge_file.id = $${index+1};`;
values.push(event.arguments.id);
// HERE IS THE PROBLEM PART
break;
default:
callback(`There is no functionality to process this field: ${event.info.fieldName}`);
return;
}
let res = null;
try {
res = await client.query(query, values); // just sending created query
} catch(error) {
console.log("#client.query");
console.log(error);
}
/* DisConnecting from Postgres */
callback(null, res.rows);
};
Basically, this algorithm creates my query string through multiple string concatenations. I think it's too complicated and error-prone. Is there a way to create dynamic queries based on the presence / absence of certain arguments easily?
Just in case, here is my PostgreSQL schema:
-- main client object for clients
CREATE TABLE client (
id bigserial primary key,
full_name varchar(255)
);
-- knowledge_file
create table knowledge_file (
id bigserial primary key,
html_text text,
plain_text text,
properties jsonb,
last_date_modified timestamptz,
date_created timestamptz,
word_count varchar(50)
);
-- which client holds which knowledge file
create TABLE client_knowledge_file (
id bigserial primary key,
client_id bigint not null references client(id),
knowledge_file_id bigint not null references knowledge_file(id) unique ON DELETE CASCADE
);
I know this is not an optimum solution and might not completely answer your question but I also ran into similar problem and this is how I solved it.
I created a resolver pipeline.
In one function, I used the select statement to get the current
record.
In second function, I checked if the fields (in your case htmlText and properties) are null. If true, then use the ctx.prev.result values otherwise use the new ones).
Practical example
First resolver function:
{
"version": "2018-05-29",
"statements": [
"select id, html_text AS \"htmlText\", plain_text AS \"plainText\", properties, last_date_modified AS \"lastDateTimeModified\", date_created AS \"dateTimeCreated\" from knowledge_file where id = $ctx.args.Id"
]
}
Second resolver function:
#set($htmlText = $util.defaultIfNull($ctx.args.htmlText , $ctx.prev.result.htmlText))
#set($properties = $util.defaultIfNull($ctx.args.properties , $ctx.prev.result.properties))
{
"version": "2018-05-29",
"statements": [
"update knowledge_file set html_text = $htmlText, plain_text = $ctx.args.plainText, properties = $properties, last_date_modified = CURRENT_TIMESTAMP, date_created = CURRENT_DATE where id = $ctx.args.Id returning id, html_text AS \"htmlText\", plain_text AS \"plainText\", properties, last_date_modified AS \"lastDateTimeModified\", date_created AS \"dateTimeCreated\""
]
}

Prisma Not Returning Created Related Records

i want to create a new graphql api and i have an issue that i am struggling to fix.
the code is open source and can be found at: https://github.com/glitr-io/glitr-api
i want to create a mutation to create a record with relations... it seems the record is created correctly with all the expected relations, (when checking directly into the database), but the value returned by the create<YourTableName> method, is missing all the relations.
... so so i get an error on the api because "Cannot return null for non-nullable field Meme.author.". i am unable to figure out what could be wrong in my code.
the resolver looks like the following:
...
const newMeme = await ctx.prisma.createMeme({
author: {
connect: { id: userId },
},
memeItems: {
create: memeItems.map(({
type,
meta,
value,
style,
tags = []
}) => ({
type,
meta,
value,
style,
tags: {
create: tags.map(({ name = '' }) => (
{
name
}
))
}
}))
},
tags: {
create: tags.map(({ name = '' }) => (
{
name
}
))
}
});
console.log('newMeme', newMeme);
...
that value of newMeme in the console.log here (which what is returned in this resolver) is:
newMeme {
id: 'ck351j0f9pqa90919f52fx67w',
createdAt: '2019-11-18T23:08:46.437Z',
updatedAt: '2019-11-18T23:08:46.437Z',
}
where those fields returned are the auto-generated fields. so i get an error for a following mutation because i tried to get the author:
mutation{
meme(
memeItems: [{
type: TEXT
meta: "test1-meta"
value: "test1-value"
style: "test1-style"
}, {
type: TEXT
meta: "test2-meta"
value: "test2-value"
style: "test2-style"
}]
) {
id,
author {
displayName
}
}
}
can anyone see what issue could be causing this?
(as previously mentioned... the record is created successfully with all relationships as expected when checking directly into the database).
As described in the prisma docs the promise of the Prisma client functions to write data, e.g for the createMeme function, only returns the scalar fields of the object:
When creating new records in the database, the create-method takes one input object which wraps all the scalar fields of the record to be
created. It also provides a way to create relational data for the
model, this can be supplied using nested object writes.
Each method call returns a Promise for an object that contains all the
scalar fields of the model that was just created.
See: https://www.prisma.io/docs/prisma-client/basic-data-access/writing-data-JAVASCRIPT-rsc6/#creating-records
To also return the relations of the object you need to read the object again using an info fragment or the fluent api, see: https://www.prisma.io/docs/prisma-client/basic-data-access/reading-data-JAVASCRIPT-rsc2/#relations

Parse.com manipulate Response Object

I am trying to work Ember with Parse.com using
ember-model-parse-adapter by samharnack.
I add added a function to make multiple work search(like search engine) for which I have defined a function on cloud using Parse.Cloud.define and run from client.
The problem is the Array that my cloud response returns is not compatible with Ember Model because of two attributes they are __type and className. how can I modify the response to get response similar to that i get when I run a find query from client. i.e without __type and className
Example responses
for App.List.find() = {
"results":[
{
"text":"zzz",
"words":[
"zzz"
],
"createdAt":"2013-06-25T16:19:04.120Z",
"updatedAt":"2013-06-25T16:19:04.120Z",
"objectId":"L1X55krC8x"
}
]
}
for App.List.cloudFunction("sliptSearch",{"text" : this.get("searchText")})
{
"results":[
{
"text":"zzz",
"words":[
"zzz"
],
"createdAt":"2013-06-25T16:19:04.120Z",
"updatedAt":"2013-06-25T16:19:04.120Z",
"objectId":"L1X55krC8x",
"__type" : Object, //undesired
"className" : "Lists" //undesired
}
]
}
Thanks Vlad something like this worked for me for array
resultobj = [];
searchListQuery.find({
success: function(results) {
for( var i=0, l=results.length; i<l; i++ ) {
temp = results.pop();
resultobj.push({
text: temp.get("text"),
createdAt: temp.createdAt,
updatedAt: temp.updatedAt,
objectId: temp.id,
words: "",
hashtags: ""
});
}
In your cloud code before you make any response, create and object and extract from it the attributes/members you need and then response it. like so:
//lets say result is some Parse.User or any other Parse.Object
function(result)
{
var responseObj = {};
responseObj.name = responseObj.get("name");
responseObj.age = responseObj.get("age");
responseObj.id = responseObj.id;
response.success(responseObj);
}
on the response side you will get {"result": {"name": "jhon", "age": "26", "id": "zxc123s21"}}
Hope this would help you

Resources