How do I upload info associated to different Model Types using AWS & GraphQL? - swift

I have an app that I have been working on that allows users to upload a post and other users can send messages after seeing their post. I am using AWS Amplify as a backend and I am trying to store user messages in a DynamoDB table. I am able to successfully store user info in a DynamoDB table after they create an account and successfully upload a post. My problem starts when I try to message another user about their post.
I get this error after pressing send Failed to create graphql GraphQLResponseError<ChatMessage>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "The variables input contains a field name \'chatMessageAuthorId\' that is not defined for input object type \'CreateChatMessageInput\' ", locations: nil, path: nil, extensions: nil)] Recovery suggestion: The list of `GraphQLError` contains service-specific messages
My GraphQL Schema is the following:
type User
#model
#auth(rules: [{ allow: owner, operations: [create, delete, update]}]) {
id: ID!
userSub: String!
fullName: String!
conversations: [ConvoLink] #connection(name: "UserLinks")
messages: [ChatMessage] #connection(name: "UserMessages", keyField: "authorId")
createdAt: String
updatedAt: String
}
type Conversation
#model
#auth(rules: [{ allow: owner, ownerField: "members" }]) {
id: ID!
messages: [ChatMessage] #connection(name: "ConvoMsgs", sortField: "createdAt")
associated: [ConvoLink] #connection(name: "AssociatedLinks")
name: String!
members: [String!]!
createdAt: String
updatedAt: String
}
type ChatMessage
#model
#auth(rules: [{ allow: owner, ownerField: "authorId" }]) {
id: ID!
author: User #connection(name: "UserMessages", keyField: "authorId")
authorId: String
content: String!
conversation: Conversation! #connection(name: "ConvoMsgs")
messageConversationId: ID!
createdAt: String
updatedAt: String
}
type ConvoLink
#model(
mutations: { create: "createConvoLink", update: "updateConvoLink" }
queries: null
subscriptions: null
) {
id: ID!
user: User! #connection(name: "UserLinks")
convoLinkUserId: ID
conversation: Conversation! #connection(name: "AssociatedLinks")
convoLinkConversationId: ID!
createdAt: String
updatedAt: String
}
type Post #model
#auth(rules: [{ allow: owner, operations: [create, delete, update] }]) {
id: ID!
userSub: String!
filename: String!
description: String!
dateUploaded: String!
}
And this is my Swift code to send a message to another user.
#objc func sendMessage(){
currentUserSub = AWSMobileClient.default().userSub!
guard let message = messageBox.text else{return}
let formatter = DateFormatter()
formatter.dateFormat = "h:mm a"
let hourString = formatter.string(from: Date())
let user = User(userSub: currentUserSub, fullName: userFullName)
let conversation = Conversation(messages: List<ChatMessage>.init(), associated: List<ConvoLink>.init(), name: "Chat between \(currentUserSub) & \(otherUserSub)", members: [currentUserSub,otherUserSub], createdAt: hourString, updatedAt: hourString)
let chatMessage = ChatMessage(author: user, authorId: currentUserSub, content: message, conversation: conversation, messageConversationId: "\(currentUserSub) & \(otherUserSub)", createdAt: hourString, updatedAt: hourString)
_ = Amplify.API.mutate(request: .create(chatMessage)) { event in
switch event {
case .success(let result):
switch result {
case .success(let convo):
DispatchQueue.main.async {
print("Successfully created the convo: \(convo)")
self.messageButton.setTitle("Message Sent", for: .normal)
self.messageButton.isEnabled = false
}
case .failure(let graphQLError):
DispatchQueue.main.async {
print("Failed to create graphql \(graphQLError)")
self.messageButton.setTitle("An error has occured, try again.", for: .normal)
// self.checkIfOffline()
}
}
case .failure(let apiError):
print("Failed to create a message", apiError)
// self.checkIfOffline()
}
}
hideKeyboard()
print(message)
}
Can anyone tell me what I am doing wrong? I am fairly new to GraphQL and AWS.

Related

findUnique query returns null for array fields

