|
@@ -1,10 +1,9 @@
|
|
|
import { IResolvers } from 'apollo-server-express'
|
|
|
import fs from 'fs'
|
|
|
import randombytes from 'randombytes'
|
|
|
-import sharp from 'sharp'
|
|
|
-import ffmpeg from 'fluent-ffmpeg'
|
|
|
-import { tmpDir, uploadDir } from './constants'
|
|
|
+import { uploadDir } from './constants'
|
|
|
import { checkPermission } from '../user/resolvers'
|
|
|
+import { saveStreamToFile, processImage, processVideo, processAudio } from './utils'
|
|
|
|
|
|
export const resolvers: IResolvers = {
|
|
|
Query: {
|
|
@@ -17,38 +16,40 @@ export const resolvers: IResolvers = {
|
|
|
checkPermission(context, 'ADMIN')
|
|
|
return context.db.query.files(args, info)
|
|
|
},
|
|
|
- videos: (parent, args, context, info) => {
|
|
|
- checkPermission(context)
|
|
|
- return context.db.query.videos(args, info)
|
|
|
- },
|
|
|
- video: (parent, args, context, info) => {
|
|
|
+ file: (parent, args, context, info) => {
|
|
|
checkPermission(context, 'ADMIN')
|
|
|
- return context.db.query.video(args, info)
|
|
|
+ return context.db.query.file(args, info)
|
|
|
},
|
|
|
},
|
|
|
Mutation: {
|
|
|
- uploadFile: async (parent, { comment, file }, context, info) => {
|
|
|
+ uploadFile: async (parent, { file, ...args }, context, info) => {
|
|
|
checkPermission(context, 'ADMIN')
|
|
|
const fileInfo = await uploadFile(file)
|
|
|
|
|
|
return context.db.mutation.createFile(
|
|
|
{
|
|
|
data: {
|
|
|
+ ...args,
|
|
|
...fileInfo,
|
|
|
- comment,
|
|
|
user: { connect: { id: context.req.userId } },
|
|
|
},
|
|
|
},
|
|
|
info
|
|
|
)
|
|
|
},
|
|
|
- createVideo: (parent, args, context, info) => {
|
|
|
+ updateFile: (parent, args, context, info) => {
|
|
|
checkPermission(context, 'ADMIN')
|
|
|
- return context.db.mutation.createVideo(args, info)
|
|
|
+ return context.db.mutation.updateFile(args, info)
|
|
|
},
|
|
|
- updateVideo: (parent, args, context, info) => {
|
|
|
+ deleteFile: async (parent, { id }, context, info) => {
|
|
|
checkPermission(context, 'ADMIN')
|
|
|
- return context.db.mutation.updateVideo(args, info)
|
|
|
+ const file = await context.db.query.file({ where: { id } })
|
|
|
+ if (!file) throw Error(`File '${id}' not found.`)
|
|
|
+ console.log(file)
|
|
|
+ try {
|
|
|
+ await fs.promises.unlink(file.path)
|
|
|
+ } catch (error) {}
|
|
|
+ return context.db.mutation.deleteFile({ where: { id } })
|
|
|
},
|
|
|
},
|
|
|
}
|
|
@@ -73,99 +74,26 @@ async function fsFiles(directory: string) {
|
|
|
async function uploadFile(file: any) {
|
|
|
const { createReadStream, filename, mimetype, encoding } = await file
|
|
|
const stream = createReadStream()
|
|
|
-
|
|
|
const fsFilename = randombytes(16).toString('hex')
|
|
|
- const tmpPath = `${tmpDir}/${fsFilename}`
|
|
|
- const path = `${uploadDir}/${fsFilename}`
|
|
|
- let thumbnail = null
|
|
|
- const thumbnailFile = `thmb${fsFilename}`
|
|
|
- const thumbnailPath = `${uploadDir}/${thumbnailFile}`
|
|
|
- await new Promise((resolve, reject) => {
|
|
|
- const file = fs.createWriteStream(tmpPath)
|
|
|
- file.on('finish', resolve)
|
|
|
- file.on('error', (error) => {
|
|
|
- fs.unlink(tmpPath, () => {
|
|
|
- reject(error)
|
|
|
- })
|
|
|
- })
|
|
|
- stream.on('error', (error: any) => file.destroy(error))
|
|
|
- stream.pipe(file)
|
|
|
- })
|
|
|
+ const path: [string, string] = [uploadDir, fsFilename]
|
|
|
+ const filePath = path.join('/')
|
|
|
+
|
|
|
+ let fileDetails = { filename, mimetype, encoding, path: filePath }
|
|
|
|
|
|
if (mimetype.startsWith('image/')) {
|
|
|
- console.log('image')
|
|
|
- try {
|
|
|
- await processImage(tmpPath, path)
|
|
|
- await createThumbnail(tmpPath, thumbnailPath)
|
|
|
- await fs.promises.unlink(tmpPath)
|
|
|
- thumbnail = thumbnailPath
|
|
|
- } catch (error) {
|
|
|
- try {
|
|
|
- await fs.promises.unlink(tmpPath)
|
|
|
- await fs.promises.unlink(path)
|
|
|
- } catch (ignore) {}
|
|
|
- throw error
|
|
|
- }
|
|
|
+ const imageDetails = await processImage(stream, path)
|
|
|
+ fileDetails = { ...fileDetails, ...imageDetails }
|
|
|
} else if (mimetype.startsWith('video/')) {
|
|
|
- console.log('video')
|
|
|
- try {
|
|
|
- ffmpeg(path)
|
|
|
- .screenshot({
|
|
|
- timestamps: ['25%'],
|
|
|
- filename: thumbnailFile,
|
|
|
- folder: uploadDir,
|
|
|
- size: '200x?',
|
|
|
- })
|
|
|
- .on('end', () => fs.promises.rename(`${thumbnailPath}.png`, thumbnailPath))
|
|
|
- await fs.promises.rename(tmpPath, path)
|
|
|
- thumbnail = thumbnailPath
|
|
|
- } catch (error) {
|
|
|
- try {
|
|
|
- await fs.promises.unlink(tmpPath)
|
|
|
- await fs.promises.unlink(path)
|
|
|
- } catch (ignore) {}
|
|
|
- throw error
|
|
|
- }
|
|
|
+ const videoDetails = await processVideo(stream, path)
|
|
|
+ fileDetails = { ...fileDetails, ...videoDetails }
|
|
|
+ } else if (mimetype.startsWith('audio/')) {
|
|
|
+ const audioDetails = await processAudio(stream, path)
|
|
|
+ fileDetails = { ...fileDetails, ...audioDetails }
|
|
|
} else {
|
|
|
- console.log('no image')
|
|
|
- try {
|
|
|
- await fs.promises.rename(tmpPath, path)
|
|
|
- } catch (error) {
|
|
|
- try {
|
|
|
- await fs.promises.unlink(tmpPath)
|
|
|
- await fs.promises.unlink(path)
|
|
|
- } catch (ignore) {}
|
|
|
- throw error
|
|
|
- }
|
|
|
+ await saveStreamToFile(stream, path)
|
|
|
}
|
|
|
|
|
|
- const { size } = await fs.promises.stat(path)
|
|
|
+ const { size } = await fs.promises.stat(filePath)
|
|
|
|
|
|
- return {
|
|
|
- path,
|
|
|
- mimetype,
|
|
|
- thumbnail,
|
|
|
- filename,
|
|
|
- encoding,
|
|
|
- size,
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function processImage(tmpFile: string, outputFile: string) {
|
|
|
- return sharp(tmpFile)
|
|
|
- .resize(1600, 1600, {
|
|
|
- fit: sharp.fit.inside,
|
|
|
- withoutEnlargement: true,
|
|
|
- })
|
|
|
- .jpeg()
|
|
|
- .toFile(outputFile)
|
|
|
-}
|
|
|
-
|
|
|
-function createThumbnail(tmpFile: string, thumbnail: string) {
|
|
|
- return sharp(tmpFile)
|
|
|
- .resize(200, 200, {
|
|
|
- fit: sharp.fit.inside,
|
|
|
- })
|
|
|
- .jpeg()
|
|
|
- .toFile(thumbnail)
|
|
|
+ return { ...fileDetails, size }
|
|
|
}
|