Missing createReadStream as a argument from passed file - file-upload

Im trying to upload file from react application , there are 3arguments: filename, mimetype and encoding . After the file is uploaded all three are passed to the server but there is no parameter createReadStream.
$ node -v
v16.13.2
I've tried so far to send the file to the apollo-server.
my Queries are following:
export const UPLOAD = gql`
mutation Upload($uploadFile: Upload) {
fileUpload {
uploadFile(file: $uploadFile) {
filename
mimetype
encoding
}
}
}
`;
import { useMutation } from '#apollo/client'
import React from 'react'
import { UPLOAD } from './queries/Queries'
const UploadCV = () => {
const [UploadCV, { data, error,refe }]= useMutation(UPLOAD,{
context:{clientName:'upload'},
onComplete:data=>console.log(data)
})
const handleUpload = (e)=>{
console.log(e.target.files[0])
const file = e.target.files[0]
// console.log(typeof file.name)
// console.log(typeof file.type)
// console.log(typeof String(file.size))
if(!file)return
UploadCV({
variables:{
uploadFile: {
filename: file.name,
mimetype: file.type,
encoding: String(file.size),
}
}
})
}
return (
<div>
<input type="file"
onChange={handleUpload}
id="avatar" name="avatar"
accept="image/png, image/jpeg, application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document"
/>
</div>
)
}
export default UploadCV
Till now its fine but once its hit server start the problem.
const Upload = require("graphql-upload-minimal").GraphQLUpload
module.exports = {
fileUpload: {
Upload,
async uploadFile({ file:{ filename,mimetype, encoding,createReadStream} }) {
console.log('FileName:',filename , 'MiemeType:', mimetype,'Encoding:', encoding,'CreateReadStream:',createReadStream)
`
},
},
};
That is the output of the sended file.
My schema
const { buildSchema } = require('graphql');
const uploadFile = require('./uploadFile');
module.exports = buildSchema(`
scalar Upload
${uploadFile}
type Mutation{
fileUpload:UploadFile
}
type Query{
getCVs:getAllCV
}
`);
module.exports = `
type File{
filename:String
mimetype:String
encoding:String
}
type UploadFile{
uploadFile(file:Upload):File
}
type getAllCV{
getAllCV:[File]
}`

Related

I'm trying to get and print random pokemon using Api, but it gives me the error 404

This is API site link: https://pokeapi.co/docs/v2#pokemon
This is API example: https://pokeapi.co/api/v2/pokemon/{id or name}/
this is store.js
import { defineStore } from 'pinia'
import axios from 'axios'
let serverHost = 'https://pokeapi.co/api/v2'
export const pokeStore = defineStore("pokemons", {
state: () => {
return {
pokemons: []
};
},
actions: {
async getOnePokemon(id) {
try {
let response = await axios.get(`${serverHost}/pokemon/${id}`)
if(response.data){
response.data.name = response.data.name.replace('-', " ");
let pokemon = {
id: response.data.id,
name: response.data.name,
image: response.data.sprites.front_default
}
if(!pokemon.image){
pokemon.image = response.data.sprites.front_shiny
}
return pokemon
}
} catch (error) {
this.getOnePokemon(Math.floor(Math.random() * 151) + 1)
}
},
}
});
this is home where im trying to print random pokemon:
```
<script setup lang="ts">
import { RouterView } from 'vue-router'
import { ref } from 'vue'
import { pokeStore } from '../store/store'
const PokemonStore = pokeStore();
let allpokemonsIds = ref<any>()
async function GetPokemons() {
try {
let response = await PokemonStore.getOnePokemon();
allpokemonsIds.value = response;
console.log(allpokemonsIds.value)
} catch (error) {
throw error;
}
}
GetPokemons()
</script>
I want to get random pokemon from the first 151 pokemon and print it using the console log in the Home component
Change your code to this...
import { defineStore } from 'pinia'
import axios from 'axios'
let serverHost = 'https://pokeapi.co/api/v2'
export const pokeStore = defineStore("pokemons", {
state: () => {
return {
pokemons: []
};
},
actions: {
async getOnePokemon() {
try {
let id = Math.floor(Math.random() * 151) + 1; // <- I have changed this...
let response = await axios.get(`${serverHost}/pokemon/${id}`)
if(response.data){
response.data.name = response.data.name.replace('-', " ");
let pokemon = {
id: response.data.id,
name: response.data.name,
image: response.data.sprites.front_default
}
if(!pokemon.image){
pokemon.image = response.data.sprites.front_shiny
}
return pokemon
}
} catch (error) {
console.log(error)
}
},
}
});
You had some code in the catch that was trying to do the random id nbut that's not where that should go.
I've updated to generate the random id in the function instead of passing the id in.

Handle image upload for CKEditor 5 with a graphql backend

here's my current setup that is resulting in TypeError: undefined is not a function
import client from 'GraphQl/apolloClient'
import { ADD_POST_IMAGE } from 'GraphQl/News/Mutations'
function MyCustomUploadAdapterPlugin(editor) {
editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
return new MyUploadAdapter(loader)
}
}
class MyUploadAdapter {
constructor(props) {
// CKEditor 5's FileLoader instance.
this.loader = props
this.mutation = client.mutate({ mutation: ADD_POST_IMAGE })
}
// Starts the upload process.
upload() {
return new Promise((resolve, reject) => {
this._sendRequest()
})
}
// Prepares the data and sends the request.
_sendRequest() {
const [addPost, { error }] = this.mutation
this.loader.file.then(async (result) => {
const { data: response } = await addPost({
variables: { data: { image: result } },
})
console.log(response)
})
}
}
export default MyCustomUploadAdapterPlugin
i'm trying to setup a custom upload adapter for React CKEditor plugin 5.
since i have a graphql backend, i plan to use mutations for upload.

Fetching API for Articles with NextJS and Strapi

I would like some help on an API issue.
I have been trying to link each Article page based on the content I have created in Strapi CMS on my local server.
The API endpoint that I manage to gather data is from 'http://localhost:1337/api/articles?populate=*'.
Here is my code:
// lib/api.js
export class ApiError extends Error {
constructor(url, status) {
super(`'${url}' returned ${status}`);
if(Error.captureStackTrace) {
Error.captureStackTrace(this, ApiError);
}
this.name = 'ApiError';
this.status = status;
}
}
export async function fetchJson(url, options) {
const response = await fetch(url, options);
if(!response.ok) {
throw new ApiError(url, response.status);
}
return await response.json();
}
// lib/articles.js
import { fetchJson } from "./api";
const API_URL = process.env.API_URL;
// Gets a single article
export async function getArticle(id) {
const article = await fetchJson(`${API_URL}/api/article/${id}`);
return stripArticle(article);
}
// Gets all articles
export async function getArticles() {
const articles = await fetchJson(`${API_URL}/api/articles`);
return articles.map(stripArticle);
}
function stripArticle(article) {
return {
id: article.id,
title: article.attributes.Title,
content: article.attributes.Content,
pictureUrl: API_URL + article.attributes.Photo.formats.thumbnail.url,
}
}
Article Page:
//article/[id].js
import Page from "../../components/Page";
import { getArticle, getArticles } from "../../lib/articles";
import ReactMarkdown from 'react-markdown';
import Moment from 'react-moment';
export async function getStaticProps({ params }) {
const article = await getArticle(params.id)
return {
props: { article },
unstable_revalidate: 1,
}
}
export default function Article({ article }) {
return (
<Page title={article.Title}>
<ReactMarkdown source={article.Content} />
<p>
<Moment from="MM Do YYYY">{article.CreatedAt}</Moment>
</p>
</Page>
)
}
export async function getStaticPaths() {
const articles = await getArticles()
return {
paths: articles.map((article) => ({
params: { id: article.id.toString() }, // Number convert to string
})),
fallback: 'blocking', // What if error. Client is blocked, until new page is ready.
};
}
I would get an error: TypeError: articles.map is not a function.
If there is a better way to format and write the code, do let me know as I have been trying to find which is best.
Thanks for the help in advance.

NestJS upload multiple files using GraphQL

I tried to upload multiple files using Nestjs Graphql but i can't ..
this code works fine with controller (Rest) exactly in https://docs.nestjs.com/techniques/file-upload
#Post('upload')
#UseInterceptors(FilesInterceptor('files',saveImageToStorage))
uploadImages(#UploadedFiles() files:Array<Express.Multer.File>, #Req() req:Request):any{
return ;
}
but it can't work with Graphql may be because (FileInterceptor) can not work with graphql , how i impelment this code or this way with graphql to upload multiple images?
note: I tried many ways to upload multiple images using nest graph(with multer and with graphql-upload) but all ways failed !
image-storage.ts
import { diskStorage } from "multer";
import { generate } from 'shortid';
import * as fs from 'fs';
//const FileType = require('file-type');
import path = require('path');
type validFileExtension = 'png' | 'jpg' | 'jpeg';
type validMimeType = 'image/png' | 'image/jpg' | 'image/jpeg';
const validFileExtensions: validFileExtension[] = ['png' , 'jpg' , 'jpeg'];
const validMimeTypes: validMimeType[] = ['image/png' , 'image/jpg' , 'image/jpeg'];
export const saveImageToStorage = {
storage:diskStorage({
destination:'./images',
filename:(req,file,cb)=>{
const fileExtension:string = path.extname(file.originalname);
const fileName:string = generate() + fileExtension
cb(null,fileName)
}
}),
fileFilter: (req, file, cb) => {
const allowedMimeTypes: validMimeType[] = validMimeTypes;
allowedMimeTypes.includes(file.mimetype) ? cb(null,true) : cb(null,false)
}
}
export const removeFile = (fullFilePath:string):void => {
try{
fs.unlinkSync(fullFilePath);
} catch(err) {
console.log(err);
}
}
Could you please provide the AppModule code ?
Have you tried this :
import { graphqlUploadExpress } from 'graphql-upload';
...
....
....
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(graphqlUploadExpress()).forRoutes('graphql');
}
}
and in your GraphQLModule parameters :
uploads: false

