Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1778c92
fix: remove old state.value and use method to retrieve current value
ochicf Jun 27, 2018
a05c45a
Merge remote-tracking branch 'origin/master' into develop
ochicf Jul 27, 2018
03f7270
add graphql FSFile type
ochicf Jul 27, 2018
0be8733
add functions to create resolvers
ochicf Jul 27, 2018
0e3fa1f
create resolver on server schema generation
ochicf Jul 27, 2018
c1ff742
always resolve to a FSFile
ochicf Jul 27, 2018
41661dd
resolve url with the given version
ochicf Jul 27, 2018
8441a70
avoid exposing functions that do not need to be consumed
ochicf Jul 27, 2018
cc347a1
do not expose generateFieldSchemaBase
ochicf Jul 27, 2018
ee4c00c
move defaultResolveId to its own file in modules folder
ochicf Jul 27, 2018
06a7f7d
add a default previewFromValue callback
ochicf Jul 27, 2018
1e4326a
default index to null
ochicf Jul 27, 2018
80fb173
add to FSCollections properties that Vulcan created collections have
ochicf Aug 9, 2018
1cc7381
execute hooks in some of the FSCollection events
ochicf Aug 9, 2018
2e9c63d
update docs
ochicf Aug 9, 2018
1c28b53
update ostrio:files
ochicf Aug 9, 2018
51442a8
Merge pull request #14 from OrigenStudio/feature/resolve-files
ochicf Aug 9, 2018
36445d2
bump version to 0.0.6-rc.1
ochicf Aug 10, 2018
224d9d3
bump version to 0.1.0-rc.1
ochicf Sep 17, 2018
47078e9
remove createS3Client stub
ochicf Nov 13, 2018
7584bc7
bump version
ochicf Nov 13, 2018
391c45c
update vulcan-files version
ochicf Nov 13, 2018
4db50aa
wrap all 3rd party methods in storageProvider argument
ochicf Nov 13, 2018
676ea0a
remove S3 related documentation, since it is in a separate package
ochicf Nov 13, 2018
bffde6b
remove s3 export
ochicf Nov 13, 2018
f2b5bcc
fix: version
ochicf Nov 13, 2018
934f971
add basic documentation to integrate with 3rd party storage providers
ochicf Nov 13, 2018
2b7882b
update to Apollo 2
eric-burel Jul 10, 2019
d1b0849
use new callback to register the upload middleware on /graphql
eric-burel Jul 11, 2019
de218e2
cleanup callback
eric-burel Jul 11, 2019
8b61455
set variables correctly server side
eric-burel Jul 11, 2019
21b1847
try to update editHandler
eric-burel Jul 11, 2019
3a41493
update to newest Vulcan syntax
eric-burel Jul 11, 2019
1d10d9a
update onUpdate to 1.12 syntax
eric-burel Jul 11, 2019
324608f
update UploadInput with Components pattern
eric-burel Jul 12, 2019
4a2ef12
working on file deletion
eric-burel Jul 12, 2019
aa65972
can delete files for single upload/creation
eric-burel Jul 17, 2019
2fe2333
allow to pass down dropZone props
eric-burel Jul 17, 2019
4391bfb
lighter colors as a default
eric-burel Jul 17, 2019
5c121b2
update npm dependencies in README
eric-burel Jul 17, 2019
ad6394b
fix packages list
eric-burel Jul 18, 2019
bc1c851
update vulcan core version
EloyID Oct 24, 2019
8ecb564
change package version
juliensl Oct 24, 2019
c0021db
Merge branch 'feature/apollo2' of https://github.com/live-for-good/vu…
juliensl Oct 24, 2019
c385e05
bugfix client graphQL call
juliensl Mar 2, 2020
9e56594
simplify field schema generator
eric-burel Aug 26, 2020
7d31747
do not write file on disk anymore, load the buffer directly
eric-burel Aug 27, 2020
aff5491
cleanup addFileFromReadable
eric-burel Aug 27, 2020
92e2b95
get file info when uploading
eric-burel Aug 31, 2020
eda709f
support an addFileFromBuffer method when streams fails
eric-burel Aug 31, 2020
269693e
add onDelete, not yet working
eric-burel Sep 2, 2020
557dc7c
fixed onDelete cb
eric-burel Sep 2, 2020
30bcd78
get the file document to compute its path
eric-burel Sep 7, 2020
69a0d2f
fix file reading function, do not swallow errors anymore
eric-burel Sep 7, 2020
22dae26
pass file definition
eric-burel Sep 7, 2020
1a5456b
fix getValue, pass file document
eric-burel Sep 14, 2020
7c9cffa
upload input passes document as props
EloyID Sep 16, 2020
9b6a4c8
quickfix images
EloyID Sep 17, 2020
67e9bba
renenable promise
eric-burel Sep 24, 2020
3901b70
update addFileFromReadable
eric-burel Sep 24, 2020
8aa289e
make old files system work again
EloyID Sep 24, 2020
453a51e
update comments
eric-burel Sep 25, 2020
b3256c3
Fixed some typos
Sep 25, 2020
7078943
Merge pull request #1 from Einlar/patch-1
eric-burel Sep 28, 2020
e66b70c
update uploadinput to new dropzone
EloyID Nov 16, 2020
b9f0b92
Merge branch 'develop' of https://github.com/live-for-good/vulcan-fil…
EloyID Nov 16, 2020
e3874b3
add custom fetch
eric-burel Nov 27, 2020
e768271
make abort optional
eric-burel Nov 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 66 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,19 @@ In your project's root folder run:
To avoid using `Npm.depends` this package does not include any NPM module, so you will have to install them yourself by running the following command:

