Nuxt 3 useFetch Post request: File object missing from server api route - post

I am trying to post a file object to my nuxt 3 api route
Problem is:
Data from client has my file object
Data from server returns empty object
Screenshot of the issue
Where did my file object go?
const handleImageUpload = async (evt: Event) => {
const target = evt.target as HTMLInputElement
if (target.files) {
const file = target.files[0]
const upload: iUpload = {
name: file.name,
type: file.type,
file
}
console.log("data from client", upload)
try {
const { data, error } = await useFetch(constants.imageUploadApiUrl, {
headers: { "Content-type": "application/json" },
method: 'POST',
body: upload
})
console.log("data from server", data.value)
} catch (error) {
console.log(error)
}
}
}
constants.imageUploadApiUrl (api route) has the following
import { getQuery, readBody } from "h3"
import { iUpload } from "~~/helpers/interfaces"
export default defineEventHandler(async (event) => {
try {
const query = getQuery(event)
const body = await readBody(event) as iUpload
return { body }
} catch (error: any) {
return { error: error.message }
}
})
iUpload interface is this
export interface iUpload {
name: string;
type: string;
file: File;
}

I eventually got it working. Meanwhile it's using supabase as it's backend (forgot to mention that).
But here are the changes I made.
#1 - I added a utility function to convert the file to base64 string
export const getBase64 = (file: File) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
#2 - I updated the handleImageUpload function like below. The only change being in the file key
const handleImageUpload = async (evt: Event) => {
const target = evt.target as HTMLInputElement
if (target.files) {
const fileObj = target.files[0]
const upload: iUpload = {
path: id(memberName(store.selected), '-'),
name: fileObj.name,
file: await getBase64(fileObj) as string, // <**=**
type: fileObj.type
}
console.log("data from client", upload)
try {
const { data, error } = await useFetch(constants.imageUploadApiUrl, {
headers: { "Content-type": "multipart/form-data" },
method: 'POST',
body: upload
})
console.log("data from server", data.value)
} catch (error) {
console.log(error)
}
}
}
#3 - Furthermore I updated the server route as follows:
export default defineEventHandler(async (event) => {
try {
const body = await readBody(event) as iUpload
const filePath = `${body.path}/${body.name}`
const res = await fetch(body.file)
const blob = await res.blob()
const response = await supabase.storage
.from("pictures")
.upload(filePath, blob, {
contentType: body.type,
upsert: true,
})
return {
data: response.data,
error: response.error?.message,
}
} catch (error: any) {
return { error: error.message }
}
})
#4 - Lastly I updated the policies on supabase storage bucket and storage object to the following:
supabase storage policy update

Related

How to respond with a stream in a Sveltekit server load function

Below I try to respond with a stream when I receive ticker updates.
+page.server.js:
import YahooFinanceTicker from "yahoo-finance-ticker";
const ticker = new YahooFinanceTicker();
const tickerListener = await ticker.subscribe(["BTC-USD"])
const stream = new ReadableStream({
start(controller) {
tickerListener.on("ticker", (ticker) => {
console.log(ticker.price);
controller.enqueue(ticker.price);
});
}
});
export async function load() {
return response????
};
Note: The YahooFinanceTicker can't run in the browser.
How to handle / set the response in the Sveltekit load function.
To my knowledge, the load functions cannot be used for this as their responses are JS/JSON serialized. You can use an endpoint in +server to return a Response object which can be constructed from a ReadableStream.
Solution: H.B. comment showed me the right direction to push unsollicited price ticker updates the client.
api route: yahoo-finance-ticker +server.js
import YahooFinanceTicker from "yahoo-finance-ticker";
const ticker = new YahooFinanceTicker();
const tickerListener = await ticker.subscribe(["BTC-USD"])
/** #type {import('./$types').RequestHandler} */
export function GET({ request }) {
const ac = new AbortController();
console.log("GET api: yahoo-finance-ticker")
const stream = new ReadableStream({
start(controller) {
tickerListener.on("ticker", (ticker) => {
console.log(ticker.price);
controller.enqueue(String(ticker.price));
}, { signal: ac.signal });
},
cancel() {
console.log("cancel and abort");
ac.abort();
},
})
return new Response(stream, {
headers: {
'content-type': 'text/event-stream',
}
});
}
page route: +page.svelte
<script>
let result = "";
async function getStream() {
const response = await fetch("/api/yahoo-finance-ticker");
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { value, done } = await reader.read();
console.log("resp", done, value);
if (done) break;
result += `${value}<br>`;
}
}
getStream();
</script>
<section>
<p>{#html result}</p>
</section>

Use formData to upload two filetypes in an iOS app?

I would like my uploadFormPage() function to be able to take jpegs and pdf's. Is it possible for me to have 2 file types for the same FormData() const?`
export function uploadFormPage(documentId, formId, file, callback) {
return async dispatch => {
try {
const formData = new FormData();
formData.append('page', {
name: `document-${documentId}-${formId}-${Date.now()}.jpg`,
type: 'image/jpeg',
uri: file,
});
const result = await Api.uploadFiles(formData);
const entity = {
id: formId,
resourceKey: result.page,
};
const rsp = await Api.uploadFormPage(documentId, entity);
dispatch({type: LOAD_DOCUMENTS, data: rsp});
callback(null, rsp);
} catch (e) {
callback(e, null);
}
};
}

Apollo-Server-Express not receiving Upload Object

I'm trying to upload a file using apollo-server-express and apollo-client. However, when the file object is passed to the resolver it is always empty. I can see the file on the client, but not the server side. How can I resolve this ?
My Schema
type File {
id: ID
path: String
filename: String
mimetype: String
}
extend type Query {
getFiles: [File]
}
extend type Mutation {
uploadSingleFile(file: Upload!): File
}
My Resolver
Mutation: {
uploadSingleFile: combineResolvers(
isAuthenticated,
async (parent, { file }, { models, user, storeUpload }, info) => {
console.log('Resolver-> uploadSingleFile')
console.log(file) // Will return empty, { }
const x = await file
console.log(x) // Will also return empty, { }
const storedFile = storeUpload(file)
return storedFile
}
),
},
My Client-side queries file
export const UPLOAD_SINGLE_FILE = gql`
mutation uploadSingleFile($file: Upload!) {
uploadSingleFile(file: $file) {
id
}
}
`
My Client-side interface
import React from 'react'
// GQL
import { useApolloClient, useMutation } from '#apollo/react-hooks'
import { UPLOAD_SINGLE_FILE } from '../../queries'
const FileUpload = props => {
const [uploadSingleFile, uploadSingleFileResult] = useMutation(UPLOAD_SINGLE_FILE, {
onCompleted(uploadSingleFile) {
console.log('Completed uploadSingleFile')
}
})
const apolloClient = useApolloClient()
const handleUploadFile = ({
target: {
validity,
files: [file]
}
}) => {
console.log('Uploading file...')
if(validity.valid) {
console.log('Valid')
console.log(file.name)
uploadSingleFile({ variables: { file } })
.then(() => {
apolloClient.resetStore()
})
}
else console.log('Invalid file')
}
return(
<input type="file" required onChange={handleUploadFile} />
)
}
export default FileUpload
UPDATED
My front-end set-up is:
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
})
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token')
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
})
You need to utilize the appropriate Link with your Apollo Client in order to enable file uploads. The easiest way to do that is by using createUploadLink from apollo-upload-client. It functions as a drop-in replacement for createHttpLink, so just swap out the functions and you'll be good to go.
const httpLink = createUploadLink({
uri: 'http://localhost:4000/graphql',
})
const authLink = ...
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
})
Assuming you have the proper link set up and in use (using createUploadLink as Daniel mentions in his post), you should be able to destructure the props from file once you await the promise in your resolver on the server.
const { filename, mimetype, createReadStream } = await file.promise;
console.log(filename, mimetype);
// to get a stream to use of the data
const stream = createReadStream();
UPDATE: in more recent versions of graphql-upload you can just await the file like you do in the OP, rather than the file.promise. I was using an older version of the lib it seems.

