-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# @pozible/signed-upload | ||
Use [Signed URLs](https://cloud.google.com/storage/docs/access-control/signed-urls) to upload a file straight to Cloud Storage. It returns `optimisticUrl` to access the file once it is uploaded. | ||
|
||
## Usage | ||
``` | ||
import signedUpload, { getUrls, upload } from '@pozible/signed-upload' | ||
const {optimisticUrl, signedUrl} = await getUrls(metadata, filePath, config) | ||
await upload(file, metadata, signedUrl) | ||
// Or bootstrap the two commands with | ||
const {optimisticUrl} = await signedUpload(file, metadata, filePath, config) | ||
``` | ||
|
||
## Arguments | ||
### `metadata` | ||
`dimensions` attribute is used together with Cloud Function listening on bucket's changes to further process the image. Omit it when uploading generic files. | ||
``` | ||
{ | ||
dimensions: { | ||
imageUrl: { | ||
width, | ||
height | ||
}, | ||
thumbUrl: { | ||
... | ||
}, | ||
... | ||
}, | ||
filename: 'original-filename.ext', | ||
mimetype: 'valid/type' | ||
} | ||
``` | ||
|
||
### `filePath` | ||
A function to provide a final path to the file in your bucket with uuid as a parameter to give random value to the file name. No need to add extension as it will be automatically added based on given filename in `metadata`. | ||
``` | ||
const filePath = randomId => `/path/to/your/file-with-${randomId}` | ||
const filePath = () => `/path/to/your/original-file-name` | ||
``` | ||
|
||
### `config` | ||
- `baseUrl` (optional): Add baseUrl to be added to `optimisticPath` to form complete `optimisticUrl`. | ||
- `bucketName`: When it is deployed to Google Cloud Platform it will use current project, else specify one here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { IDictionary, Metadata } from '../module'; | ||
declare const _default: (metadata: Metadata, filePath: string) => IDictionary<string>; | ||
export default _default; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { IDictionary, Config } from "../module"; | ||
declare const _default: ({ baseUrl, bucketName }: Config, optimisticPath: IDictionary<string>) => IDictionary<string>; | ||
export default _default; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Metadata } from '../module'; | ||
declare const _default: (metadata: Metadata, filePathWithExt: string, bucketName: string) => Promise<import("@google-cloud/storage").GetSignedUrlResponse>; | ||
export default _default; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { Metadata, Config } from '../module'; | ||
declare const _default: (metadata: Metadata, filePath: any, config: Config) => Promise<{ | ||
optimisticPath: import("../module").IDictionary<string>; | ||
optimisticUrl: import("../module").IDictionary<string>; | ||
signedUrl: string; | ||
}>; | ||
export default _default; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference types="node" /> | ||
import { Metadata, Config } from '../module'; | ||
export declare const getUrls: (metadata: Metadata, filePath: any, config: Config) => Promise<{ | ||
optimisticPath: import("../module").IDictionary<string>; | ||
optimisticUrl: import("../module").IDictionary<string>; | ||
signedUrl: string; | ||
}>; | ||
export declare const upload: (file: any, metadata: Metadata, signedUrl: string) => Promise<import("node-fetch").Response>; | ||
declare const _default: (file: File | NodeJS.ReadableStream, metadata: Metadata, filePath: any, config: Config) => Promise<{ | ||
optimisticPath: import("../module").IDictionary<string>; | ||
optimisticUrl: import("../module").IDictionary<string>; | ||
signedUrl: string; | ||
}>; | ||
export default _default; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Metadata } from '../module'; | ||
declare const _default: (file: any, metadata: Metadata, signedUrl: string) => Promise<import("node-fetch").Response>; | ||
export default _default; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export {default as getUrls} from './dist/getUrls' | ||
export {default as upload} from './dist/upload' | ||
import _signedUpload from './dist/index' | ||
export default _signedUpload |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export interface IDictionary<TValue> { | ||
[id: string]: TValue | ||
} | ||
|
||
export interface Dimension { | ||
width: number | ||
height: number | ||
} | ||
|
||
export interface Metadata { | ||
dimensions?:IDictionary<Dimension> | ||
filename:string | ||
mimetype:string | ||
} | ||
|
||
export interface Config { | ||
bucketName:string | ||
baseUrl?:string | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"name": "@pozible/signed-upload", | ||
"version": "1.0.0", | ||
"description": "Bootstrapped file upload.", | ||
"main": "dist/index.js", | ||
"types": "index.d.ts", | ||
"repository": "[email protected]:pozi-team/signed-upload.git", | ||
"author": "spondbob <[email protected]>", | ||
"keywords": [ | ||
"signed urls", | ||
"cloud storage" | ||
], | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@types/chai": "^4.2.8", | ||
"@types/mocha": "^7.0.1", | ||
"@types/node-fetch": "^2.5.4", | ||
"@types/uuid": "^3.4.7", | ||
"chai": "^4.2.0", | ||
"mocha": "^7.0.1", | ||
"ts-node": "^8.6.2", | ||
"typescript": "^3.7.5" | ||
}, | ||
"dependencies": { | ||
"@google-cloud/storage": "^4.3.0", | ||
"@types/graphql-upload": "^8.0.3", | ||
"node-fetch": "^2.6.0", | ||
"uuid": "^3.4.0" | ||
}, | ||
"scripts": { | ||
"build": "tsc --declaration", | ||
"test": "mocha -r ts-node/register tests/**/*.test.ts" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { IDictionary, Metadata } from '../module' | ||
|
||
const getExtension = fileName => fileName.split('.').pop() | ||
|
||
export default (metadata:Metadata, filePath:string): IDictionary<string> => { | ||
const ext = getExtension(metadata.filename) | ||
const dimensions = metadata.dimensions || {} | ||
|
||
if (!dimensions || Object.keys(dimensions).length === 0) return { url: `${filePath}.${ext}` } | ||
|
||
return Object.keys(dimensions).reduce((acc, cur) => { | ||
const { height, width } = dimensions[cur] | ||
acc[cur] = `${filePath}@${width}x${height}.${ext}` | ||
return acc | ||
}, {}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { IDictionary, Config } from "../module" | ||
|
||
export default ({baseUrl, bucketName}:Config, optimisticPath:IDictionary<string>) => { | ||
const storageUrl = 'https://storage.googleapis.com' | ||
const url = baseUrl || `${storageUrl}/${bucketName}` | ||
return Object.keys(optimisticPath).reduce((acc, cur) => { | ||
acc[cur] = url+'/'+optimisticPath[cur] | ||
return acc | ||
}, {} as IDictionary<string>) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Storage } from '@google-cloud/storage' | ||
import { Metadata } from '../module' | ||
|
||
const storage = new Storage() | ||
|
||
export default (metadata:Metadata, filePathWithExt:string, bucketName:string) => { | ||
const SECONDS = 10000 | ||
const expires = Date.now() + 30 * SECONDS | ||
const extensionHeaders = metadata ? { | ||
extensionHeaders: {'x-goog-meta-data': JSON.stringify(metadata)} | ||
} : {} | ||
|
||
const myBucket = storage.bucket(bucketName) | ||
const bucketFile = myBucket.file(filePathWithExt) | ||
return bucketFile.getSignedUrl({ | ||
action: 'write', | ||
contentType: metadata.mimetype, | ||
expires, | ||
...extensionHeaders, | ||
version: 'v4', | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import uuidv1 from 'uuid/v1' | ||
|
||
import getOptimisticPath from './getOptimisticPath' | ||
import getOptimisticUrl from './getOptimisticUrl' | ||
import getSignedUrl from './getSignedUrl' | ||
import { Metadata, Config } from '../module' | ||
|
||
export default async (metadata:Metadata, filePath, config:Config) => { | ||
const fp = filePath(uuidv1()) | ||
const optimisticPath = getOptimisticPath(metadata, fp) | ||
const optimisticUrl = getOptimisticUrl(config, optimisticPath) | ||
const [signedUrl] = await getSignedUrl(metadata, optimisticPath.imageUrl || optimisticPath.url, config.bucketName) | ||
|
||
return { | ||
optimisticPath, | ||
optimisticUrl, | ||
signedUrl, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Metadata, Config } from '../module' | ||
import _getUrls from './getUrls' | ||
import _upload from './upload' | ||
|
||
export const getUrls = _getUrls | ||
export const upload = _upload | ||
|
||
export default async (file:File|NodeJS.ReadableStream, metadata:Metadata, filePath, config:Config) => { | ||
const urls = await getUrls(metadata, filePath, config) | ||
await upload(file, metadata, urls.signedUrl) | ||
return urls | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import fetch from 'node-fetch' | ||
import { Metadata } from '../module' | ||
|
||
export default (file, metadata:Metadata, signedUrl:string) => { | ||
return fetch(signedUrl, { | ||
body: file, | ||
headers: { | ||
'Content-Type': metadata.mimetype, | ||
'x-goog-meta-data': JSON.stringify(metadata) | ||
}, | ||
method: 'PUT', | ||
}) | ||
} |