```
meteor npm install async-busboy@^0.6.2 brackets2dots@^1.1.0 lodash@^4.0.0 object-path@^0.11.4 randomstring@^1.1.5 react-dropzone@^3.12.2 recursive-iterator@^3.3.0 knox@^0.9.2 gm@^1.23.1
meteor npm install gm@^1.23.1 lodash@^4.0.0 randomstring@^1.1.5 react-dropzone@^3.12.2 recursive-iterator@^3.3.0 apollo-upload-client@^11.0.0
```

Alternatively, here you have a list of the packages and versions required, so you can add them to your project's `package.json`:

```json
{
"async-busboy": "^0.6.2",
"brackets2dots": "^1.1.0",
"gm": "^1.23.1",
"knox": "^0.9.2",
"lodash": "^4.0.0",
"object-path": "^0.11.4",
"randomstring": "^1.1.5",
"react-dropzone": "^3.12.2",
"recursive-iterator": "^3.3.0"
"recursive-iterator": "^3.3.0",
"apollo-upload-client": "^11.0.0"
}
```

Expand All @@ -49,6 +46,7 @@ One problem of directly working with [Meteor-Files](https://github.com/VeliovGro
import { createFSCollection } from 'meteor/origenstudio:vulcan-files';

const MyFSCollection = createFSCollection({
typeName: 'MyFSTypeName', // optional, defaults to `collectionName`
collectionName: 'MyFSCollection',
// ...other FilesCollection options
});
Expand All @@ -66,59 +64,82 @@ This module provides a `generateFieldSchema` function that will handle many of t
// TODO add documentation, see `Vulcanstagram` example for now
```

### 2.3. Uploading to 3rd parties
### 2.3. Callback hooks

Right now only Amazon S3 is supported.
#### 2.3.1. Upload hooks

#### 2.3.1 Amazon S3
##### After upload

You can create an S3 client easily with the `createS3Client` function: you only need to provide it your bucket configuration and your CloudFront domain. As always, a stub function is exported in client so you can use this function anywhere.
Two hooks are executed when a file has been uploaded:

Since you'll want to have your S3 configuration in your settings, you can retrieve them with Vulcan's `getSetting` function.
- `*.upload.after`: runs for all collections
- `{typename}.upload.after`: runs with the uploaded file type name

```json
{
"amazonAWSS3": {
"mainBucket": {
"cfdomain": "https://yourdomain.cloudfront.net",
"client": {
"key": "",
"secret": "",
"region": "eu-west-1",
"bucket": "your-bucket-name"
}
}
}
}
Signature of callbacks added to these hooks:

```
(fileDocument, { FSCollection }) => fileDocument
```

Now you can create the S3 client from your settings:
Note that these hooks won't reflect any change on the file document (as it has already been saved), but it can be useful to perform side effects.

```js
import { getSetting } from 'meteor/vulcan:core';
import { createS3Client } from 'meteor/origenstudio:vulcan-files';
### 2.4. Uploading to 3rd parties

// make sure the path of the settings match your own!
const s3Client = createS3Client(
getSetting('amazonAWSS3.mainBucket.client'),
getSetting('amazonAWSS3.mainBucket.cfdomain'),
);
This package provides an easy integration with third party services, though it does not include any out of the box. You can integrate with you preferred 3rd party service by using one of the following packages:

if (Meteor.isClient) {
// is empty object so you can safely retrieve properties from it
console.log(s3Client); //-> {}
}
- Amazon S3: [origenstudio:vulcan-files-s3](https://github.com/OrigenStudio/vulcan-files-s3)

```
##### Creating custom integrations

Once you have your client, you can use it to provide the 3rd party function that `createFSCollection` expects:
You can integrate with your own third party storage provider by setting the `storageProvider` option when creating the files collection. It should have the following shape:

```js
const MyFilesS3 = createFSCollection({
collectionName: 'MyFilesS3',
uploadTo3rdParty: s3Client.upload,
deleteFrom3rdParty: s3Client.delete,
})
const storageProvider = {
/**
* Called when a document is inserted, and it should be used to upload the file
* into the storage provider.
*
* @param {Collection} FSCollection
* Meteor Files collection
* @param {string} documentId
* Id of the inserted file document
* @param {Object} versionRef
* Information of the version of the file being uploaded
* @return {Promise<any>}
*/
upload: async (FSCollection, documentId, versionRef) => versionRef,
/**
* Called when a document is deleted, and it should be used to delete the file
* from the storage provider.
*
* @param {Collection} FSCollection
* Meteor Files collection
* @param {string} documentId
* Id of the file document being deleted
* @param {Object} versionRef
* Information of the version of the file being deleted
* @return {Promise<any>}
* @throws Error if could not delete file
*/
delete: async (FSCollection, documentId, versionRef) => versionRef,
/**
* Called when a document is requested to be served, and it should be used to
* serve the file from the storage provider.
*
* Note that by returning `false` the standard behavior will be resumed, and
* it should be done when the file has not been uploaded (ex: during upload).
*
* @param {object} http
* Middleware request instance, as provided by Meteor Files
* @param {object} fileRef
* The fileRef of the document to be served
* @param {string} version
* The version name of the document to be served
* @return {Boolean}
* `true` to intercept request, `false` to continue standard behaviour
*/
serve: (http, fileRef, version) => false,
};
```

## 3. Examples
Expand All @@ -131,5 +152,4 @@ Features:

- single file
- uses default field's value behavior, so only the file id is stored
- resolves only the file url
- images will be uploaded to `S3` if config is provided
9 changes: 0 additions & 9 deletions lib/client/createS3ClientStub.js

This file was deleted.

105 changes: 105 additions & 0 deletions lib/client/customFetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* A Fetch implementation that supports upload tracking
*
* To be used only in this specific scenario, prefer normal fetch as a default and during SSR
* @see https://github.com/jaydenseric/apollo-upload-client/issues/88
*
* @example
* export const Default = withMp3FileUpload(({ mutate }) => {
const [file, setFile] = useState<null | File>(null);
const [progress, setProgress] = useState<number>(0);

useEffect(() => {
if (!mutate || !file) {
return;
}
let abort: any;
mutate({
variables: {
file
},
context: {
fetchOptions: {
useUpload: true,
onProgress: (ev: ProgressEvent) => {
setProgress(ev.loaded / ev.total);
},
onAbortPossible: (abortHandler: any) => {
abort = abortHandler;
}
}
}
}).catch(err => console.log(err));

return () => {
if (abort) {
abort();
}
};
}, [file]);
}
*/

const parseHeaders = (rawHeaders) => {
const headers = new Headers();
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
const preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " ");
preProcessedHeaders.split(/\r?\n/).forEach((line) => {
const parts = line.split(":");
const key = parts.shift().trim();
if (key) {
const value = parts.join(":").trim();
headers.append(key, value);
}
});
return headers;
};

export const uploadFetch = (url, options) =>
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
const opts = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || ""),
};
opts.url =
"responseURL" in xhr
? xhr.responseURL
: opts.headers.get("X-Request-URL");
const body = "response" in xhr ? xhr.response : xhr.responseText;
resolve(new Response(body, opts));
};
xhr.onerror = () => {
reject(new TypeError("Network request failed"));
};
xhr.ontimeout = () => {
reject(new TypeError("Network request failed"));
};
xhr.open(options.method, url, true);

