Merge branch 'master' into 37-full-text-search-top-bar

This commit is contained in:
Grzegorz Leoniec 2019-03-04 15:56:32 +01:00 committed by GitHub
commit adcedb264f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 422 additions and 74 deletions

3
.gitignore vendored
View File

@ -8,3 +8,6 @@ coverage.lcov
.nyc_output/
public/uploads/*
!.gitkeep
# Apple macOS folder attribute file
.DS_Store

View File

@ -66,7 +66,7 @@
},
"devDependencies": {
"@babel/cli": "~7.2.3",
"@babel/core": "~7.3.3",
"@babel/core": "~7.3.4",
"@babel/node": "~7.2.2",
"@babel/preset-env": "~7.3.4",
"@babel/register": "~7.0.0",
@ -75,7 +75,7 @@
"babel-eslint": "~10.0.1",
"babel-jest": "~24.1.0",
"chai": "~4.2.0",
"eslint": "~5.14.1",
"eslint": "~5.15.0",
"eslint-config-standard": "~12.0.0",
"eslint-plugin-import": "~2.16.0",
"eslint-plugin-jest": "~22.3.0",

View File

@ -2,29 +2,21 @@ export default {
Mutation: {
CreateUser: async (resolve, root, args, context, info) => {
args.createdAt = (new Date()).toISOString()
args.disabled = false
args.deleted = false
const result = await resolve(root, args, context, info)
return result
},
CreatePost: async (resolve, root, args, context, info) => {
args.createdAt = (new Date()).toISOString()
args.disabled = false
args.deleted = false
const result = await resolve(root, args, context, info)
return result
},
CreateComment: async (resolve, root, args, context, info) => {
args.createdAt = (new Date()).toISOString()
args.disabled = false
args.deleted = false
const result = await resolve(root, args, context, info)
return result
},
CreateOrganization: async (resolve, root, args, context, info) => {
args.createdAt = (new Date()).toISOString()
args.disabled = false
args.deleted = false
const result = await resolve(root, args, context, info)
return result
},

View File

@ -1,4 +1,4 @@
import { rule, shield, allow } from 'graphql-shield'
import { rule, shield, allow, or } from 'graphql-shield'
/*
* TODO: implement
@ -7,32 +7,39 @@ import { rule, shield, allow } from 'graphql-shield'
const isAuthenticated = rule()(async (parent, args, ctx, info) => {
return ctx.user !== null
})
/*
const isAdmin = rule()(async (parent, args, ctx, info) => {
return ctx.user.role === 'ADMIN'
})
const isModerator = rule()(async (parent, args, ctx, info) => {
return ctx.user.role === 'MODERATOR'
})
*/
const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, ctx, info) => {
return ctx.user.id === parent.id
const isModerator = rule()(async (parent, args, { user }, info) => {
return user && (user.role === 'moderator' || user.role === 'admin')
})
const isAdmin = rule()(async (parent, args, { user }, info) => {
return user && (user.role === 'admin')
})
const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, context, info) => {
return context.user.id === parent.id
})
const onlyEnabledContent = rule({ cache: 'strict' })(async (parent, args, ctx, info) => {
const { disabled, deleted } = args
return !(disabled || deleted)
})
// Permissions
const permissions = shield({
Query: {
statistics: allow,
currentUser: allow
// fruits: and(isAuthenticated, or(isAdmin, isModerator)),
// customers: and(isAuthenticated, isAdmin)
currentUser: allow,
Post: or(onlyEnabledContent, isModerator)
},
Mutation: {
CreatePost: isAuthenticated,
// TODO UpdatePost: isOwner,
// TODO DeletePost: isOwner,
report: isAuthenticated
report: isAuthenticated,
CreateBadge: isAdmin,
UpdateBadge: isAdmin,
DeleteBadge: isAdmin
// addFruitToBasket: isAuthenticated
// CreateUser: allow,
},
@ -40,7 +47,6 @@ const permissions = shield({
email: isMyOwn,
password: isMyOwn
}
// Post: isAuthenticated
})
export default permissions

View File

@ -1,38 +1,23 @@
const setDefaults = (args) => {
if (typeof args.deleted !== 'boolean') {
args.deleted = false
}
if (typeof args.disabled !== 'boolean') {
args.disabled = false
}
return args
}
export default {
Query: {
Post: async (resolve, root, args, context, info) => {
if (typeof args.deleted !== 'boolean') {
args.deleted = false
}
if (typeof args.disabled !== 'boolean') {
args.disabled = false
}
const result = await resolve(root, args, context, info)
return result
Post: (resolve, root, args, context, info) => {
return resolve(root, setDefaults(args), context, info)
},
Comment: async (resolve, root, args, context, info) => {
if (typeof args.deleted !== 'boolean') {
args.deleted = false
}
if (typeof args.disabled !== 'boolean') {
args.disabled = false
}
const result = await resolve(root, args, context, info)
return result
return resolve(root, setDefaults(args), context, info)
},
User: async (resolve, root, args, context, info) => {
if (typeof args.deleted !== 'boolean') {
args.deleted = false
}
if (typeof args.disabled !== 'boolean') {
args.disabled = false
}
// console.log('ROOT', root)
// console.log('ARGS', args)
// console.log('CONTEXT', context)
// console.log('info', info.fieldNodes[0].arguments)
const result = await resolve(root, args, context, info)
return result
return resolve(root, setDefaults(args), context, info)
}
}
}

View File

@ -0,0 +1,119 @@
import Factory from '../seed/factories'
import { host, login } from '../jest/helpers'
import { GraphQLClient } from 'graphql-request'
const factory = Factory()
let client
let query
let action
beforeEach(async () => {
await Promise.all([
factory.create('User', { role: 'user', email: 'user@example.org', password: '1234' }),
factory.create('User', { role: 'moderator', email: 'moderator@example.org', password: '1234' })
])
await factory.authenticateAs({ email: 'user@example.org', password: '1234' })
await Promise.all([
factory.create('Post', { title: 'Deleted post', deleted: true, disabled: false }),
factory.create('Post', { title: 'Disabled post', deleted: false, disabled: true }),
factory.create('Post', { title: 'Publicly visible post', deleted: false, disabled: false })
])
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('softDeleteMiddleware', () => {
describe('Post', () => {
action = () => {
return client.request(query)
}
beforeEach(() => {
query = '{ Post { title } }'
})
describe('as user', () => {
beforeEach(async () => {
const headers = await login({ email: 'user@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('hides deleted or disabled posts', async () => {
const expected = { Post: [{ title: 'Publicly visible post' }] }
await expect(action()).resolves.toEqual(expected)
})
})
describe('as moderator', () => {
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('hides deleted or disabled posts', async () => {
const expected = { Post: [{ title: 'Publicly visible post' }] }
await expect(action()).resolves.toEqual(expected)
})
})
describe('filter (deleted: true)', () => {
beforeEach(() => {
query = '{ Post(deleted: true) { title } }'
})
describe('as user', () => {
beforeEach(async () => {
const headers = await login({ email: 'user@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorisation error', async () => {
await expect(action()).rejects.toThrow('Not Authorised!')
})
})
describe('as moderator', () => {
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('shows deleted posts', async () => {
const expected = { Post: [{ title: 'Deleted post' }] }
await expect(action()).resolves.toEqual(expected)
})
})
})
describe('filter (disabled: true)', () => {
beforeEach(() => {
query = '{ Post(disabled: true) { title } }'
})
describe('as user', () => {
beforeEach(async () => {
const headers = await login({ email: 'user@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorisation error', async () => {
await expect(action()).rejects.toThrow('Not Authorised!')
})
})
describe('as moderator', () => {
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('shows disabled posts', async () => {
const expected = { Post: [{ title: 'Disabled post' }] }
await expect(action()).resolves.toEqual(expected)
})
})
})
})
})

View File

@ -0,0 +1,223 @@
import Factory from '../seed/factories'
import { GraphQLClient } from 'graphql-request'
import { host, login } from '../jest/helpers'
const factory = Factory()
describe('badges', () => {
beforeEach(async () => {
await factory.create('User', {
email: 'user@example.org',
role: 'user',
password: '1234'
})
await factory.create('User', {
id: 'u2',
role: 'moderator',
email: 'moderator@example.org'
})
await factory.create('User', {
id: 'u3',
role: 'admin',
email: 'admin@example.org'
})
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('CreateBadge', () => {
const variables = {
id: 'b1',
key: 'indiegogo_en_racoon',
type: 'crowdfunding',
status: 'permanent',
icon: '/img/badges/indiegogo_en_racoon.svg'
}
const mutation = `
mutation(
$id: ID
$key: String!
$type: BadgeTypeEnum!
$status: BadgeStatusEnum!
$icon: String!
) {
CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) {
id,
key,
type,
status,
icon
}
}
`
describe('unauthenticated', () => {
let client
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(
client.request(mutation, variables)
).rejects.toThrow('Not Authorised')
})
})
describe('authenticated admin', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('creates a badge', async () => {
const expected = {
CreateBadge: {
icon: '/img/badges/indiegogo_en_racoon.svg',
id: 'b1',
key: 'indiegogo_en_racoon',
status: 'permanent',
type: 'crowdfunding'
}
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
describe('authenticated moderator', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorization error', async () => {
await expect(
client.request(mutation, variables)
).rejects.toThrow('Not Authorised')
})
})
})
describe('UpdateBadge', () => {
beforeEach(async () => {
await factory.authenticateAs({ email: 'admin@example.org', password: '1234' })
await factory.create('Badge', { id: 'b1' })
})
const variables = {
id: 'b1',
key: 'whatever'
}
const mutation = `
mutation($id: ID!, $key: String!) {
UpdateBadge(id: $id, key: $key) {
id
key
}
}
`
describe('unauthenticated', () => {
let client
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(
client.request(mutation, variables)
).rejects.toThrow('Not Authorised')
})
})
describe('authenticated moderator', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorization error', async () => {
await expect(
client.request(mutation, variables)
).rejects.toThrow('Not Authorised')
})
})
describe('authenticated admin', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('updates a badge', async () => {
const expected = {
UpdateBadge: {
id: 'b1',
key: 'whatever'
}
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
})
describe('DeleteBadge', () => {
beforeEach(async () => {
await factory.authenticateAs({ email: 'admin@example.org', password: '1234' })
await factory.create('Badge', { id: 'b1' })
})
const variables = {
id: 'b1'
}
const mutation = `
mutation($id: ID!) {
DeleteBadge(id: $id) {
id
}
}
`
describe('unauthenticated', () => {
let client
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(
client.request(mutation, variables)
).rejects.toThrow('Not Authorised')
})
})
describe('authenticated moderator', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('throws authorization error', async () => {
await expect(
client.request(mutation, variables)
).rejects.toThrow('Not Authorised')
})
})
describe('authenticated admin', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
})
it('deletes a badge', async () => {
const expected = {
DeleteBadge: {
id: 'b1'
}
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
})
})

View File

@ -10,14 +10,14 @@ export default function (params) {
} = params
return `
mutation {
CreateBadge(
id: "${id}",
key: "${key}",
type: ${type},
status: ${status},
icon: "${icon}"
) { id }
}
mutation {
CreateBadge(
id: "${id}",
key: "${key}",
type: ${type},
status: ${status},
icon: "${icon}"
) { id }
}
`
}

View File

@ -38,7 +38,7 @@
dependencies:
"@babel/highlight" "^7.0.0"
"@babel/core@^7.1.0", "@babel/core@~7.3.3":
"@babel/core@^7.1.0":
version "7.3.3"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.3.tgz#d090d157b7c5060d05a05acaebc048bd2b037947"
integrity sha512-w445QGI2qd0E0GlSnq6huRZWPMmQGCp5gd5ZWS4hagn0EiwzxD5QMFkpchyusAyVC1n27OKXzQ0/88aVU9n4xQ==
@ -58,6 +58,26 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/core@~7.3.4":
version "7.3.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b"
integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/generator" "^7.3.4"
"@babel/helpers" "^7.2.0"
"@babel/parser" "^7.3.4"
"@babel/template" "^7.2.2"
"@babel/traverse" "^7.3.4"
"@babel/types" "^7.3.4"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
lodash "^4.17.11"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
"@babel/generator@^7.0.0", "@babel/generator@^7.2.2", "@babel/generator@^7.3.3":
version "7.3.3"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.3.tgz#185962ade59a52e00ca2bdfcfd1d58e528d4e39e"
@ -2544,10 +2564,10 @@ eslint-scope@3.7.1:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-scope@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172"
integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==
eslint-scope@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.2.tgz#5f10cd6cabb1965bf479fa65745673439e21cb0e"
integrity sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg==
dependencies:
esrecurse "^4.1.0"
estraverse "^4.1.1"
@ -2562,10 +2582,10 @@ eslint-visitor-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
eslint@~5.14.1:
version "5.14.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.14.1.tgz#490a28906be313685c55ccd43a39e8d22efc04ba"
integrity sha512-CyUMbmsjxedx8B0mr79mNOqetvkbij/zrXnFeK2zc3pGRn3/tibjiNAv/3UxFEyfMDjh+ZqTrJrEGBFiGfD5Og==
eslint@~5.15.0:
version "5.15.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.15.0.tgz#f313a2f7c7628d39adeefdba4a9c41f842012c9e"
integrity sha512-xwG7SS5JLeqkiR3iOmVgtF8Y6xPdtr6AAsN6ph7Q6R/fv+3UlKYoika8SmNzmb35qdRF+RfTY35kMEdtbi+9wg==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.9.1"
@ -2573,7 +2593,7 @@ eslint@~5.14.1:
cross-spawn "^6.0.5"
debug "^4.0.1"
doctrine "^3.0.0"
eslint-scope "^4.0.0"
eslint-scope "^4.0.2"
eslint-utils "^1.3.1"
eslint-visitor-keys "^1.0.0"
espree "^5.0.1"