Преглед на файлове

mainly working on file upload

Tomi Cvetic преди 5 години
родител
ревизия
05686f2851

+ 123 - 0
backend/database/generated/prisma-client/index.d.ts

@@ -969,6 +969,12 @@ export interface ClientConstructor<T> {
 export type FileOrderByInput =
   | "id_ASC"
   | "id_DESC"
+  | "path_ASC"
+  | "path_DESC"
+  | "name_ASC"
+  | "name_DESC"
+  | "description_ASC"
+  | "description_DESC"
   | "filename_ASC"
   | "filename_DESC"
   | "mimetype_ASC"
@@ -1229,6 +1235,48 @@ export interface FileWhereInput {
   id_not_starts_with?: ID_Input;
   id_ends_with?: ID_Input;
   id_not_ends_with?: ID_Input;
+  path?: String;
+  path_not?: String;
+  path_in?: String[] | String;
+  path_not_in?: String[] | String;
+  path_lt?: String;
+  path_lte?: String;
+  path_gt?: String;
+  path_gte?: String;
+  path_contains?: String;
+  path_not_contains?: String;
+  path_starts_with?: String;
+  path_not_starts_with?: String;
+  path_ends_with?: String;
+  path_not_ends_with?: String;
+  name?: String;
+  name_not?: String;
+  name_in?: String[] | String;
+  name_not_in?: String[] | String;
+  name_lt?: String;
+  name_lte?: String;
+  name_gt?: String;
+  name_gte?: String;
+  name_contains?: String;
+  name_not_contains?: String;
+  name_starts_with?: String;
+  name_not_starts_with?: String;
+  name_ends_with?: String;
+  name_not_ends_with?: String;
+  description?: String;
+  description_not?: String;
+  description_in?: String[] | String;
+  description_not_in?: String[] | String;
+  description_lt?: String;
+  description_lte?: String;
+  description_gt?: String;
+  description_gte?: String;
+  description_contains?: String;
+  description_not_contains?: String;
+  description_starts_with?: String;
+  description_not_starts_with?: String;
+  description_ends_with?: String;
+  description_not_ends_with?: String;
   filename?: String;
   filename_not?: String;
   filename_in?: String[] | String;
@@ -2381,6 +2429,9 @@ export interface FileCreateManyInput {
 }
 
 export interface FileCreateInput {
+  path: String;
+  name?: String;
+  description?: String;
   filename: String;
   mimetype: String;
   truncated: Boolean;
@@ -2654,6 +2705,9 @@ export interface FileUpdateWithWhereUniqueNestedInput {
 }
 
 export interface FileUpdateDataInput {
+  path?: String;
+  name?: String;
+  description?: String;
   filename?: String;
   mimetype?: String;
   truncated?: Boolean;
@@ -2682,6 +2736,48 @@ export interface FileScalarWhereInput {
   id_not_starts_with?: ID_Input;
   id_ends_with?: ID_Input;
   id_not_ends_with?: ID_Input;
+  path?: String;
+  path_not?: String;
+  path_in?: String[] | String;
+  path_not_in?: String[] | String;
+  path_lt?: String;
+  path_lte?: String;
+  path_gt?: String;
+  path_gte?: String;
+  path_contains?: String;
+  path_not_contains?: String;
+  path_starts_with?: String;
+  path_not_starts_with?: String;
+  path_ends_with?: String;
+  path_not_ends_with?: String;
+  name?: String;
+  name_not?: String;
+  name_in?: String[] | String;
+  name_not_in?: String[] | String;
+  name_lt?: String;
+  name_lte?: String;
+  name_gt?: String;
+  name_gte?: String;
+  name_contains?: String;
+  name_not_contains?: String;
+  name_starts_with?: String;
+  name_not_starts_with?: String;
+  name_ends_with?: String;
+  name_not_ends_with?: String;
+  description?: String;
+  description_not?: String;
+  description_in?: String[] | String;
+  description_not_in?: String[] | String;
+  description_lt?: String;
+  description_lte?: String;
+  description_gt?: String;
+  description_gte?: String;
+  description_contains?: String;
+  description_not_contains?: String;
+  description_starts_with?: String;
+  description_not_starts_with?: String;
+  description_ends_with?: String;
+  description_not_ends_with?: String;
   filename?: String;
   filename_not?: String;
   filename_in?: String[] | String;
@@ -2745,6 +2841,9 @@ export interface FileUpdateManyWithWhereNestedInput {
 }
 
 export interface FileUpdateManyDataInput {
+  path?: String;
+  name?: String;
+  description?: String;
   filename?: String;
   mimetype?: String;
   truncated?: Boolean;
@@ -4141,6 +4240,9 @@ export interface EventUpdateManyMutationInput {
 }
 
 export interface FileUpdateInput {
+  path?: String;
+  name?: String;
+  description?: String;
   filename?: String;
   mimetype?: String;
   truncated?: Boolean;
@@ -4149,6 +4251,9 @@ export interface FileUpdateInput {
 }
 
 export interface FileUpdateManyMutationInput {
+  path?: String;
+  name?: String;
+  description?: String;
   filename?: String;
   mimetype?: String;
   truncated?: Boolean;
@@ -4683,6 +4788,9 @@ export interface ProjectSubscription
 
 export interface File {
   id: ID_Output;
+  path: String;
+  name?: String;
+  description?: String;
   filename: String;
   mimetype: String;
   truncated: Boolean;
@@ -4692,6 +4800,9 @@ export interface File {
 
 export interface FilePromise extends Promise<File>, Fragmentable {
   id: () => Promise<ID_Output>;
+  path: () => Promise<String>;
+  name: () => Promise<String>;
+  description: () => Promise<String>;
   filename: () => Promise<String>;
   mimetype: () => Promise<String>;
   truncated: () => Promise<Boolean>;
@@ -4703,6 +4814,9 @@ export interface FileSubscription
   extends Promise<AsyncIterator<File>>,
     Fragmentable {
   id: () => Promise<AsyncIterator<ID_Output>>;
+  path: () => Promise<AsyncIterator<String>>;
+  name: () => Promise<AsyncIterator<String>>;
+  description: () => Promise<AsyncIterator<String>>;
   filename: () => Promise<AsyncIterator<String>>;
   mimetype: () => Promise<AsyncIterator<String>>;
   truncated: () => Promise<AsyncIterator<Boolean>>;
@@ -6759,6 +6873,9 @@ export interface FileSubscriptionPayloadSubscription
 
 export interface FilePreviousValues {
   id: ID_Output;
+  path: String;
+  name?: String;
+  description?: String;
   filename: String;
   mimetype: String;
   truncated: Boolean;
@@ -6770,6 +6887,9 @@ export interface FilePreviousValuesPromise
   extends Promise<FilePreviousValues>,
     Fragmentable {
   id: () => Promise<ID_Output>;
+  path: () => Promise<String>;
+  name: () => Promise<String>;
+  description: () => Promise<String>;
   filename: () => Promise<String>;
   mimetype: () => Promise<String>;
   truncated: () => Promise<Boolean>;
@@ -6781,6 +6901,9 @@ export interface FilePreviousValuesSubscription
   extends Promise<AsyncIterator<FilePreviousValues>>,
     Fragmentable {
   id: () => Promise<AsyncIterator<ID_Output>>;
+  path: () => Promise<AsyncIterator<String>>;
+  name: () => Promise<AsyncIterator<String>>;
+  description: () => Promise<AsyncIterator<String>>;
   filename: () => Promise<AsyncIterator<String>>;
   mimetype: () => Promise<AsyncIterator<String>>;
   truncated: () => Promise<AsyncIterator<Boolean>>;

+ 111 - 0
backend/database/generated/prisma-client/prisma-schema.js

@@ -724,6 +724,9 @@ input EventWhereUniqueInput {
 
 type File {
   id: ID!
+  path: String!
+  name: String
+  description: String
   filename: String!
   mimetype: String!
   truncated: Boolean!
@@ -738,6 +741,9 @@ type FileConnection {
 }
 
 input FileCreateInput {
+  path: String!
+  name: String
+  description: String
   filename: String!
   mimetype: String!
   truncated: Boolean!
@@ -758,6 +764,12 @@ type FileEdge {
 enum FileOrderByInput {
   id_ASC
   id_DESC
+  path_ASC
+  path_DESC
+  name_ASC
+  name_DESC
+  description_ASC
+  description_DESC
   filename_ASC
   filename_DESC
   mimetype_ASC
@@ -776,6 +788,9 @@ enum FileOrderByInput {
 
 type FilePreviousValues {
   id: ID!
+  path: String!
+  name: String
+  description: String
   filename: String!
   mimetype: String!
   truncated: Boolean!
@@ -798,6 +813,48 @@ input FileScalarWhereInput {
   id_not_starts_with: ID
   id_ends_with: ID
   id_not_ends_with: ID
+  path: String
+  path_not: String
+  path_in: [String!]
+  path_not_in: [String!]
+  path_lt: String
+  path_lte: String
+  path_gt: String
+  path_gte: String
+  path_contains: String
+  path_not_contains: String
+  path_starts_with: String
+  path_not_starts_with: String
+  path_ends_with: String
+  path_not_ends_with: String
+  name: String
+  name_not: String
+  name_in: [String!]
+  name_not_in: [String!]
+  name_lt: String
+  name_lte: String
+  name_gt: String
+  name_gte: String
+  name_contains: String
+  name_not_contains: String
+  name_starts_with: String
+  name_not_starts_with: String
+  name_ends_with: String
+  name_not_ends_with: String
+  description: String
+  description_not: String
+  description_in: [String!]
+  description_not_in: [String!]
+  description_lt: String
+  description_lte: String
+  description_gt: String
+  description_gte: String
+  description_contains: String
+  description_not_contains: String
+  description_starts_with: String
+  description_not_starts_with: String
+  description_ends_with: String
+  description_not_ends_with: String
   filename: String
   filename_not: String
   filename_in: [String!]
@@ -874,6 +931,9 @@ input FileSubscriptionWhereInput {
 }
 
 input FileUpdateDataInput {
+  path: String
+  name: String
+  description: String
   filename: String
   mimetype: String
   truncated: Boolean
@@ -882,6 +942,9 @@ input FileUpdateDataInput {
 }
 
 input FileUpdateInput {
+  path: String
+  name: String
+  description: String
   filename: String
   mimetype: String
   truncated: Boolean
@@ -890,6 +953,9 @@ input FileUpdateInput {
 }
 
 input FileUpdateManyDataInput {
+  path: String
+  name: String
+  description: String
   filename: String
   mimetype: String
   truncated: Boolean
@@ -910,6 +976,9 @@ input FileUpdateManyInput {
 }
 
 input FileUpdateManyMutationInput {
+  path: String
+  name: String
+  description: String
   filename: String
   mimetype: String
   truncated: Boolean
@@ -948,6 +1017,48 @@ input FileWhereInput {
   id_not_starts_with: ID
   id_ends_with: ID
   id_not_ends_with: ID
+  path: String
+  path_not: String
+  path_in: [String!]
+  path_not_in: [String!]
+  path_lt: String
+  path_lte: String
+  path_gt: String
+  path_gte: String
+  path_contains: String
+  path_not_contains: String
+  path_starts_with: String
+  path_not_starts_with: String
+  path_ends_with: String
+  path_not_ends_with: String
+  name: String
+  name_not: String
+  name_in: [String!]
+  name_not_in: [String!]
+  name_lt: String
+  name_lte: String
+  name_gt: String
+  name_gte: String
+  name_contains: String
+  name_not_contains: String
+  name_starts_with: String
+  name_not_starts_with: String
+  name_ends_with: String
+  name_not_ends_with: String
+  description: String
+  description_not: String
+  description_in: [String!]
+  description_not_in: [String!]
+  description_lt: String
+  description_lte: String
+  description_gt: String
+  description_gte: String
+  description_contains: String
+  description_not_contains: String
+  description_starts_with: String
+  description_not_starts_with: String
+  description_ends_with: String
+  description_not_ends_with: String
   filename: String
   filename_not: String
   filename_in: [String!]

+ 3 - 0
backend/datamodel.prisma

@@ -11,6 +11,9 @@ type User {
 
 type File {
   id: ID! @unique
+  path: String!
+  name: String
+  description: String
   filename: String!
   mimetype: String!
   truncated: Boolean!

+ 8 - 4
backend/index.js

@@ -1,8 +1,9 @@
 require('dotenv').config()
 const { GraphQLServer } = require('graphql-yoga')
-const fileUpload = require('express-fileupload')
+const multer = require('multer')
 const { resolvers } = require('./src/resolvers')
 const { db } = require('./src/db')
+const upload = multer({ dest: './static/uploads/' })
 
 const server = new GraphQLServer({
   typeDefs: './schema.graphql',
@@ -14,11 +15,14 @@ const server = new GraphQLServer({
   })
 })
 
-server.express.use(fileUpload())
-server.express.post('/upload', (req, res) => {
-  console.log(req.files)
+server.express.post('/upload', upload.single('file'), async (req, res) => {
+  const { file } = req
+  if (!file) await res.json({ error: 'File not received.' })
+  await res.json({ file })
 })
 
+server.express.get('/upload', async (req, res) => res.json({ very: 'good' }))
+
 server.start(
   {
     cors: {

+ 20 - 0
backend/package-lock.json

@@ -388,6 +388,11 @@
         }
       }
     },
+    "append-field": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+      "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
+    },
     "arch": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz",
@@ -4490,6 +4495,21 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
       "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
     },
+    "multer": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.1.tgz",
+      "integrity": "sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw==",
+      "requires": {
+        "append-field": "^1.0.0",
+        "busboy": "^0.2.11",
+        "concat-stream": "^1.5.2",
+        "mkdirp": "^0.5.1",
+        "object-assign": "^4.1.1",
+        "on-finished": "^2.3.0",
+        "type-is": "^1.6.4",
+        "xtend": "^4.0.0"
+      }
+    },
     "multimatch": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",

+ 2 - 0
backend/package.json

@@ -7,6 +7,8 @@
     "dotenv": "^7.0.0",
     "express-fileupload": "^1.1.4",
     "graphql-yoga": "^1.17.4",
+    "lodash": "^4.17.11",
+    "multer": "^1.4.1",
     "prisma": "^1.30.1",
     "prisma-binding": "^2.3.10",
     "prisma-client-lib": "^1.30.1",

+ 6 - 1
backend/schema.graphql

@@ -1,10 +1,14 @@
+scalar Upload
+
 type Query {
-  projects: [Project]!,
+  projects: [Project]!
+  uploads: [File]
   connectionCommand(connectionId: String!, command: String!): String!
 }
 
 type Mutation {
   createUser(name: String!, email: String!): User!
+  uploadFile(file: Upload!): File!
 }
 
 type User {
@@ -18,6 +22,7 @@ type User {
 
 type File {
   id: ID! 
+  path: String!
   filename: String!
   mimetype: String!
   truncated: Boolean!

+ 27 - 0
backend/src/modules/file.js

@@ -0,0 +1,27 @@
+const SAMPLE_FILE = {
+  id: '13',
+  path: 'uploads/hello.txt',
+  filename: 'hello.txt',
+  mimetype: 'text/plain',
+  truncated: false,
+  size: 533421,
+  md5: 'asd5675a5sd'
+}
+
+async function processUpload (upload) {
+  console.log(upload)
+  const { stream, filename, mimetype, encoding } = await upload
+  console.log(stream, filename)
+  return SAMPLE_FILE
+}
+
+const resolvers = {
+  Query: {
+    uploads: () => [SAMPLE_FILE]
+  },
+  Mutation: {
+    uploadFile: (obj, { file }) => processUpload(file)
+  }
+}
+
+module.exports = { resolvers }

+ 9 - 4
backend/src/resolvers.js

@@ -1,3 +1,6 @@
+const { merge } = require('lodash')
+const files = require('./modules/file')
+
 const { forwardTo } = require('prisma-binding')
 
 const Query = {
@@ -5,10 +8,12 @@ const Query = {
   connectionCommand: (parent, args, context, info) => 'Hello!'
 }
 
-const Mutations = {}
-
-const resolvers = {
-  Query
+const Mutation = {
 }
 
+const resolvers = merge({
+  Query,
+  Mutation
+}, files.resolvers)
+
 module.exports = { resolvers }

BIN
backend/static/uploads/3ac2485856725c4743e5df66b40f790c


BIN
backend/static/uploads/a12dcefb6e223a41810c89a7f8b397fb


BIN
backend/static/uploads/e3765e4c60c1b7f9e24a0a54559c1bdb


+ 29 - 0
frontend/components/File.js

@@ -0,0 +1,29 @@
+import React from 'react'
+import gql from 'graphql-tag'
+import { Query } from 'react-apollo'
+
+const QUERY_FILES = gql`
+  query QUERY_FILES {
+    id
+    filename
+    mimetype
+    truncated
+    size
+    md5
+  }
+`
+
+class File extends React.Component {
+  render () {
+    const { id } = this.props
+    return (
+      <Query query={QUERY_FILES} variables={{ id }}>
+        {({ data, error, loading }) => (
+          <p>Here I expect some files.</p>
+        )}
+      </Query>
+    )
+  }
+}
+
+export default File

+ 52 - 0
frontend/components/FileUpload.js

@@ -0,0 +1,52 @@
+import React from 'react'
+import gql from 'graphql-tag'
+import { endpoint } from '../config'
+/*import { Query } from 'react-apollo'
+
+const QUERY_UPLOAD = gql`
+  query QUERY_UPLOAD($id: ID!) {
+    upload(id: $id) {
+      id
+      filename
+      path
+      description
+      name
+      size
+    }
+  } 
+`*/
+
+class FileUpload extends React.Component {
+  state = {
+    path: ''
+  }
+
+  upload = async event => {
+    event.preventDefault()
+    const { validity, files } = event.target
+    console.log(event, validity, files, files[0])
+    const data = new FormData()
+    data.append('file', files[0])
+
+    const res = await fetch(`${endpoint}/upload`, {
+      method: 'POST',
+      body: data
+    })
+
+    const { error, file } = await res.json()
+    console.log(error, file)
+    if (error) throw new Error('Failed to upload.')
+
+    this.setState({ path: `${endpoint}/static/uploads/${file}` })
+  }
+
+  render() {
+    return (
+      <form>
+        <input type='file' name='file' id='file' onChange={this.upload} /><img src={this.state.name} alt={this.state.name} />
+      </form>
+    )
+  }
+}
+
+export default FileUpload

+ 0 - 0
frontend/components/Instrument.js


+ 4 - 5
frontend/lib/withApollo.js

@@ -1,20 +1,19 @@
 /**
  * Using next-with-apollo
  * https://github.com/lfades/next-with-apollo
- * 
+ *
  * Changes:
  * * Reading endpoint and prodEndpoint from a config file
  * * Setting request to handle credentials.
  */
 
 import withApollo from 'next-with-apollo'
-import ApolloClient, { InMemoryCache } from 'apollo-boost'
+import ApolloClient from 'apollo-boost'
 import { endpoint, prodEndpoint } from '../config'
 
-function createClient({ ctx, headers, initialState }) {
+function createClient ({ ctx, headers, initialState }) {
   return new ApolloClient({
     uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
-    cache: new InMemoryCache().restore(initialState || {}),
     request: operation => {
       operation.setContext({
         fetchOptions: {
@@ -30,4 +29,4 @@ function createClient({ ctx, headers, initialState }) {
   })
 }
 
-export default withApollo(createClient)
+export default withApollo(createClient)

+ 15 - 0
frontend/package-lock.json

@@ -1197,6 +1197,16 @@
         }
       }
     },
+    "apollo-upload-client": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-10.0.0.tgz",
+      "integrity": "sha512-N0SENiEkZXoY4nl9xxrXFcj/cL0AVkSNQ4aYXSaruCBWE0aKpK6aCe4DBmiEHrK3FAsMxZPEJxBRIWNbsXT8dw==",
+      "requires": {
+        "apollo-link": "^1.2.6",
+        "apollo-link-http-common": "^0.2.8",
+        "extract-files": "^5.0.0"
+      }
+    },
     "apollo-utilities": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.2.1.tgz",
@@ -2769,6 +2779,11 @@
         }
       }
     },
+    "extract-files": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-5.0.1.tgz",
+      "integrity": "sha512-qRW6y9eKF0VbCyOoOEtFhzJ3uykAw8GKwQVXyAIqwocyEWW4m+v+evec34RwtUkkxxHh7NKBLJ6AnXM8W4dH5w=="
+    },
     "fast-deep-equal": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",

+ 6 - 0
frontend/package.json

@@ -13,6 +13,12 @@
   "license": "ISC",
   "dependencies": {
     "apollo-boost": "^0.3.1",
+    "apollo-cache-inmemory": "^1.5.1",
+    "apollo-client": "^2.5.1",
+    "apollo-link": "^1.2.11",
+    "apollo-link-error": "^1.1.10",
+    "apollo-link-http": "^1.5.14",
+    "apollo-upload-client": "^10.0.0",
     "dotenv": "^7.0.0",
     "graphql": "^14.2.1",
     "graphql-tag": "^2.10.1",

+ 2 - 2
frontend/pages/index.js

@@ -3,11 +3,11 @@
  * https://www.prisma.io/docs/1.29/get-started/03-build-graphql-servers-with-prisma-JAVASCRIPT-e001/
  */
 
-import ProjectPage from './projects'
+import FileUpload from '../components/FileUpload'
 
 const Index = props => (
   <div>
-    <ProjectPage />
+    <FileUpload />
   </div>
 )