import { UnsupportedMediaTypeException } from '@nestjs/common'
import { Scalar } from '@nestjs/graphql'
import { ValueNode } from 'graphql'
import { FileUpload, GraphQLUpload } from 'graphql-upload'
export type CSVParseProps = {
file: FileUpload
promise: Promise<FileUpload>
}
export type CSVUpload = Promise<FileUpload | Error>
export type CSVFile = FileUpload
@Scalar('CSV', () => CSV)
export class CSV {
description = 'CSV upload type.'
supportedFormats = ['text/csv']
parseLiteral(arg: ValueNode) {
const file = GraphQLUpload.parseLiteral(arg, (arg as any).value)
if (
file.kind === 'ObjectValue' &&
typeof file.filename === 'string' &&
typeof file.mimetype === 'string' &&
typeof file.encoding === 'string' &&
typeof file.createReadStream === 'function'
)
return Promise.resolve(file)
return null
}
// If this is `async` then any error thrown
// hangs and doesn't return to the user. However,
// if a non-promise is returned it fails reading the
// stream later. We can't evaluate the `sync`
// version of the file either as there's a data race (it's not
// always there). So we return the `Promise` version
// for usage that gets parsed after return...
parseValue(value: CSVParseProps) {
return value.promise.then((file) => {
if (!this.supportedFormats.includes(file.mimetype))
return new UnsupportedMediaTypeException(
`Unsupported file format. Supports: ${this.supportedFormats.join(
' '
)}.`
)
return file
})
}
serialize(value: unknown) {
return GraphQLUpload.serialize(value)
}
}
import * as FileType from 'file-type'
import { GraphQLError, GraphQLScalarType } from 'graphql'
import { Readable } from 'stream'
export interface FileUpload {
filename: string
mimetype: string
encoding: string
createReadStream: () => Readable
}
export const GraphQLUpload = new GraphQLScalarType({
name: 'Upload',
description: 'The `Upload` scalar type represents a file upload.',
async parseValue(value: Promise<FileUpload>): Promise<FileUpload> {
const upload = await value
const stream = upload.createReadStream()
const fileType = await FileType.fromStream(stream)
if (fileType?.mime !== upload.mimetype)
throw new GraphQLError('Mime type does not match file content.')
return upload
},
parseLiteral(ast): void {
throw new GraphQLError('Upload literal unsupported.', ast)
},
serialize(): void {
throw new GraphQLError('Upload serialization unsupported.')
},
})