From 99b62543031fe8714c5a415bb7abd8c6827a5375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 8 Jul 2019 16:27:56 +0200 Subject: [PATCH 1/5] Split neode models into own directory --- backend/src/bootstrap/neode.js | 82 +--------------------------- backend/src/models/EmailAddress.js | 12 ++++ backend/src/models/InvitationCode.js | 16 ++++++ backend/src/models/User.js | 54 ++++++++++++++++++ 4 files changed, 83 insertions(+), 81 deletions(-) create mode 100644 backend/src/models/EmailAddress.js create mode 100644 backend/src/models/InvitationCode.js create mode 100644 backend/src/models/User.js diff --git a/backend/src/bootstrap/neode.js b/backend/src/bootstrap/neode.js index 419cb1032..12ac3c299 100644 --- a/backend/src/bootstrap/neode.js +++ b/backend/src/bootstrap/neode.js @@ -1,88 +1,8 @@ import Neode from 'neode' -import uuid from 'uuid/v4' export default function setupNeode(options) { const { uri, username, password } = options const neodeInstance = new Neode(uri, username, password) - neodeInstance.model('InvitationCode', { - createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - token: { type: 'string', primary: true, token: true }, - generatedBy: { - type: 'relationship', - relationship: 'GENERATED', - target: 'User', - direction: 'in', - }, - activated: { - type: 'relationship', - relationship: 'ACTIVATED', - target: 'EmailAddress', - direction: 'out', - }, - }) - neodeInstance.model('EmailAddress', { - email: { type: 'string', primary: true, lowercase: true, email: true }, - createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - verifiedAt: { type: 'string', isoDate: true }, - nonce: { type: 'string', token: true }, - belongsTo: { - type: 'relationship', - relationship: 'BELONGS_TO', - target: 'User', - direction: 'out', - }, - }) - neodeInstance.model('User', { - id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests - actorId: { type: 'string', allow: [null] }, - name: { type: 'string', min: 3 }, - email: { type: 'string', lowercase: true, email: true }, - slug: 'string', - encryptedPassword: 'string', - avatar: { type: 'string', allow: [null] }, - coverImg: { type: 'string', allow: [null] }, - deleted: { type: 'boolean', default: false }, - disabled: { type: 'boolean', default: false }, - role: 'string', - publicKey: 'string', - privateKey: 'string', - wasInvited: 'boolean', - wasSeeded: 'boolean', - locationName: { type: 'string', allow: [null] }, - about: { type: 'string', allow: [null] }, - primaryEmail: { - type: 'relationship', - relationship: 'PRIMARY_EMAIL', - target: 'EmailAddress', - direction: 'out', - }, - following: { - type: 'relationship', - relationship: 'FOLLOWS', - target: 'User', - direction: 'out', - }, - followedBy: { - type: 'relationship', - relationship: 'FOLLOWS', - target: 'User', - direction: 'in', - }, - friends: { type: 'relationship', relationship: 'FRIENDS', target: 'User', direction: 'both' }, - disabledBy: { - type: 'relationship', - relationship: 'DISABLED', - target: 'User', - direction: 'in', - }, - invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' }, - createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - updatedAt: { - type: 'string', - isoDate: true, - required: true, - default: () => new Date().toISOString(), - }, - }) + neodeInstance.withDirectory(`${__dirname}/../models`) return neodeInstance } diff --git a/backend/src/models/EmailAddress.js b/backend/src/models/EmailAddress.js new file mode 100644 index 000000000..ddd56c297 --- /dev/null +++ b/backend/src/models/EmailAddress.js @@ -0,0 +1,12 @@ +module.exports = { + email: { type: 'string', primary: true, lowercase: true, email: true }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + verifiedAt: { type: 'string', isoDate: true }, + nonce: { type: 'string', token: true }, + belongsTo: { + type: 'relationship', + relationship: 'BELONGS_TO', + target: 'User', + direction: 'out', + }, +} diff --git a/backend/src/models/InvitationCode.js b/backend/src/models/InvitationCode.js new file mode 100644 index 000000000..f137f6c15 --- /dev/null +++ b/backend/src/models/InvitationCode.js @@ -0,0 +1,16 @@ +module.exports = { + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + token: { type: 'string', primary: true, token: true }, + generatedBy: { + type: 'relationship', + relationship: 'GENERATED', + target: 'User', + direction: 'in', + }, + activated: { + type: 'relationship', + relationship: 'ACTIVATED', + target: 'EmailAddress', + direction: 'out', + }, +} diff --git a/backend/src/models/User.js b/backend/src/models/User.js new file mode 100644 index 000000000..9ad07cf11 --- /dev/null +++ b/backend/src/models/User.js @@ -0,0 +1,54 @@ +import uuid from 'uuid/v4' + +module.exports = { + id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests + actorId: { type: 'string', allow: [null] }, + name: { type: 'string', min: 3 }, + email: { type: 'string', lowercase: true, email: true }, + slug: 'string', + encryptedPassword: 'string', + avatar: { type: 'string', allow: [null] }, + coverImg: { type: 'string', allow: [null] }, + deleted: { type: 'boolean', default: false }, + disabled: { type: 'boolean', default: false }, + role: 'string', + publicKey: 'string', + privateKey: 'string', + wasInvited: 'boolean', + wasSeeded: 'boolean', + locationName: { type: 'string', allow: [null] }, + about: { type: 'string', allow: [null] }, + primaryEmail: { + type: 'relationship', + relationship: 'PRIMARY_EMAIL', + target: 'EmailAddress', + direction: 'out', + }, + following: { + type: 'relationship', + relationship: 'FOLLOWS', + target: 'User', + direction: 'out', + }, + followedBy: { + type: 'relationship', + relationship: 'FOLLOWS', + target: 'User', + direction: 'in', + }, + friends: { type: 'relationship', relationship: 'FRIENDS', target: 'User', direction: 'both' }, + disabledBy: { + type: 'relationship', + relationship: 'DISABLED', + target: 'User', + direction: 'in', + }, + invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + updatedAt: { + type: 'string', + isoDate: true, + required: true, + default: () => new Date().toISOString(), + }, +} From e77bf314706f2bcab0cfdabc2c14ac9b601b1592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 8 Jul 2019 16:44:07 +0200 Subject: [PATCH 2/5] User role must be set --- backend/src/models/User.js | 2 +- backend/src/models/spec/User.spec.js | 20 ++++++++++++++++++++ backend/src/schema/types/type/User.gql | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 backend/src/models/spec/User.spec.js diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 9ad07cf11..060359682 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -11,7 +11,7 @@ module.exports = { coverImg: { type: 'string', allow: [null] }, deleted: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false }, - role: 'string', + role: { type: 'string', required: true, default: 'user' }, publicKey: 'string', privateKey: 'string', wasInvited: 'boolean', diff --git a/backend/src/models/spec/User.spec.js b/backend/src/models/spec/User.spec.js new file mode 100644 index 000000000..d1aa79b7d --- /dev/null +++ b/backend/src/models/spec/User.spec.js @@ -0,0 +1,20 @@ +import Factory from '../../seed/factories' +import { neode } from '../../bootstrap/neo4j' + +const factory = Factory() +const instance = neode() + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('role', () => { + it('defaults to `user`', async () => { + const user = await instance.create('User', { name: 'John' }) + await expect(user.toJson()).resolves.toEqual( + expect.objectContaining({ + role: 'user', + }), + ) + }) +}) diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 314f03521..b984f2d79 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -9,7 +9,7 @@ type User { deleted: Boolean disabled: Boolean disabledBy: User @relation(name: "DISABLED", direction: "IN") - role: UserGroup + role: UserGroup! publicKey: String invitedBy: User @relation(name: "INVITED", direction: "IN") invited: [User] @relation(name: "INVITED", direction: "OUT") From e4173a24aa2d65b947d1f470c79998a17bdd3a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 8 Jul 2019 19:50:19 +0200 Subject: [PATCH 3/5] Fix backend tests --- backend/src/models/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 060359682..d8f768ae9 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -11,7 +11,7 @@ module.exports = { coverImg: { type: 'string', allow: [null] }, deleted: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false }, - role: { type: 'string', required: true, default: 'user' }, + role: { type: 'string', default: 'user' }, publicKey: 'string', privateKey: 'string', wasInvited: 'boolean', From a3de375fef2f153ff33db9390ffe2e3846f7ad1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 8 Jul 2019 20:27:23 +0200 Subject: [PATCH 4/5] neodes `withDir` seems incompatible with cypress --- backend/src/bootstrap/neode.js | 3 ++- backend/src/models/{spec => }/User.spec.js | 4 ++-- backend/src/models/index.js | 13 +++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) rename backend/src/models/{spec => }/User.spec.js (80%) create mode 100644 backend/src/models/index.js diff --git a/backend/src/bootstrap/neode.js b/backend/src/bootstrap/neode.js index 12ac3c299..65a2074be 100644 --- a/backend/src/bootstrap/neode.js +++ b/backend/src/bootstrap/neode.js @@ -1,8 +1,9 @@ import Neode from 'neode' +import models from '../models' export default function setupNeode(options) { const { uri, username, password } = options const neodeInstance = new Neode(uri, username, password) - neodeInstance.withDirectory(`${__dirname}/../models`) + neodeInstance.with(models) return neodeInstance } diff --git a/backend/src/models/spec/User.spec.js b/backend/src/models/User.spec.js similarity index 80% rename from backend/src/models/spec/User.spec.js rename to backend/src/models/User.spec.js index d1aa79b7d..e00136970 100644 --- a/backend/src/models/spec/User.spec.js +++ b/backend/src/models/User.spec.js @@ -1,5 +1,5 @@ -import Factory from '../../seed/factories' -import { neode } from '../../bootstrap/neo4j' +import Factory from '../seed/factories' +import { neode } from '../bootstrap/neo4j' const factory = Factory() const instance = neode() diff --git a/backend/src/models/index.js b/backend/src/models/index.js new file mode 100644 index 000000000..dbf654c68 --- /dev/null +++ b/backend/src/models/index.js @@ -0,0 +1,13 @@ +import fs from 'fs' +import path from 'path' + +const models = {} +fs.readdirSync(__dirname).forEach(file => { + file = path.join(__dirname, file).toString('utf-8') + const name = path.basename(file, '.js') + if (!/\.spec/.test(name) && path.extname(file) === '.js') { + // Is a gql file + models[name] = require(file) + } +}) +export default models From f3ac218c735dd6b9ba662995fa156368464de1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 8 Jul 2019 23:42:13 +0200 Subject: [PATCH 5/5] Silly cypress cannot deal with `fs` npm module ... a server-side npm package and does not work for the browser. --- backend/src/models/index.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/backend/src/models/index.js b/backend/src/models/index.js index dbf654c68..0e6ae5864 100644 --- a/backend/src/models/index.js +++ b/backend/src/models/index.js @@ -1,13 +1,7 @@ -import fs from 'fs' -import path from 'path' - -const models = {} -fs.readdirSync(__dirname).forEach(file => { - file = path.join(__dirname, file).toString('utf-8') - const name = path.basename(file, '.js') - if (!/\.spec/.test(name) && path.extname(file) === '.js') { - // Is a gql file - models[name] = require(file) - } -}) -export default models +// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm +// module that is not browser-compatible. Node's `fs` module is server-side only +export default { + User: require('./User.js'), + InvitationCode: require('./InvitationCode.js'), + EmailAddress: require('./EmailAddress.js'), +}