Allowing a multipart (image) upload through GraphQL for Elixir backend - reactjs

I was hoping to get a clearer understand where I may be going wrong. Sorry if I ask a lot of questions since I feel a bit lost and been stuck for about a week on this one.
Currently, I've changed the package I was using for linking with apollo-client. The previous package was apollo-link-http and now I'm using apollo-absinthe-upload-link since I read it allows for image upload.
It's done as follows
const httpLink = createLink({
uri: 'http://localhost:4000/api',
credentials: 'same-origin',
fetch,
fetchOptions
});
There wasn't any change to sending information to the backend but I do continue to be lost in regards to uploading an image. The purpose of this image is to upload to the cloud then save the url information with the product details.
I'm using a hook as const [ images, setImages ] = useState([]); and an input of <input type="file" placeholder="Upload Image" onChange={handleUploadImage} />
The purpose of the onChange function is to set the image information to the images property. When we send the mutation to the backend, the way it's done is as follows
const buildForm = () => {
let storeID = 2;
const fileData = fileList.fileList.map((file) => {
return file.originFileObj;
});
const data = new FormData();
data.append('fileData', images);
debugger;
return {
productName,
productDescription,
productPrice,
productType,
isReturnable,
storeID,
fileData: data
};
};
when it goes, within my console I'm getting an error of Uncaught (in promise) Error: GraphQL error: Argument "fileData" has invalid value $fileData. and I'm seeing on the backend the key fileData having an empty object as its value. I was hoping to get some advice on what might be wrong or what I should consider. If someone mentioned CURL please explain since I have no idea what that means in regards to GraphQL and sending a mutation. Thank you for the help on this matter.
P.S - The mutation call that is being used is
export const CREATE_PRODUCT_MUTATION = gql`
mutation CreateProduct(
$storeID: Int!
$productName: String!
$productDescription: String!
$productPrice: Decimal!
$productType: Int!
$isReturnable: Boolean!
$fileData: Upload!
) {
createProduct(
product: {
productName: $productName
productDescription: $productDescription
productPrice: $productPrice
productType: $productType
isReturnable: $isReturnable
}
storeId: $storeID
fileData: $fileData
) {
id
productName
productDescription
productPrice
}
}
`;
UPDATE - Network return request
{"errors":[{"locations":[{"column":0,"line":2}],"message":"Argument \"fileData\" has invalid value $fileData."}]}
Backend Schema
#desc "List a new product"
field :create_product, :product do
arg(:product, :new_product)
arg(:store_id, :integer)
arg(:file_data, non_null(:upload))

apollo-absinthe-upload-link expects a variable of type File or Blob (see here), but you are passing fileData as type FormData.
Since your input type is file, you could do:
const handleUploadImage = (event) => setImages(event.target.files);
const buildForm = () => ({
productName,
productDescription,
productPrice,
productType,
isReturnable,
storeID: 2,
fileData: images[0],
});
References:
file-selector
react-dropzone

Related

Upload react-pdf dynamically generated file to Sanity using NextJS

