Graphql error when finding object. Can not assign null to non nullable field - graphql-js

I have been playing with the simple problem for days now and can not figure out my error. I am trying to return one object from an array of object with the object id.
playground query
schema definition
type Query {
info: String
feed: [Link!]
link(id: String!): Link
}
type Mutation {
post(url: String!, description: String!): Link!
updateLink(id: ID!, url: String!, description: String!): Link!
deleteLink(id: ID!): Link!
}
type Link {
id: ID!
description: String!
url: String!
}
index.js
const { GraphQLServer } = require('graphql-yoga');
let links = [{
id: 'link-0',
url: 'www.howtographql.com',
description: 'Fullstack tutorial for GraphQL',
author: 'sam roehrich'
}]
let idCount = links.length
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
feed: () => links,
link: (parent, {id}) => {
return links.filter(link => {
link.id === id
})
}
}

Way late to the party, but I thought I'd give a clear answer to this question for others in a similar situation.
Daniel Rearden in the comments is right. Basically, the problem is that the filter() function returns an array instead of an Object. A simple solution to this problem would be to return the first (and only) item of the array in the resolver:
const link = links.filter(link => {
link.id === id
})
// Return only the first link from the array.
return link[0];

filter returns a new list that satisfy the given condition, so you're returning a list than an item(object) of the list

Related

Bypassing Firebase IN: limit in Swift

I have a query that I am doing with Google's Firestore where I get a query of posts from a collection. This is a central collection of all posts on the network but I want to be able to filter what comes from the server so the processing/filtering doesn't happen on the client side. Essentially, I want to get a feed of posts from only account the user follows.
Creating that array is easy enough. When the user logins it downloads the array of UID the user follows. This array, theoretically, could be long. Firestore has this handy '.whereField' option that can filter look through a String Array using in: like this.
.whereField("userUID", in: auth.userFollowedAccounts!)
It works perfectly but according to the documentation Firestore only allowed 10 items in the array when using in:. This causes an issues because I want users to be able to follow more then 10 accounts. I saw some other solutions from some other languages to get around this issue by splicing the array or doing some clever looping to go though all the options. Unfortunately, I can't seem to find a solution for Swift. Would love someone to take a look and help me brainstorm some work arounds for this.
// Full Code Block
func getData() {
let db = Firestore.firestore()
db.collection("posts")
.order(by: "dateCreated", descending: true)
.whereField("userUID", in: auth.userFollowedAccounts!)
.limit(to: 10)
.getDocuments { (snap, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
self.post.removeAll()
for i in snap!.documents {
let data = Post(id: i.documentID, username: i.get("username") as? String, fullName: i.get("fullName") as? String, profilePictureURL: i.get("profilePictureURL") as? String, link: i.get("link") as? String, location: i.get("location") as? String, postImage: i.get("postImage") as? String, isVerified: i.get("isVerified") as? Bool, caption: i.get("caption") as? String, likeCounter: i.get("likeCounter") as? Int, dateCreated: i.get("dateCreated") as? String, userUID: i.get("userUID") as? String, isAdmin: i.get("isAdmin") as? Bool, pronouns: i.get("pronouns") as? String)
self.post.append(data)
}
self.lastDoc = snap!.documents.last
}
}
Let me know if you have any questions.
Here's a simple example using Firestore async/await calls to shorten the code.
The Firebase structure was not included in the question (please include that in questions) so I will craft one which may be similar to what you're using
Starting with a users collection which uses the users uid as the documentId, keeps the users name and then the users who they are following as an array
users
uid_0
user_name: "Leroy"
following
0: uid_9
uid_1
user_name: "Biff"
following
0: uid_0
1: uid_2
In this, uid_0 is following uid_9 and uid_1 is following uid_0 and uid_2
Then a posts collection snippit
posts
post_0
post_msg: "Post 0 from uid_0"
post_uid: "uid_0"
post_1
post_msg: "Post 0 from uid_1"
post_uid: "uid_1"
post_2
post_msg: "Post 1 from uid_0"
post_uid: "uid_0"
The posts have a message and the uid of the user that posted it.
Then the code.
func getPostsOfUsersIFollow() {
let usersCollection = self.db.collection("users")
let postsCollection = self.db.collection("posts")
let thisUserDoc = usersCollection.document("uid_1") //me
Task {
do {
let userResult = try await thisUserDoc.getDocument()
let usersIFollow = userResult.get("following") as? [String] ?? []
print(usersIFollow) //outputs the list of users uid's I follow
for uid in usersIFollow {
let usersPosts = try await postsCollection.whereField("post_uid", isEqualTo: uid).getDocuments()
for postDoc in usersPosts.documents {
let postMsg = postDoc.get("post_msg") as? String ?? "no msg"
print("post_id: \(postDoc.documentID) uid: \(uid) posted msg: \(postMsg)")
}
}
} catch {
print("need to handle error")
}
}
}
If I am user uid_1, I am following uid_0 and uid_2. When this code is run, it will first query for all of the users I am following (uid_0, uid_2) then iterate over that list, which can be any number of users, to query the posts from each of those users, and output those posts to console.
So, if uid_0 has 3 posts and uid_2 has 3 posts the final output would look like this
["uid_0", "uid_2"]
post_id: post_0 uid_0 posted msg: Post 0 from uid_0
post_id: post_4 uid_0 posted msg: Post 1 from uid_0
post_id: post_6 uid_0 posted msg: Post 2 from uid_0
post_id: post_2 uid_2 posted msg: Post 0 from uid_2
post_id: post_5 uid_2 posted msg: Post 1 from uid_2
post_id: post_9 uid_2 posted msg: Post 2 from uid_2
In this case I output to console but in code, you'd likely have some class to store the uid, name and post and then populate an array which backs a tableView or collection with that data
class UserPostClass {
var uid = ""
var postId = ""
var userName = ""
var postMsg = ""
}
var userPostArray = [UserPostClass]()
and then once the array was populated, reload your tableView, view etc.
The one gotcha here is ensuring the UI is responsive - with small datasets this will work as is, but if you're loading thousands of posts (don't do that) you'll likely want to paginate your data to break it into smaller chunks.
The other thing to note is there is no ordering, so you'll likely want to add an orderBy clause