I read the Prisma Relations documentation and it fixed my findMany query which is able to return valid data but I'm getting inconsistent results with findUnique.
Schema
model User {
id Int #id #default(autoincrement())
fname String
lname String
email String
password String
vehicles Vehicle[]
}
model Vehicle {
id Int #id #default(autoincrement())
vin String #unique
model String
make String
drivers User[]
}
Typedefs
const typeDefs = gql'
type User {
id: ID!
fname: String
lname: String
email: String
password: String
vehicles: [Vehicle]
}
type Vehicle {
id: ID!
vin: String
model: String
make: String
drivers: [User]
}
type Mutation {
post(id: ID!, fname: String!, lname: String!): User
}
type Query {
users: [User]
user(id: ID!): User
vehicles: [Vehicle]
vehicle(vin: String): Vehicle
}
'
This one works
users: async (_, __, context) => {
return context.prisma.user.findMany({
include: { vehicles: true}
})
},
However, for some reason the findUnique version will not resolve the array field for "vehicles"
This one doesn't work
user: async (_, args, context) => {
const id = +args.id
return context.prisma.user.findUnique({ where: {id} },
include: { vehicles: true}
)
},
This is what it returns
{
"data": {
"user": {
"id": "1",
"fname": "Jess",
"lname": "Potato",
"vehicles": null
}
}
}
I was reading about fragments and trying to find documentation on graphql resolvers but I haven't found anything relevant that can solve this issue.
Any insight would be appreciated! Thanks!
You need to fix the arguments passed to findUnique. Notice the arrangement of the { and }.
Change
return context.prisma.user.findUnique({ where: { id } },
// ^
include: { vehicles: true}
)
to
return context.prisma.user.findUnique({
where: { id },
include: { vehicles: true }
})

mongoose: getting unknown objectId when stored string instead of objectId

According to me when i pushed 'ABC' instead of objectId, It should show some error like
Cast to ObjectId failed for value "ABC" at path "likes".
but when i print that updated data i saw some unknown objectId in likes array
My question is, what is that unknown objectId(6a61736f6e20626f75726e65) and why it is generated
automatically
User Model
new Schema({
name: { type: String,required:true},
}, { usePushEach: true ,timestamp:true});
Feed Model
new Schema({
user: { type: Types.ObjectId, ref: 'user', required: true },
name: { type: String,default:null, trim: true },
likes: [{ type : Types.ObjectId, ref: 'user',}],
}, { usePushEach: true ,timestamp:true});
In Feed Schema likes have reference to User Schema
var data = await feed.findByIdAndUpdate(
postId,
{
$push: { likes: 'ABC' }
}
);
if(data){
var data = await feed.findById(postId);
console.log(data.likes);//["6a61736f6e20626f75726e65"]
}
OutPut:
["6a61736f6e20626f75726e65"]
Since you are pushing a String in likes which is not an ObjectId, you are getting this error.
Your schema clearly states that likes is an array of ObjectId:
likes: [{ type : Types.ObjectId, ref: 'user'}],
so you have to pass an ObjectId instead of user's name.
You have to do this:
// Whenever you are pushing value in `likes` `array`,
// you need to get the `ObjectId` of the user who liked the feed.
let userId = <userId_of_Ishwar>;
var data = await feed.findByIdAndUpdate(
postId,
{
$push: { likes: userId }
});

CompactMapValues not working on this simple dictionary