feathersjs sequelize call stored procedure by hook doesn't resolve

I have a problem creating a hook calling a stored procedure.
My custom service hooks (customservice.hooks.js) are :
const callstored = require('../../hooks/callstored')
module.exports = {
before: {
all: [],
find: [callstored()],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
...
My service class (customservice.class.js):
const mysql = require('mysql')
class Service {
constructor (options) {
this.options = options || {};
}
find (params) {
return Promise.resolve( [] );
}
get (id, params) {
return Promise.resolve({
id, text: `A new message with ID: ${id}!`
});
}
create (data, params) {
if (Array.isArray(data)) {
return Promise.all(data.map(current => this.create(current)));
}
return Promise.resolve(data);
}
update (id, data, params) {
return Promise.resolve(data);
}
patch (id, data, params) {
return Promise.resolve(data);
}
remove (id, params) {
return Promise.resolve({ id });
}
}
module.exports = function (options) {
return new Service(options);
};
module.exports.Service = Service;
And my service (customservice.service.js) :
const createService = require('./rankingvotes.class.js');
const hooks = require('./rankingvotes.hooks');
module.exports = function (app) {
const paginate = app.get('paginate');
const options = {
name: 'rankingvotes',
paginate
};
// Initialize our service with any options it requires
app.use('/rankingvotes', createService(options));
// Get our initialized service so that we can register hooks and filters
const service = app.service('rankingvotes');
service.hooks(hooks);
app.publish(() => {
});
};
And finally my hook is (callstored.js)
const Sequelize = require('sequelize');
module.exports = function () {
return function (hook) {
sequelize = hook.app.get('sequelizeClient');
let result=[];
return sequelize.query('CALL RANKING();',{
nest: true,
raw: true }).then(function(response){
console.log(response[0]) //data are correct
hook.data=response[0];
return hook;
}).error(function(err){
console.log(err);
return hook;
});
}
}
If I check the console I got data correctly, but nothing calling from a REST client like Postman.
Any idea? Thank you.
If you want to change the response you have to set hook.result.
const Sequelize = require('sequelize');
module.exports = function () {
return async hook => {
try {
const sequelize = hook.app.get('sequelizeClient');
const response = await sequelize.query('CALL RANKING();',{
nest: true,
raw: true
});
console.log(response[0]) //data are correct
context.result = response[0];
} catch(error) {
console.error(error);
}
return hook;
}
}
hook.data is the request data and is only available for create, update and patch.
Keep in mind that setting hook.result will skip your custom service find if it is set in a before hook.

Nativescript image to Base64 then fetch POST

How can I Base64 an image so it would be suitable to send with nativescript fetch module?
Here is a basic demo for your case using test server
"use strict";
var imageSourceModule = require("image-source");
function navigatingTo(args) {
var page = args.object;
// using icon.png from the template app from res://icon
var imgSrc = imageSourceModule.fromResource("icon");
var imageAsBase64 = imgSrc.toBase64String("PNG");
fetch("https://httpbin.org/post", {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: imageAsBase64
}).then(function (r) { return r.json(); }).then(function (result) {
console.log("Base64String: " + result.data);
// for (var key in result) {
// if (result.hasOwnProperty(key)) {
// var element = object[result];
// console.log(key);
// console.log(element);
// console.log("------")
// }
// }
}, function (e) {
console.log("Error occurred " + e);
});
}
exports.navigatingTo = navigatingTo;

Resources