Fetch several ParseObjects with objectId

I am updating an iOS app using ParseSwift. In general, every ParseUser of the app has several associated Userdata ParseObjects that need to be queried or fetched.
The current implementation uses individual queries to find every individual Userdata object one after the other. This works but is obviously not optimal.
What I already reworked is that every ParseUser now saves an Array of objectId's of the user-associated Userdata ParseObjects.
What I am trying to achieve now is to query for all Userdata objects that correspond to these saved objectId's in the ParseUser's userdataIds field.
This is my code:
func asyncAllUserdataFromServer() {
let userdataIdArray = User.current!.userdataIds
var objectsToBeFetched = [Userdata]()
userdataIdArray?.forEach({ objectId in
let stringObjectId = objectId as String
// create a dummy Userdata ParseObject with stringObjectId
let object = Userdata(objectId: stringObjectId)
// add that object to array of Userdata objects that need to be fetched
objectsToBeFetched.append(object)
})
// fetch all objects in one server request
objectsToBeFetched.fetchAll { result in
switch result {
case .success(let fetchedData):
// --- at this point, fetchedData is a ParseError ---
// --- here I want to loop over all fetched Userdata objects ---
case .failure(let error):
...
}
}
}
I read in the ParseSwift documentation that several objects can be fetched at the same time based on their objectIds, so my idea was to create an Array of dummy Userdata objects with the objectIds to fetch. This, however, does not work.
As indicated in the code block, I was able to narrow the error down, and while the query is executed, fetchedData comes back as a ParseError saying:
Error fetching individual object: ParseError code=101 error=objectId "y55wmfYTtT" was not found in className "Userdata"
This Userdata object with that objectId is the first (and only) objectId saved in the User.current.userdataIds array. This object does exist on my server, I know that for sure.
Any ideas why it cannot be fetched?
General infos:
Xcode 13.2.1
Swift 5
ParseSwift 2.5.0
Back4App backend
Edit:
User implementation:
struct User: ParseUser {
//: These are required by `ParseObject`.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
//: These are required by `ParseUser`.
var username: String?
var email: String?
var emailVerified: Bool?
var password: String?
var authData: [String: [String: String]?]?
//: Custom keys.
var userdataIds: [String]? // --> array of Userdata objectId's
}
Userdata implementation:
struct Userdata: ParseObject, ParseObjectMutable {
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var firstname: String?
var lastSeen: Int64?
var isLoggedIn: Bool?
var profilepicture: ParseFile?
}
You should check your Class Level Permissions (CLP's) and here in Parse Dashboard, if you are using the default configuration, you can’t query the _User class unless you make the CLP’s public or authorized. In addition, you can probably shrink your code by using a query and finding the objects instead of building your user objects and fetching the way you are doing:
let userdataIdArray = User.current!.userdataIds
let query = Userdata.query(containedIn(key: "objectId", array: userdataIdArray))
//Completion block
query.find { result in
switch result {
case .success(let usersFound):
...
case .failure(let error):
...
}
}
// Async/await
do {
let usersFound = try await query.find()
...
} catch {
...
}
You should also look into your server settings to make sure this option is doing what you want it to do: https://github.com/parse-community/parse-server/blob/4c29d4d23b67e4abaf25803fe71cae47ce1b5957/src/Options/docs.js#L31
Are you sure that the Object Ids for UserData are the same as the User ids? while you might have user id in the class, it is probably not the objectId, but some other stored field.
doing
let query = PFQuery(className:"UserData")
query.whereKey("userId", containedIn:userdataIdArray)
query.findObjectsInBackground { (objects: [PFObject]?, error: Error?) in
// do stuff
}
May help.

Making a query on a mongo DB with ParseSwift

I have two collections in a Mongo DB.
Here is how a document looks in the first collection (MainCollection):
_id
:"mzWqPEDYRU"
TITLE
:"ZAZ: I want."
ownerID
:"lGutCBY52g"
accessKey
:"0kAd4TOmoK0"
_created_at
:2020-03-13T11:42:11.169+00:00
_updated_at
:2020-03-13T17:08:15.090+00:00
downloadCount
:2
And here is how it looks in the second collection (SecondCollection):
_id
:"07BOGA8bHG"
_p_unit
:"MainCollection$mzWqPEDYRU"
SENTENCE
:"I love nature peace and freedom."
Order
:5
ownerID
:"lGutCBY52g"
AUDIO
:"07067b5589d1edd1d907e96c1daf6da1_VOICE.bin"
_created_at
:2020-03-13T11:42:17.483+00:00
_updated_at
:2020-03-13T11:42:19.336+00:00
There is a parent children relationship between the first and the second collection. In the last document we can see the _p_unit field where the "mzWqPEDYRU" part points to the id of the parent in the first collection.
Though I finally get what I want, getting elements of the second collection with a given parent, it is not done how it should.
I have one problem making a selective query on SecondCollection. Here is the currently working code:
func theFunction(element: MainCollection) {
do {SecondCollection.query().find() {
result in
switch result {
case .success(let items):
print("items.count = \(items.count)")
var finalCount = 0
for item in items {
// Ignore useless elements:
if item.unit?.objectId != element.objectId! {continue}
finalCount += 1
/// .... Work with selected element.
}
print("finalCount = \(finalCount)")
case .failure(let error):
print("Error in \(#function): \(error)")
}
}
}
}
The way the above code is written works in the sense that I get the elements in SecondCollection I am interested in. But this trick inside the for loop to eliminate the non-needed element is not the way to go.
if item.unit?.objectId != element.objectId! {continue}
The filtering should happen in the query, with the line:
SecondCollection.query().find()
The problem is that everything I have tried failed. I did things like:
SecondCollection.query("unit" == element.objectId!).find()
with a zillion variations, but all with no luck.
Does anybody know the proper syntax?
In case this may be useful, here is how SecondCollection is declared:
struct SecondCollection: ParseObject,Identifiable,Equatable,Hashable {
// These fields are required for any Object.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
// Local properties.
var id:UUID {return UUID()}
var SENTENCE: String?,
Order: Int?,
ownerID: String?,
AUDIO: ParseFile?,
unit: Pointer<MainCollection>?
.......
}
First, I do not think that bringing all elements and then "eliminating" the ones you don't need is a good approach. That way you are retrieving a lot more data than needed and it is very inneficient.
I tried out your code but you did not make clear (at least for me) if you are using Pointers or Relations between those classes. The approach would be different for each one.
Which one are you using?
UPDATE: Hey there! I think I could make it work kinda the way you need it.
I created two classes ParentClass (name: string, age: number, children: relation to ChildClass):
struct ParentClass: ParseObject {
//: These are required for any Object.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
//: Your own properties.
var name: String?
var age: Int = 0
var children: ParseRelation<Self> {
ParseRelation(parent: self, key: "children", className: "ChildClass")
}
}
And ChildClass (name: string, age:number):
struct ChildClass: ParseObject {
//: These are required for any Object.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
//: Your own properties.
var name: String?
var age: Int = 0
}
Then I did a simple query to find the first Parent. You could use the find() method and bring all the ones that you need, but I did it this way to keep it simpler to explain:
let parentQuery = ParentClass.query()
parentQuery.first { result in
switch result {
case .success(let found):
print ("FOUND A PARENT!")
print(found.name!)
print(found.age)
print(found.children)
case .failure(let error):
print(error)
}
}
Now that I got the Parent (that has two children in my case), I used the query() method to generate the query on the ChildClass containing all the ChildClass' objects with the found parent:
do {
let childrenObject = ChildClass()
try found.children.query(childrenObject).find()
{ result in
switch result {
case .success(let allChildrenFromParent):
print("The following Children are part of the \(found.name!):")
for child in allChildrenFromParent {
print("\(child.name!) is a child of \(found.name!)")
}
case .failure(let error):
print("Error finding Children: \(error)")
}
}
} catch {
print("Error: \(error)")
}
And the whole code ended up like this:
let parentQuery = ParentClass.query()
parentQuery.first { result in
switch result {
case .success(let found):
print ("FOUND A PARENT!")
print(found.name!)
print(found.age)
print(found.children)
do {
let childrenObject = ChildClass()
try found.children.query(childrenObject).find()
{ result in
switch result {
case .success(let allChildrenFromParent):
print("The following Children are part of the \(found.name!):")
for child in allChildrenFromParent {
print("\(child.name!) is a child of \(found.name!)")
}
case .failure(let error):
print("Error finding Children: \(error)")
}
}
} catch {
print("Error: \(error)")
}
case .failure(let error):
print(error)
}
}

How to make data in different DynamoDB Tables relational?

I am currently following along with the AWS Amplify docs and I am using the default blog GraphQL schema to try and create relational Dynamo DB tables.
The blog model tables show up in DynamoDB and I can upload info to them I just don't know how to make them relational. The model types are Blog , Post , and Comment . For example, after uploading a post to its DynamoDB table, how can I link an uploaded comment to that same post? In my Swift code I try to attempt to do so but to no avail.
I also do not understand the syntax List<Comment>.init() and it probably should not be there but gives no errors. Maybe that is the cause of my problem.
Creating a post:
let blog = Blog(name: "UserBlog", posts: List<Post>.init())
let posts = Post(title: "Testing out AWS", blog:blog, comments: List<Comment>.init())
_ = Amplify.API.mutate(request: .create(posts)) { event in
switch event {
case .success(let result):
switch result {
case .success(let post):
print("Successfully created the post: \(post)")
case .failure(let graphQLError):
print("Failed to create graphql \(graphQLError)")
}
case .failure(let apiError):
print("Failed to create a todo", apiError)
}
}
Output from debug console
Successfully created the post: Post(id: "83D71F16-6B0D-453A-A163-AABF484CE527", title: "Testing out AWS", blog: nil, comments: nil)
Then after creating the comment for that post with this code
let blog = Blog(name: "UserBlog", posts: List<Post>.init())
let posts = Post(title: "Testing out AWS", blog:blog, comments: List<Comment>.init())
let comments = Comment(content: "It worked", post: posts)
_ = Amplify.API.mutate(request: .create(comments)) { event in
switch event {
case .success(let result):
switch result {
case .success(let comment):
print("Successfully created the comment: \(comment)")
case .failure(let graphQLError):
print("Failed to create graphql \(graphQLError)")
}
case .failure(let apiError):
print("Failed to create a todo", apiError)
}
}
Output from debug console
Successfully created the comment: Comment(id: "85395F8B-C8C2-4ACB-8FC5-DAEFC2728C32", content: Optional("It worked"), post: nil)
And finally after fetching from the tables using this code
let post = Post.keys
let predicate = post.title == "Testing out AWS"
_ = Amplify.API.query(request: .list(Post.self, where: predicate)) { event in
switch event {
case .success(let result):
switch result {
case .success(let post):
print("Successfully retrieved list of posts: \(post)")
case .failure(let error):
print("Got failed result with \(error.errorDescription)")
}
case .failure(let error):
print("Got failed event with error \(error)")
}
}
Output from debug console
Successfully retrieved list of posts: [testAWS.Post(id: "83D71F16-6B0D-453A-A163-AABF484CE527", title: "Testing out AWS", blog: nil, comments: nil)]
How can I link the comment to the post so when I query it the comment will show instead of nil
My Schema:
type Blog #model {
id: ID!
name: String!
posts: [Post] #connection(name: "BlogPosts")
}
type Post #model {
id: ID!
title: String!
blog: Blog #connection(name: "BlogPosts")
comments: [Comment] #connection(name: "PostComments")
}
type Comment #model {
id: ID!
content: String
post: Post #connection(name: "PostComments")
}
You should save each one of your models if you haven't done so, for example, I see you have created a blog, then a post with the blog. You'll need to save the blog, and then save the post, otherwise the blog does not exist in DynamoDB. It looks correct for Post and Comment since you are saving the post and then the comment (containing the post for association).
The data is persisted but isn't being returned to your client. Currently the builder that is being used to create the GraphQLRequest .list(Post.self, where: predicate) will only generate the selection set to return the Post, but not the optional list of connected comments. This is by default a walk-depth of 1. Currently Amplify for Android does this differently and there is an existing Issue open in the repo to track whether this should be updated to have a walk depth of 2 by default. https://github.com/aws-amplify/amplify-ios/issues/681
One workaround here is to create your own custom GraphQL request with a selection set that contains both the post and the comments. Or a custom GraphQL request for a list query that has a filter parameter that you set to filter for a particular post.id.
You can see this example here creates a nested selection set with post and comments: https://github.com/aws-amplify/docs/blob/fbe1773bd21476954f379909b7a9a7abf3f02c2a/docs/lib/graphqlapi/fragments/ios/advanced-workflows.md#nested-data
extension GraphQLRequest {
static func getPostWithComments(byId id: String) -> GraphQLRequest<JSONValue> {
let document = """
query getPost($id: ID!) {
getPost(id: $id) {
id
title
rating
status
comments {
items {
id
postID
content
}
}
}
}
"""
return GraphQLRequest<JSONValue>(document: document,
variables: ["id": id],
responseType: JSONValue.self)
}
}
Feel free to post your questions as you have here over in the github repo https://github.com/aws-amplify/amplify-ios/issues as it will help us in facilitating the conversation

