From f08e2a37677e711cad7397c532670913645f010a Mon Sep 17 00:00:00 2001 From: eug-vs Date: Wed, 12 Aug 2020 18:35:10 +0300 Subject: feat: File service logic --- services/files/files.class.ts | 59 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) (limited to 'services') diff --git a/services/files/files.class.ts b/services/files/files.class.ts index e2f9df3..7e4bcbf 100644 --- a/services/files/files.class.ts +++ b/services/files/files.class.ts @@ -1,6 +1,10 @@ import { Application } from '@feathersjs/express'; import { Params } from '@feathersjs/feathers'; import { v4 } from 'uuid'; +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; +import { User } from 'which-types'; // Use require to avoid bug // https://stackoverflow.com/questions/62611373/heroku-crashes-when-importing-aws-sdk @@ -13,15 +17,68 @@ export default class Files { bucket!: string; async find(params: Params): Promise { + const path = this.generateS3Path(params.user?.username); + return this.getUploadUrl(path); + } + + public isS3url(url: string): boolean { + return url.startsWith('https://${this.bucket}.s3'); + } + + public generateS3Path(prefix='', ext='png'): string { + const key = v4(); + const fileName = `${key}.${ext}`; + return prefix ? `${prefix}/${fileName}` : fileName; + } + + async getUploadUrl(path: string): Promise { // Return signed upload URL return this.s3.getSignedUrl('putObject', { Bucket: this.bucket, - Key: `${params.user?.username}/${v4()}.png`, + Key: path, ContentType: 'image/*', Expires: 300, }); } + async getDownloadUrl(path: string): Promise { + return this.getUploadUrl(path).then((url: string) => { + const queryIndex = url.indexOf('?'); + return url.slice(0, queryIndex); + }) + } + + private createTmpDir() { + if (!fs.existsSync('tmp')) fs.mkdirSync('tmp'); + } + + async downloadFile(url: string): Promise { + return new Promise(async (resolve, reject) => { + this.createTmpDir(); + const filePath = `tmp/${v4()}`; + const fileStream = fs.createWriteStream(filePath); + const response = await axios.get(url, { responseType: 'stream' }) + response.data.pipe(fileStream) + .on('error', reject) + .on('close', () => resolve(filePath)); + }); + } + + async uploadFileToS3(filePath: string, s3Path: string) { + const fileStream = fs.createReadStream(filePath); + const request = this.s3.upload({ + Bucket: this.bucket, + Key: s3Path, + Body: fileStream, + ContentType: 'image/png' + }); + request.on('httpUploadProgress', progress => { + console.log('progress', progress); + }) + await request.promise(); + return this.getDownloadUrl(s3Path); + } + setup(app: Application): void { this.app = app; this.s3 = new S3({ -- cgit v1.2.3 From 4b2397d68b62d9f5682d1fd9f4fb2082ac4aa260 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Wed, 12 Aug 2020 18:42:29 +0300 Subject: feat: impelemnt fetchImages hook --- services/polls/polls.hooks.ts | 4 +++- services/users/users.hooks.ts | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'services') diff --git a/services/polls/polls.hooks.ts b/services/polls/polls.hooks.ts index 35eae29..7a5b1da 100644 --- a/services/polls/polls.hooks.ts +++ b/services/polls/polls.hooks.ts @@ -8,6 +8,7 @@ import { PollSchema } from '../../models/polls/poll.schema'; import VoteModel from '../../models/votes/vote.model'; import sortByDate from '../../hooks/sortByDate'; import signAuthority from '../../hooks/signAuthority'; +import fetchImages from '../../hooks/fetchImages'; const convertPoll = async (context: HookContext): Promise => { @@ -53,7 +54,8 @@ export default { patch: disallow('external') }, after: { - all: convertPoll + all: convertPoll, + create: fetchImages(['contents.left.url', 'contents.right.url']) } }; diff --git a/services/users/users.hooks.ts b/services/users/users.hooks.ts index 29f1074..ddfc47f 100644 --- a/services/users/users.hooks.ts +++ b/services/users/users.hooks.ts @@ -4,6 +4,7 @@ import { discard, disallow } from 'feathers-hooks-common'; import { HookContext } from '@feathersjs/feathers'; import { NotAuthenticated } from '@feathersjs/errors'; import requireAuth from '../../hooks/requireAuth'; +import fetchImages from '../../hooks/fetchImages'; const hashPassword = hooks.hashPassword('password'); @@ -24,6 +25,8 @@ const compareUser = async (context: HookContext): Promise => { export default { after: { all: hooks.protect('password'), + create: fetchImages(['avatarUrl']), + patch: fetchImages(['avatarUrl']), get: discard('password') // Protect password from local get's }, before: { -- cgit v1.2.3 From 8f4ae4fd89cab5ba4f9e8d2750bc8589ce997ff1 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Wed, 12 Aug 2020 19:02:38 +0300 Subject: fix: delete files after upload --- services/files/files.class.ts | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'services') diff --git a/services/files/files.class.ts b/services/files/files.class.ts index 7e4bcbf..f2a0960 100644 --- a/services/files/files.class.ts +++ b/services/files/files.class.ts @@ -3,18 +3,20 @@ import { Params } from '@feathersjs/feathers'; import { v4 } from 'uuid'; import axios from 'axios'; import fs from 'fs'; -import path from 'path'; -import { User } from 'which-types'; // Use require to avoid bug // https://stackoverflow.com/questions/62611373/heroku-crashes-when-importing-aws-sdk +// TODO: use import statement +// eslint-disable-next-line const S3 = require('aws-sdk/clients/s3'); export default class Files { - app!: Application; - s3!: any; - bucket!: string; + public app!: Application; + + private s3!: typeof S3; + + private bucket!: string; async find(params: Params): Promise { const path = this.generateS3Path(params.user?.username); @@ -22,10 +24,10 @@ export default class Files { } public isS3url(url: string): boolean { - return url.startsWith('https://${this.bucket}.s3'); + return url.startsWith(`https://${this.bucket}.s3`); } - public generateS3Path(prefix='', ext='png'): string { + public generateS3Path(prefix = '', ext = 'png'): string { const key = v4(); const fileName = `${key}.${ext}`; return prefix ? `${prefix}/${fileName}` : fileName; @@ -37,7 +39,7 @@ export default class Files { Bucket: this.bucket, Key: path, ContentType: 'image/*', - Expires: 300, + Expires: 300 }); } @@ -45,7 +47,7 @@ export default class Files { return this.getUploadUrl(path).then((url: string) => { const queryIndex = url.indexOf('?'); return url.slice(0, queryIndex); - }) + }); } private createTmpDir() { @@ -53,29 +55,29 @@ export default class Files { } async downloadFile(url: string): Promise { - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { this.createTmpDir(); const filePath = `tmp/${v4()}`; const fileStream = fs.createWriteStream(filePath); - const response = await axios.get(url, { responseType: 'stream' }) - response.data.pipe(fileStream) - .on('error', reject) - .on('close', () => resolve(filePath)); + axios.get(url, { responseType: 'stream' }) + .then(response => { + response.data.pipe(fileStream) + .on('error', reject) + .on('close', () => resolve(filePath)); + }) + .catch(error => reject(error)); }); } - async uploadFileToS3(filePath: string, s3Path: string) { + async uploadFileToS3(filePath: string, s3Path: string): Promise { const fileStream = fs.createReadStream(filePath); - const request = this.s3.upload({ + await this.s3.upload({ Bucket: this.bucket, Key: s3Path, Body: fileStream, ContentType: 'image/png' - }); - request.on('httpUploadProgress', progress => { - console.log('progress', progress); - }) - await request.promise(); + }).promise(); + fs.unlinkSync(filePath); return this.getDownloadUrl(s3Path); } -- cgit v1.2.3