I have a model that looks like:
public struct Profile {
public let bio: String?
public let company: String
public let createdDate: Date
public let department: String
public let email: String
public let firstName: String
public let coverImageURL: URL?
public let jobTitle: String
public let lastName: String
public let location: String?
public let name: String
public let profileImageURL: URL?
public let roles: [String]
public let isActive: Bool
public let updatedDate: Date?
public let userID: String
public init(
bio: String?, company: String, createdDate: Date, department: String, email: String, firstName: String, coverImageURL: URL?, jobTitle: String,
lastName: String, location: String?, name: String, profileImageURL: URL?, roles: [String], isActive: Bool, updatedDate: Date?, userID: String) {
self.bio = bio
self.company = company
self.createdDate = createdDate
self.department = department
self.email = email
self.firstName = firstName
self.coverImageURL = coverImageURL
self.jobTitle = jobTitle
self.lastName = lastName
self.location = location
self.name = name
self.profileImageURL = profileImageURL
self.roles = roles
self.isActive = isActive
self.updatedDate = updatedDate
self.userID = userID
}
}
extension Profile: Equatable { }
I am trying to create a tuple that represents it as (model: Profile, json: [String: Any])
using the following method -
func makeProfile() -> (model: Profile, json: [String: Any]) {
let updatedDate = Date()
let updatedDateStr = updatedDate.toString(dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX")
let createdDate = Date()
let createdDateStr = createdDate.toString(dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX")
let coverImageURL = makeURL("https://headerUri.com")
let profileImageURL = makeURL("https://pictureUri.com")
let model = Profile(
bio: "Some Bio",
company: "Cool Job INC",
createdDate: createdDate,
department: "Engineering",
email: "name#domain.tld",
firstName: "Anne",
coverImageURL: coverImageURL,
jobTitle: "Test Dummy",
lastName: "Employee",
location: "London",
name: "Anne Employee",
profileImageURL: profileImageURL,
roles: ["ADMIN"],
isActive: true,
updatedDate: updatedDate,
userID: UUID().uuidString
)
let json: [String: Any] = [
"bio": model.bio,
"company": model.company,
"createdDate": createdDateStr,
"department": model.department,
"email": model.email,
"firstName": model.firstName,
"headerUri": model.coverImageURL?.absoluteString,
"jobTitle": model.jobTitle,
"lastName": model.lastName,
"location": model.location,
"name": model.name,
"pictureUri": model.profileImageURL?.absoluteString,
"roles": model.roles,
"isActive": model.isActive,
"updatedDate": updatedDateStr,
"userId": model.userID
].compactMapValues { $0 }
return (model: model, json: json)
}
}
extension Date {
func toString(dateFormat format: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: self)
}
}
All of the optional properties are showing a warning Expression implicitly coerced from 'String?' to 'Any'
I have added .compactMapValues { $0 } to the dict but has had no effect.
How can I clear this warning?
You're getting an error because you trying to implicitly coerce an optional value (which could be nil) to Any, which hides that the value could be nil.
compactMapValues doesn't guarantee at compile-time that the value is not nil; all the compiler knows is that something that is an optional (like String?) is now treated as a non-optional Any.
One way you could avoid the error is by providing a default value instead of the optional:
let json: [String: Any] = [
"bio": model.bio ?? ""
// ...
]
Alternatively, you can create a dictionary of: [String, Any?] to assign values to, and then use .compactMapValues, which would give you back non-optional values of Any.
let withOptionals: [String: Any?] = [
"bio": model.bio,
// ...
]
let json: [String, Any] = withOptionals.compactMapValues { $0 }
You're getting the warning because the values are optional when you initialize the dictionary. Your .compactMapValues { $0 } won't help because by then it's "too late".
Instead you could initialize an empty dictionary and add the values 1 by 1.
var json = [String: Any]()
json["bio"] = model.bio
json["company"] = model.company
// all of the other values
json["userId"] = model.userID

How to delete a record with all relevant records in prisma

I know that there are some sections related to my question in the documentation of prisma-client:
deleting objects
updating and deleting many records
However, I can't understand that how can I delete a record with all its related records in (JavaScript) prisma-client.
For example, my datamodel is something like this:
type Board {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "BoardOwnershipRelation")
title: String!
description: String
taskGroups: [TaskGroup!]!
}
type TaskGroup {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "TaskGroupOwnershipRelation")
board: Board!
title: String!
description: String
precedence: Int
tasks: [Task!]!
}
type Task {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "TaskOwnershipRelation")
taskGroup: TaskGroup!
title: String!
description: String
dueDate: DateTime
precedence: Int
items: [TaskItem!]!
assignedTo: [User!]! #relation(name: "AssignmentRelation")
}
type TaskItem {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "TaskItemOwnershipRelation")
task: Task!
title: String!
description: String
checked: Boolean!
precedence: Int
}
How can I delete a Board with all its related TaskGroups, Tasks, and TaskItems ?!
Edit:
I've recently tried this solution, which also works well.
// e.g. this is in my GraphQL resolvers async function...
await prisma.deleteManyTaskItems({
task: {
taskGroup: {
board: {
id: boardId
}
}
}
});
await prisma.deleteManyTasks({
taskGroup: {
board: {
id: boardId
}
}
});
await prisma.deleteManyTaskGroups({
board: {
id: boardId
}
});
return await prisma.deleteBoard({ id: boardId });
But, is there any better solution for my issue ???
You can use the "onDelete" argument of the #relation directive to specify what happens when you delete an entity (documentation)
You only have to change your datamodel like so:
type Board {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "BoardOwnershipRelation")
title: String!
description: String
taskGroups: [TaskGroup!]! #relation(name: "BoardTaskGroups" onDelete: CASCADE)
}
type TaskGroup {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "TaskGroupOwnershipRelation")
board: Board! #relation(name: "BoardTaskGroups")
title: String!
description: String
precedence: Int
tasks: [Task!]! #relation(name: "TaskGroupsTask" onDelete: CASCADE)
}
type Task {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "TaskOwnershipRelation")
taskGroup: TaskGroup! #relation(name: "TaskGroupsTask")
title: String!
description: String
dueDate: DateTime
precedence: Int
items: [TaskItem!]! #relation(name: "TaskTaskItem" onDelete: CASCADE)
assignedTo: [User!]! #relation(name: "AssignmentRelation")
}
type TaskItem {
id: ID! #unique
createdAt: DateTime!
updatedAt: DateTime!
owner: User! #relation(name: "TaskItemOwnershipRelation")
task: Task! #relation(name: "TaskTaskItem")
title: String!
description: String
checked: Boolean!
precedence: Int
}
And then delete your Board. All the other deletions will be done by Prisma