Working with Arrays and Firebase

Looking to submit an array to firebase as a list of objects with integers as key names. I know that firebase does not support Arrays directly so was wondering how to do so. I am creating a list of items users add a users cart. so I am approaching it as such:
func addItemtoCart(item: String, completed: #escaping (_ completed: Bool) -> ()) { Firebase_REference_Cart.child(userID).child("itemIDs").updateChildValues(["itemiD": itemID])
completed(true)
}
I understand that this will not work because every time and item is added to the cart it will replace the item in under the "ItemId". I was looking to have something like this
CartITems: {
0: "945495949856956",
1: "9459469486895695"
2: "J888568567857685"
}
If someone could please describe how to do this from A to Z in the most descriptive way possible it would help greatly. I am new to firebase and need a bit of guidance.
First of all create model like:
struct Model {
var id: String
init(id: String) {
self.id = id
}}
Then make an instance of this model in your class: var model = Model(id: "First Id")
Then to push it to Firebase use:
func addInstance(model: Model) -> Completable {
return Completable.create( subscribe: { completable in
let reference = Database.database()
.reference()
.child("Models")
.childByAutoId
var model = model
model.id = reference.key
reference.setValue(model.dictionary)
self.modelVariable.value.append(model)
completable(.completed)
}
else {
completable(.error(NSError(domain: "Sample", code: 403, userInfo: [NSLocalizedDescriptionKey: "Server Error"])))
}
return Disposables.create()
})