Merge branch 'uploads-on-server' of github.com:Human-Connection/Human-Connection into 399-user-profile-image-uploads

This commit is contained in:
Matt Rider 2019-05-21 17:32:02 -03:00
commit fd90006dff
10 changed files with 112 additions and 24 deletions

View File

View File

@ -2,6 +2,7 @@ import cheerio from 'cheerio'
const ID_REGEX = /\/profile\/([\w\-.!~*'"(),]+)/g
export default function (content) {
if (!content) return []
const $ = cheerio.load(content)
const urls = $('.mention').map((_, el) => {
return $(el).attr('href')

View File

@ -1,6 +1,12 @@
import extractIds from './extractMentions'
import extractIds from '.'
describe('extractIds', () => {
describe('content undefined', () => {
it('returns empty array', () => {
expect(extractIds()).toEqual([])
})
})
describe('extract', () => {
describe('searches through links', () => {
it('ignores links without .mention class', () => {
const content = '<p>Something inspirational about <a href="/profile/u2" target="_blank">@bob-der-baumeister</a> and <a href="/profile/u3" target="_blank">@jenny-rostock</a>.</p>'

View File

@ -1,4 +1,4 @@
import extractIds from './extractMentions'
import extractIds from './extractIds'
const notify = async (resolve, root, args, context, resolveInfo) => {
// extract user ids before xss-middleware removes link classes

View File

@ -0,0 +1,27 @@
import { createWriteStream } from 'fs'
import path from 'path'
import slug from 'slug'
const storeUpload = ({ createReadStream, fileLocation }) =>
new Promise((resolve, reject) =>
createReadStream()
.pipe(createWriteStream(`public${fileLocation}`))
.on('finish', resolve)
.on('error', reject)
)
export default async function fileUpload (params, { file, url }, uploadCallback = storeUpload) {
const upload = params[file]
if (upload) {
const { createReadStream, filename } = await upload
const { name } = path.parse(filename)
const fileLocation = `/uploads/${Date.now()}-${slug(name)}`
await uploadCallback({ createReadStream, fileLocation })
delete params[file]
params[url] = fileLocation
}
return params
}

View File

@ -0,0 +1,56 @@
import fileUpload from '.'
describe('fileUpload', () => {
let params
let uploadCallback
beforeEach(() => {
params = {
uploadAttribute: {
filename: 'avatar.jpg',
mimetype: 'image/jpeg',
encoding: '7bit',
createReadStream: jest.fn()
}
}
uploadCallback = jest.fn()
})
it('calls uploadCallback', async () => {
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(uploadCallback).toHaveBeenCalled()
})
describe('file name', () => {
it('saves the upload url in params[url]', async () => {
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(params.attribute).toMatch(/^\/uploads\/\d+-avatar$/)
})
it('uses the name without file ending', async () => {
params.uploadAttribute.filename = 'somePng.png'
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(params.attribute).toMatch(/^\/uploads\/\d+-somePng/)
})
it('creates a url safe name', async () => {
params.uploadAttribute.filename = '/path/to/awkward?/ file-location/?foo- bar-avatar.jpg?foo- bar'
await fileUpload(params, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(params.attribute).toMatch(/^\/uploads\/\d+-foo-bar-avatar$/)
})
describe('in case of duplicates', () => {
it('creates unique names to avoid overwriting existing files', async () => {
const { attribute: first } = await fileUpload({
...params
}, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
await new Promise(resolve => setTimeout(resolve, 1000))
const { attribute: second } = await fileUpload({
...params
}, { file: 'uploadAttribute', url: 'attribute' }, uploadCallback)
expect(first).not.toEqual(second)
})
})
})
})

View File

@ -1,8 +1,15 @@
import { neo4jgraphql } from 'neo4j-graphql-js'
import fileUpload from './fileUpload'
export default {
Mutation: {
UpdatePost: async (object, params, context, resolveInfo) => {
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
return neo4jgraphql(object, params, context, resolveInfo, false)
},
CreatePost: async (object, params, context, resolveInfo) => {
params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
const result = await neo4jgraphql(object, params, context, resolveInfo, false)
const session = context.driver.session()

View File

@ -1,28 +1,14 @@
import { neo4jgraphql } from 'neo4j-graphql-js'
import { createWriteStream } from 'fs'
const storeUpload = ({ stream, fileLocation }) =>
new Promise((resolve, reject) =>
stream
.pipe(createWriteStream(`public${fileLocation}`))
.on('finish', resolve)
.on('error', reject)
)
import fileUpload from './fileUpload'
export default {
Mutation: {
UpdateUser: async (object, params, context, resolveInfo) => {
const { avatarUpload } = params
if (avatarUpload) {
const { createReadStream, filename } = await avatarUpload
const stream = createReadStream()
const fileLocation = `/uploads/${filename}`
await storeUpload({ stream, fileLocation })
delete params.avatarUpload
params.avatar = fileLocation
}
params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
return neo4jgraphql(object, params, context, resolveInfo, false)
},
CreateUser: async (object, params, context, resolveInfo) => {
params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
return neo4jgraphql(object, params, context, resolveInfo, false)
}
}

View File

@ -179,6 +179,7 @@ type Post {
content: String!
contentExcerpt: String
image: String
imageUpload: Upload
visibility: VisibilityEnum
deleted: Boolean
disabled: Boolean

View File

@ -37,7 +37,7 @@ export default {
backgroundImage() {
const { avatar } = this.user || {}
return {
backgroundImage: `url(/api/${avatar})`
backgroundImage: `url(/api${avatar})`
}
}
},
@ -119,6 +119,10 @@ export default {
padding: 40px;
}
#customdropzone:hover {
cursor: pointer;
}
#customdropzone .dz-preview {
width: 160px;
display: flex;