Nestjs - file upload with fastify multipart

I am trying to upload multiple files with nestjs using the fastify adapter. I can do so following the tutorial in this link -article on upload
Now this does the job of file upload using fastify-multipart, but I couldnt make use of the request validations before uploading,
for example, here is my rule-file-models (which later I wanted to save to postgre)
import {IsUUID, Length, IsEnum, IsString, Matches, IsOptional} from "class-validator";
import { FileExtEnum } from "./enums/file-ext.enum";
import { Updatable } from "./updatable.model";
import {Expose, Type} from "class-transformer";
export class RuleFile {
#Expose()
#IsUUID("4", { always: true })
id: string;
#Expose()
#Length(2, 50, {
always: true,
each: true,
context: {
errorCode: "REQ-000",
message: `Filename shouldbe within 2 and can reach a max of 50 characters`,
},
})
fileNames: string[];
#Expose()
#IsEnum(FileExtEnum, { always: true, each: true })
fileExts: string[];
#IsOptional({each: true, message: 'File is corrupated'})
#Type(() => Buffer)
file: Buffer;
}
export class RuleFileDetail extends RuleFile implements Updatable {
#IsString()
#Matches(/[aA]{1}[\w]{6}/)
recUpdUser: string;
}
And I wanted to validate the multipart request and see if these are set properly.
I cannot make it to work with event subscription based approach. Here are a few things I tried - adding the interceptor, to check for the request
#Injectable()
export class FileUploadValidationInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req: FastifyRequest = context.switchToHttp().getRequest();
console.log('inside interceptor', req.body);
// content type cmes with multipart/form-data;boundary----. we dont need to valdidate the boundary
// TODO: handle split errors based on semicolon
const contentType = req.headers['content-type'].split(APP_CONSTANTS.CHAR.SEMI_COLON)[0];
console.log(APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType));
const isHeaderMultipart = contentType != null?
this.headerValidation(contentType): this.throwError(contentType);
**// CANNOT check fir req.file() inside this, as it throws undefined**
return next.handle();
}
headerValidation(contentType) {
return APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType) ? true : this.throwError(contentType);
}
throwError(contentType: string) {
throw AppConfigService.getCustomError('FID-HEADERS', `Request header does not contain multipart type:
Provided incorrect type - ${contentType}`);
}
}
I wasnt able to check req.file() in the above interceptor. It throws as undefined. I tried to follow the fastify-multipart
But I wasnt able to get the request data in a prehandler as provided in the documentation for fastify-multipart
fastify.post('/', async function (req, reply) {
// process a single file
// also, consider that if you allow to upload multiple files
// you must consume all files othwise the promise will never fulfill
const data = await req.file()
data.file // stream
data.fields // other parsed parts
data.fieldname
data.filename
data.encoding
data.mimetype
// to accumulate the file in memory! Be careful!
//
// await data.toBuffer() // Buffer
//
// or
await pump(data.file, fs.createWriteStream(data.filename))
I tried getting via by registering a prehandler hook of my own like this (executed as iife)
(async function bootstrap() {
const appConfig = AppConfigService.getAppCommonConfig();
const fastifyInstance = SERVERADAPTERINSTANCE.configureFastifyServer();
// #ts-ignore
const fastifyAdapter = new FastifyAdapter(fastifyInstance);
app = await NestFactory.create<NestFastifyApplication>(
AppModule,
fastifyAdapter
).catch((err) => {
console.log("err in creating adapter", err);
process.exit(1);
});
.....
app.useGlobalPipes(
new ValidationPipe({
errorHttpStatusCode: 500,
transform: true,
validationError: {
target: true,
value: true,
},
exceptionFactory: (errors: ValidationError[]) => {
// send it to the global exception filter\
AppConfigService.validationExceptionFactory(errors);
},
}),
);
app.register(require('fastify-multipart'), {
limits: {
fieldNameSize: 100, // Max field name size in bytes
fieldSize: 1000000, // Max field value size in bytes
fields: 10, // Max number of non-file fields
fileSize: 100000000000, // For multipart forms, the max file size
files: 3, // Max number of file fields
headerPairs: 2000, // Max number of header key=>value pairs
},
});
(app.getHttpAdapter().getInstance() as FastifyInstance).addHook('onRoute', (routeOptions) => {
console.log('all urls:', routeOptions.url);
if(routeOptions.url.includes('upload')) {
// The registration actually works, but I cant use the req.file() in the prehandler
console.log('###########################');
app.getHttpAdapter().getInstance().addHook('preHandler', FilePrehandlerService.fileHandler);
}
});
SERVERADAPTERINSTANCE.configureSecurity(app);
//Connect to database
await SERVERADAPTERINSTANCE.configureDbConn(app);
app.useStaticAssets({
root: join(__dirname, "..", "public"),
prefix: "/public/",
});
app.setViewEngine({
engine: {
handlebars: require("handlebars"),
},
templates: join(__dirname, "..", "views"),
});
await app.listen(appConfig.port, appConfig.host, () => {
console.log(`Server listening on port - ${appConfig.port}`);
});
})();
Here is the prehandler,
export class FilePrehandlerService {
constructor() {}
static fileHandler = async (req, reply) => {
console.log('coming inside prehandler');
console.log('req is a multipart req',await req.file);
const data = await req.file();
console.log('data received -filename:', data.filename);
console.log('data received- fieldname:', data.fieldname);
console.log('data received- fields:', data.fields);
return;
};
}
This pattern of registring and gettin the file using preHandler works in bare fastify application. I tried it
Bare fastify server:
export class FileController {
constructor() {}
async testHandler(req: FastifyRequest, reply: FastifyReply) {
reply.send('test reading dne');
}
async fileReadHandler(req, reply: FastifyReply) {
const data = await req.file();
console.log('field val:', data.fields);
console.log('field filename:', data.filename);
console.log('field fieldname:', data.fieldname);
reply.send('done');
}
}
export const FILE_CONTROLLER_INSTANCE = new FileController();
This is my route file
const testRoute: RouteOptions<Server, IncomingMessage, ServerResponse, RouteGenericInterface, unknown> = {
method: 'GET',
url: '/test',
handler: TESTCONTROLLER_INSTANCE.testMethodRouteHandler,
};
const fileRoute: RouteOptions = {
method: 'GET',
url: '/fileTest',
preHandler: fileInterceptor,
handler: FILE_CONTROLLER_INSTANCE.testHandler,
};
const fileUploadRoute: RouteOptions = {
method: 'POST',
url: '/fileUpload',
preHandler: fileInterceptor,
handler: FILE_CONTROLLER_INSTANCE.fileReadHandler,
};
const apiRoutes = [testRoute, fileRoute, fileUploadRoute];
export default apiRoutes;
Could someone let me know the right the way to get the fieldnames , validate them befr the service being called in Nestjs
Well, I have done something like this and It works great for me. Maybe it can work for you too.
// main.ts
import multipart from "fastify-multipart";
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.register(multipart);
// upload.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
BadRequestException,
} from "#nestjs/common";
import { FastifyRequest } from "fastify";
#Injectable()
export class UploadGuard implements CanActivate {
public async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest() as FastifyRequest;
const isMultipart = req.isMultipart();
if (!isMultipart)
throw new BadRequestException("multipart/form-data expected.");
const file = await req.file();
if (!file) throw new BadRequestException("file expected");
req.incomingFile = file;
return true;
}
}
// file.decorator.ts
import { createParamDecorator, ExecutionContext } from "#nestjs/common";
import { FastifyRequest } from "fastify";
export const File = createParamDecorator(
(_data: unknown, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest() as FastifyRequest;
const file = req.incomingFile;
return file
},
);
// post controller
#Post("upload")
#UseGuards(UploadGuard)
uploadFile(#File() file: Storage.MultipartFile) {
console.log(file); // logs MultipartFile from "fastify-multipart"
return "File uploaded"
}
and finally my typing file
declare global {
namespace Storage {
interface MultipartFile {
toBuffer: () => Promise<Buffer>;
file: NodeJS.ReadableStream;
filepath: string;
fieldname: string;
filename: string;
encoding: string;
mimetype: string;
fields: import("fastify-multipart").MultipartFields;
}
}
}
declare module "fastify" {
interface FastifyRequest {
incomingFile: Storage.MultipartFile;
}
}
So I found a simpler alternative. I started using fastify-multer. I used it along with this awesome lib - which made me use the multer for fastify - #webundsoehne/nest-fastify-file-upload
These are the changes I made. I registered the multer content process.
app.register(multer( {dest:path.join(process.cwd()+'/upload'),
limits:{
fields: 5, //Number of non-file fields allowed
files: 1,
fileSize: 2097152,// 2 MB,
}}).contentParser);
Then in the controller - I use it as the nestjs doc says . This actually makes fasitfy work with multer
#UseInterceptors(FileUploadValidationInterceptor, FileInterceptor('file'))
#Post('/multerSample')
async multerUploadFiles(#UploadedFile() file, #Body() ruleFileCreate: RuleFileCreate) {
console.log('data sent', ruleFileCreate);
console.log(file);
// getting the original name of the file - no matter what
ruleFileCreate.originalName = file.originalname;
return await this.fileService.fileUpload(file.buffer, ruleFileCreate);
}
BONUS - storing the file in local and storing it in DB - Please refer
github link