I'm working on an e-commerce app built on NextJS and Sanity, so far I've made some mock products with all the necessary requirements, a user login system and checkout. I've been trying to make an invoice system so that when the user confirms an order 3 things must happen:
send all the order data to a react-pdf component and generate the invoice(working)
post the invoice file to the sanity schema so that the user has access to it when he goes to his order history page(not working)
email both the company and the client about the order(not implemented yet but I can do it)
ReactPDF allows me to access the pdf through a hook that returns me the blob of the file and the URL. I've tried to POST both of them but the url returned 404 and the blob didn't upload at all.
Searched the docs of both ReactPDF and Sanity and I couldn't find anything, although I think it has to do something with this endpoint from Sanity:
myProjectId.api.sanity.io/v2021-06-07/assets/files/myDataset
This is how I POST the order to my sanity studio
const { data } = await axios.post(
'/api/orders',
{
user: userInfo,
invoice_id: orders.length + 1,
orderItems: cartItems.map((item) => ({
...item,
slug: undefined
})),
billingData,
paymentMethod,
itemsPrice,
taxPrice,
totalPrice
},
{
headers: {
authorization: `Bearer ${userInfo.token}`
}
}
);
I've tried making 2 POST requests, one for the invoice_file alone, trying to post the blob or the url but none did work. The schema for invoice file was updated for the type of post each time so I'm 99% sure that wasn't the issue, anyway here's how the schema for invoice_file looks as for file:
{
name: 'invoice_file',
title: 'Invoice',
type: 'file',
options: {
storeOriginalFilename: true
}
},
If there would be any other code snippets relevant please let me know.
I really don't know how to find the solution for this as it's the first time trying to do such thing, so help would be much appreciated.
Thanks in advance!
I apologies as I'm not really active here but it's hard to pass on your question especially as I'm working on something similar. There's probably other ways to do this but I suggest you work use the official Sanity client. There's a specific section in the README that tells us how to do the file uploads or here.
So here's kinda the very small snippet:
import {
Document,
pdf,
} from "#react-pdf/renderer";
const doc = <Document />;
const asPdf = pdf([]); // {} is important, throws without an argument
asPdf.updateContainer(doc);
const blob = await asPdf.toBlob();
// `blob` here is coming from your react-pdf blob
const fileName = "customfilename.pdf";
client.assets.upload("file", blob, { filename: fileName }).then((fileAsset) => {
console.log(fileAsset", fileAsset);
// you can then use the fileAsset to set and reference the file that we just uploaded to our document
client.patch("document-id-here").set({
invoice_file: {
_type: "file",
asset: {
_type: "reference",
_ref: fileAsset._id,
},
},
}).commit();
});

How to upload images in react via api?

Today I saw a number of tutorials on how to upload photos in react via the api.
I did everything, tried all the methods. But in the end I get stuck.
(During the whole explanation I will focus only on the features of the image upload)
In Models I have groups and variable -
[NotMapped]
public IFormFile ImageFile {get; set; }
In api I get
[Route ("Add")]
[HttpPost]
public void Post (Group group)
And I have in state-
const initialFieldValues ​​= {
GroupName: '',
GroupAbout: '',
imageName: '',
imageSrc: defaultImageSrc,
imageFile: null
}
const [values, setValues] = useState (initialFieldValues)
When changing the image has a function-
const handleImg = (e) => {
if (e.target.files && e.target.files [0]) {
let imageFile = e.target.files [0];
const reader = new FileReader ();
reader.onload = x => {
setValues ​​({
... values,
imageFile,
imageSrc: x.target.result
})
}
reader.readAsDataURL (imageFile)
SetDisplayImg ("block");
}
else {
setValues ​​({
... values,
imageFile: null,
imageSrc: defaultImageSrc
})
}
};
And when submitting the form
const handleFormSubmit = e => {
e.preventDefault ()
const formData = new FormData ()
.append ('groupImage', values.imageFile)
addOrEdit (formData)
}
const addOrEdit = (formData) => {
axios.post ('api / groups / add', formData) .catch (error => {
console.log (error.response.data);
console.log (error.response.status);
console.log (error.response.headers);
});
}
In this code -makes error 415 (even regardless of uploading the image but, even if I put it only other variables that get stringed and work normally.)
If I add [FromForm] in the api it does not respond to me, i.e. it does not write me an error message nor does it reach the api (I checked in debugging)
If I change the axios to
const obj = {'groupImage': values.imageFile
}
axios.post ('api / groups / add', obj) .catch (error =>
I get an error message 400-
"The JSON value could not be converted to System.String. Path: $ .groupImage
And if I send the value from state
axios.post ('api / groups / add', values)
I get an error message System.NotSupportedException: Deserialization of interface types is not supported. Type 'Microsoft.AspNetCore.Http.IFormFile'. Path: $ .imageFile | LineNumber: 0 | BytePositionInLine: 6939781.
---> System.NotSupportedException: Deserialization of interface types is not supported. Type 'Microsoft.AspNetCore.Http.IFormFile'.
Anything I try to fix, it causes another error, I'm really at a loss.
Regards
>.append ('groupImage', values.imageFile)
Firstly, please make sure the key of your formdata object can match your model class property's name.
formData.append('imageName', values.imageName);
//...
//it should be imageFile, not groupImage
formData.append('imageFile', values.imageFile);
Besides, please apply the [FromForm] attribute to action parameter, like below.
public void Post([FromForm]Group group)
{
//...
Test Result
Usually a 415 means you aren't setting the right Content-Type header. Does the API you are trying to upload to mention acceptable types or encodings it expects?

MongoDB Webhook function to save forminput in database

I've been trying to save data from my form in my MongoDB for some time.
I also get a response from the database.
See also: create object in mongo db api onclick sending form
Unfortunately there are not enough tutorials in my mother tongue and I don't seem to understand everything in English.
I've tried some of the documentation, but I always fail.
What is missing in my webhook function so that the form data can be stored?
exports = function(payload) {
const mongodb = context.services.get("mongodb-atlas");
const mycollection = mongodb.db("created_notifications").collection("dpvn_collection");
return mycollection.find({}).limit(10).toArray();
};
The Webhookfunction was totally wrong.
READ THE DOCUMENTATION FIRST
exports = function(payload, response) {
const mongodb = context.services.get("mongodb-atlas");
const requestLogs = mongodb.db("created_notifications").collection("dpvn_collection");
requestLogs.insertOne({
body: EJSON.parse(payload.body.text()),
query: payload.query
}).then(result => {
})
};

GraphQL: Writing Frontend Query with Optional Arguments

I am currently trying to write a GraphQL Query called recipe which can take a number of optional arguments based on a graphQL input called RecipeSearchInput, and uses the input to find a specific recipe matching the attributes passed.
I am struggling to write the frontend query to be able to be able to take the arguments as an object.
Here's my graphQl schema for graphql input RecipeSearchInput.
input RecipeSearchInput {
_id: ID
title: String
cookTime: Int
prepTime: Int
tools: [String!]
ingredients: [String!]
steps: [String!]
videoURL: String
tags: [String!]
country: String
}
And here's my query written in the frontend to access the my mongodb server through graphql:
// gql query that requests a recipe
export const findOne = obj => {
let requestBody = {
query: `
query {
recipe(recipeInput: ${obj}) {
_id
title
cookTime
prepTime
tools
ingredients
steps
videoURL
tags
country
}
}
`
};
return fetchEndpoint(requestBody);
};
When I wrote my frontend query with a simple object that I knew existed in my database:
const displayData = async () => {
const recipeData = await api.recipe.findOne({
title: "Greek Chicken Skewers"
});
console.log(recipeData);
};
This gives me the following error:
message: "Expected value of type "RecipeSearchInput", found [object, Object]."
The problem I'm seeing is that obj is not formatted properly (in String form) to be received by Graphql as an input. The Graphql query params look like this:
Here is what the working query looks like
What's the best approach for making a query that takes more than one argument? How should I package up the argument in the frontend to please GraphQL?
Thanks in advance, and please let me know if any of this was unclear!
Shawn

React Apollo updating client cache after mutation

I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}​​​​​
generated: false​​​​​
id: "DojoType:1"​​​​​
type: "id"​​​​​
typename: "DojoType"​​​​​
<prototype>: Object { … }​​​​
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.

Resources