Object.keys(options.headers).forEach((key) => {
xhr.setRequestHeader(key, options.headers[key]);
});

if (xhr.upload) {
xhr.upload.onprogress = options.onProgress;
}

if (options.onAbortPossible) {
options.onAbortPossible(() => {
xhr.abort();
});
}

xhr.send(options.body);
});

export const customFetch = (uri, options) => {
if (typeof window !== "undefined" && options.useUpload) {
return uploadFetch(uri, options);
}
return fetch(uri, options);
};
105 changes: 53 additions & 52 deletions lib/client/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,58 +68,59 @@ function serializeFormData(obj, formDataObj = {}, namespace) {
return formDataObj;
}

export const fileUploadMiddleware = {
applyMiddleware({ request, options }, next) {
if (isUpload(request)) {
const body = new FormData();
const data = [];
const printed = printRequest(request);
data.push({
operationName: printed.operationName || undefined,
debugName: printed.debugName || undefined,
query: printed.query,
variables: request.variables || {}
});
const serialised = serializeFormData({ data });
Object.entries(serialised).forEach(([name, value]) => {
if (typeof value !== typeof undefined) {
body.set(name, value);
}
});
options.headers = options.headers || new Headers();
options.headers.Accept = '*/*';
options.headers['Content-Type'] = undefined;
options.body = body;
}
next();
},
applyBatchMiddleware({ requests, options }, next) {
if (isUpload(requests)) {
const body = new FormData();
const data = [];
requests.forEach((request, i) => {
const printed = printRequest(request);
data.push({
operationName: printed.operationName || undefined,
debugName: printed.debugName || undefined,
query: printed.query,
variables: request.variables || {}
});
});
const serialised = serializeFormData({ data });
Object.entries(serialised).forEach(([name, value]) => {
if (typeof value !== typeof undefined) {
body.set(name, value);
}
});
options.headers = options.headers || new Headers();
options.headers.Accept = '*/*';
options.headers['Content-Type'] = undefined;
options.body = body;
}
next();
}
};
// DEPRECATED with apollo 2
//export const fileUploadMiddleware = {
// applyMiddleware({ request, options }, next) {
// if (isUpload(request)) {
// const body = new FormData();
// const data = [];
// const printed = printRequest(request);
// data.push({
// operationName: printed.operationName || undefined,
// debugName: printed.debugName || undefined,
// query: printed.query,
// variables: request.variables || {}
// });
// const serialised = serializeFormData({ data });
// Object.entries(serialised).forEach(([name, value]) => {
// if (typeof value !== typeof undefined) {
// body.set(name, value);
// }
// });
// options.headers = options.headers || new Headers();
// options.headers.Accept = '*/*';
// options.headers['Content-Type'] = undefined;
// options.body = body;
// }
// next();
// },
// applyBatchMiddleware({ requests, options }, next) {
// if (isUpload(requests)) {
// const body = new FormData();
// const data = [];
// requests.forEach((request, i) => {
// const printed = printRequest(request);
// data.push({
// operationName: printed.operationName || undefined,
// debugName: printed.debugName || undefined,
// query: printed.query,
// variables: request.variables || {}
// });
// });
// const serialised = serializeFormData({ data });
// Object.entries(serialised).forEach(([name, value]) => {
// if (typeof value !== typeof undefined) {
// body.set(name, value);
// }
// });
// options.headers = options.headers || new Headers();
// options.headers.Accept = '*/*';
// options.headers['Content-Type'] = undefined;
// options.body = body;
// }
// next();
// }
//};

export function createUploadNetworkInterface(opts) {
const batchedInterface = createBatchingNetworkInterface(opts);
Expand Down
Loading