Push ObjectId to nested array in Mongoose

(Basic library CRUD application)
I am trying to create a document containing some global data about a given book, and then within a User document, add the ObjectId of the newly-created book to an array containing all books belonging to that user.
I have three data models in my application:
var userSchema = new mongoose.Schema({
name: String,
password: String,
email: String,
books: [BookInstanceSchema],
shelves: [String]
});
var bookSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
author: {
type: String,
required: true
},
description: String,
pageCount: Number,
ISBN: String,
googleID: String,
thumbnail: String,
publisher: String,
published: String,
});
var BookInstanceSchema = new mongoose.Schema({
bookId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Book'
},
userReview: String,
userRating: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
min: 0,
max: 4,
default: 0
},
shelf: String
});
The User model contains a nested array of BookInstances, which contain user-specific data such as ratings or reviews for a given book. A bookInstance in turn contains a reference to the global data for a book, to avoid duplicating data that isn't specific to any user.
What I'm trying to do is first save the global data for a book (thus generating an _id), and when done, save a bookInstance containing that _id in a given user's array of books:
router.post('/save/:id', function(req, res) {
var url = encodeurl('https://www.googleapis.com/books/v1/volumes/' + req.params.id);
request(url, function(err, response, data) {
parsedData = JSON.parse(data);
var newBook = {
title: parsedData.volumeInfo.title,
author: parsedData.volumeInfo.authors[0],
description: parsedData.volumeInfo.description,
pageCount: parsedData.volumeInfo.pageCount,
ISBN: parsedData.volumeInfo.description,
googleID: parsedData.id,
publisher: parsedData.volumeInfo.publisher,
published: parsedData.volumeInfo.publishedDate,
thumbnail: parsedData.volumeInfo.imageLinks.thumbnail
};
Book.create(newBook, function(err, newBook) {
if (err) {
console.log(err);
}
else {
console.log(newBook._id);
console.log(mongoose.Types.ObjectId.isValid(newbook._id));
User.findByIdAndUpdate(req.session.id, {
$push: {
"books": {
bookId: newBook._id,
userRating: 0,
userReview: ''
}
}
},
{
upsert: true
},
function(err, data){
if(err) {
console.log(err);
}
else {
res.redirect('/');
}
});
}
});
});
});
I'm getting the error:
message: 'Cast to ObjectId failed for value "hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0" at path "_id" for model "User"',
name: 'CastError',
stringValue: '"hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0"',
kind: 'ObjectId',
value: 'hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0',
path: '_id',
reason: undefined,
Every time, the value in the error (in this case, jhHy8T...) is different than the newBook._id I'm attempting to push into the array:
console.log(newBook._id); // 5a120272d4201d4399e465f5
console.log(mongoose.Types.ObjectId.isValid(newBook._id)); // true
It seems to me something is wrong with my User update statement:
User.findByIdAndUpdate(req.session.id, {
$push: {
"books": {
bookId: newBook._id,
userRating: 0,
userReview: ''
}
}...
Any help or suggestions on how to better organize my data are appreciated. Thanks!