mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' into update_configuration_instructions
This commit is contained in:
commit
e0f0bb95ca
4
.env
4
.env
@ -1,6 +1,10 @@
|
||||
NEO4J_URI=bolt://localhost:7687
|
||||
NEO4J_USER=neo4j
|
||||
NEO4J_PASSWORD=letmein
|
||||
|
||||
GRAPHQL_LISTEN_PORT=4000
|
||||
GRAPHQL_URI=http://localhost:4000
|
||||
CLIENT_URI=http://localhost:3000
|
||||
JWT_SECRET=b/&&7b78BF&fv/Vd
|
||||
|
||||
MOCK=false
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
node_modules/
|
||||
.env
|
||||
.vscode
|
||||
|
||||
@ -92,7 +92,7 @@ npm run seedDb
|
||||
|
||||
## Todo`s
|
||||
|
||||
- [ ] add jwt authentication
|
||||
- [x] add jwt authentication
|
||||
- [ ] get directives working correctly (@toLower, @auth, @role, etc.)
|
||||
- [ ] check if search is working
|
||||
- [ ] check if sorting is working
|
||||
|
||||
16
package.json
16
package.json
@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "./node_modules/.bin/nodemon --exec babel-node src/index.js",
|
||||
"start:debug": "./node_modules/.bin/nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js",
|
||||
"seedDb": "./node_modules/.bin/babel-node src/seed/seed-db.js"
|
||||
},
|
||||
"author": "Grzegorz Leoniec",
|
||||
@ -16,20 +17,27 @@
|
||||
"apollo-client": "^2.3.2",
|
||||
"apollo-link-http": "^1.5.4",
|
||||
"apollo-server": "^2.0.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^6.0.0",
|
||||
"graphql-custom-directives": "^0.2.13",
|
||||
"graphql-middleware": "^1.7.6",
|
||||
"graphql-middleware": "1.7.6",
|
||||
"graphql-tag": "^2.9.2",
|
||||
"graphql-yoga": "1.16.2",
|
||||
"jsonwebtoken": "^8.3.0",
|
||||
"lodash": "^4.17.11",
|
||||
"ms": "^2.1.1",
|
||||
"neo4j-driver": "^1.6.1",
|
||||
"neo4j-graphql-js": "^1.0.2",
|
||||
"neo4j-graphql-js": "1.0.4",
|
||||
"node-fetch": "^2.1.2",
|
||||
"passport": "^0.4.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"slug": "^0.9.1",
|
||||
"trunc-html": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"faker": "^4.1.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"nodemon": "^1.17.5"
|
||||
"faker": "^4.1.0",
|
||||
"nodemon": "^1.18.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,128 @@
|
||||
// import { neo4jgraphql } from "neo4j-graphql-js"
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import zipObject from 'lodash/zipObject'
|
||||
import generateJwt from './jwt/generateToken'
|
||||
import values from 'lodash/values'
|
||||
import { fixUrl } from './middleware/fixImageUrlsMiddleware'
|
||||
|
||||
export const typeDefs =
|
||||
fs.readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, "schema.graphql"))
|
||||
.toString('utf-8')
|
||||
|
||||
const query = (cypher, session) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = []
|
||||
session
|
||||
.run(cypher)
|
||||
.subscribe({
|
||||
onNext: function (record) {
|
||||
let item = {}
|
||||
record.keys.forEach(key => {
|
||||
item[key] = record.get(key)
|
||||
})
|
||||
data.push(item)
|
||||
},
|
||||
onCompleted: function () {
|
||||
session.close()
|
||||
resolve(data)
|
||||
},
|
||||
onError: function (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const queryOne = (cypher, session) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
query(cypher, session)
|
||||
.then(res => {
|
||||
resolve(res.length ? res.pop() : {})
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const resolvers = {
|
||||
// Query: {
|
||||
// usersBySubstring: neo4jgraphql
|
||||
// }
|
||||
}
|
||||
Query: {
|
||||
isLoggedIn: (parent, args, { driver, user }) => {
|
||||
return Boolean(user && user.id)
|
||||
},
|
||||
statistics: async (parent, args, { driver, user }) => {
|
||||
return new Promise(async (resolve) => {
|
||||
const session = driver.session()
|
||||
const queries = {
|
||||
countUsers: `MATCH (r:User) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countUsers`,
|
||||
countPosts: `MATCH (r:Post) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countPosts`,
|
||||
countComments: `MATCH (r:Comment) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countComments`,
|
||||
countNotifications: `MATCH (r:Notification) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countNotifications`,
|
||||
countOrganizations: `MATCH (r:Organization) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countOrganizations`,
|
||||
countProjects: `MATCH (r:Project) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countProjects`,
|
||||
countInvites: `MATCH (r:Invite) WHERE r.wasUsed <> true OR NOT exists(r.wasUsed) RETURN COUNT(r) AS countInvites`,
|
||||
countFollows: `MATCH (:User)-[r:FOLLOWS]->(:User) RETURN COUNT(r) AS countFollows`,
|
||||
countShouts: `MATCH (:User)-[r:SHOUTED]->(:Post) RETURN COUNT(r) AS countShouts`
|
||||
}
|
||||
let data = {
|
||||
countUsers: (await queryOne(queries.countUsers, session)).countUsers,
|
||||
countPosts: (await queryOne(queries.countPosts, session)).countPosts,
|
||||
countComments: (await queryOne(queries.countComments, session)).countComments,
|
||||
countNotifications: (await queryOne(queries.countNotifications, session)).countNotifications,
|
||||
countOrganizations: (await queryOne(queries.countOrganizations, session)).countOrganizations,
|
||||
countProjects: (await queryOne(queries.countProjects, session)).countProjects,
|
||||
countInvites: (await queryOne(queries.countInvites, session)).countInvites,
|
||||
countFollows: (await queryOne(queries.countFollows, session)).countFollows,
|
||||
countShouts: (await queryOne(queries.countShouts, session)).countShouts
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
|
||||
export const mutations = {
|
||||
}
|
||||
// usersBySubstring: neo4jgraphql
|
||||
},
|
||||
Mutation: {
|
||||
signup: async (parent, { email, password }, { req }) => {
|
||||
// if (data[email]) {
|
||||
// throw new Error('Another User with same email exists.')
|
||||
// }
|
||||
// data[email] = {
|
||||
// password: await bcrypt.hashSync(password, 10),
|
||||
// }
|
||||
|
||||
return true
|
||||
},
|
||||
login: async (parent, { email, password }, { driver, req, user }) => {
|
||||
// if (user && user.id) {
|
||||
// throw new Error('Already logged in.')
|
||||
// }
|
||||
|
||||
const session = driver.session()
|
||||
const res = await session.run('MATCH (u:User {email: "' + email + '"}) RETURN u.id, u.slug, u.name, u.avatar, u.email, u.password, u.role LIMIT 1')
|
||||
let u = res.records[0]._fields ? zipObject([
|
||||
'id',
|
||||
'slug',
|
||||
'name',
|
||||
'avatar',
|
||||
'email',
|
||||
'password',
|
||||
'role'
|
||||
], res.records[0]._fields) : null
|
||||
if (u) {
|
||||
if (await bcrypt.compareSync(password, u.password)) {
|
||||
delete u.password
|
||||
u.avatar = fixUrl(u.avatar)
|
||||
return Object.assign(u, {
|
||||
token: generateJwt(u)
|
||||
})
|
||||
}
|
||||
session.close()
|
||||
throw new Error('Incorrect password.')
|
||||
}
|
||||
|
||||
session.close()
|
||||
throw new Error('No Such User exists.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
src/index.js
104
src/index.js
@ -1,21 +1,21 @@
|
||||
// import { GraphQLServer } from 'graphql-yoga'
|
||||
import { applyMiddleware } from 'graphql-middleware'
|
||||
import { ApolloServer, makeExecutableSchema } from 'apollo-server'
|
||||
import { GraphQLServer } from 'graphql-yoga'
|
||||
import { makeExecutableSchema } from 'apollo-server'
|
||||
import { augmentSchema } from 'neo4j-graphql-js'
|
||||
import { typeDefs, resolvers } from './graphql-schema'
|
||||
import { v1 as neo4j } from 'neo4j-driver'
|
||||
import passwordMiddleware from './middleware/passwordMiddleware'
|
||||
import softDeleteMiddleware from './middleware/softDeleteMiddleware'
|
||||
import sluggifyMiddleware from './middleware/sluggifyMiddleware'
|
||||
import fixImageUrlsMiddleware from './middleware/fixImageUrlsMiddleware'
|
||||
import excerptMiddleware from './middleware/excerptMiddleware'
|
||||
import dotenv from 'dotenv'
|
||||
import {
|
||||
GraphQLLowerCaseDirective,
|
||||
GraphQLTrimDirective,
|
||||
GraphQLDefaultToDirective
|
||||
} from 'graphql-custom-directives';
|
||||
import faker from 'faker'
|
||||
import mocks from './mocks'
|
||||
import middleware from './middleware'
|
||||
|
||||
import passport from 'passport'
|
||||
import jwtStrategy from './jwt/strategy'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// import {
|
||||
// GraphQLLowerCaseDirective,
|
||||
// GraphQLTrimDirective,
|
||||
// GraphQLDefaultToDirective
|
||||
// } from 'graphql-custom-directives';
|
||||
|
||||
dotenv.config()
|
||||
|
||||
@ -25,15 +25,15 @@ const schema = makeExecutableSchema({
|
||||
})
|
||||
|
||||
// augmentSchema will add auto generated mutations based on types in schema
|
||||
const augmentedSchema = augmentSchema(schema)
|
||||
// const augmentedSchema = augmentSchema(schema)
|
||||
|
||||
// add custom directives
|
||||
const directives = [
|
||||
GraphQLLowerCaseDirective,
|
||||
GraphQLTrimDirective,
|
||||
GraphQLDefaultToDirective
|
||||
]
|
||||
augmentedSchema._directives.push.apply(augmentedSchema._directives, directives)
|
||||
// const directives = [
|
||||
// GraphQLLowerCaseDirective,
|
||||
// GraphQLTrimDirective,
|
||||
// GraphQLDefaultToDirective
|
||||
// ]
|
||||
// augmentedSchema._directives.push.apply(augmentedSchema._directives, directives)
|
||||
|
||||
const driver = neo4j.driver(
|
||||
process.env.NEO4J_URI || 'bolt://localhost:7687',
|
||||
@ -46,25 +46,51 @@ const driver = neo4j.driver(
|
||||
const MOCK = (process.env.MOCK === 'true')
|
||||
console.log('MOCK:', MOCK)
|
||||
|
||||
const server = new ApolloServer({
|
||||
context: {
|
||||
driver
|
||||
const server = new GraphQLServer({
|
||||
context: async (req) => {
|
||||
const payload = {
|
||||
driver,
|
||||
user: null,
|
||||
req: req.request
|
||||
}
|
||||
try {
|
||||
const token = payload.req.headers.authorization.replace('Bearer ', '')
|
||||
payload.user = await jwt.verify(token, process.env.JWT_SECRET)
|
||||
} catch (err) {}
|
||||
|
||||
return payload
|
||||
},
|
||||
schema: augmentSchema(schema),
|
||||
tracing: true,
|
||||
schema: applyMiddleware(augmentedSchema, passwordMiddleware, sluggifyMiddleware, excerptMiddleware, fixImageUrlsMiddleware, softDeleteMiddleware),
|
||||
mocks: MOCK ? {
|
||||
User: () => ({
|
||||
name: () => `${faker.name.firstName()} ${faker.name.lastName()}`,
|
||||
email: () => `${faker.internet.email()}`
|
||||
}),
|
||||
Post: () => ({
|
||||
title: () => faker.lorem.lines(1),
|
||||
slug: () => faker.lorem.slug(3),
|
||||
content: () => faker.lorem.paragraphs(5),
|
||||
contentExcerpt: () => faker.lorem.paragraphs(1)
|
||||
})
|
||||
} : false
|
||||
middlewares: middleware,
|
||||
mocks: MOCK ? mocks : false
|
||||
})
|
||||
server.listen().then(({ url }) => {
|
||||
console.log(`Server ready at ${url} 🚀`);
|
||||
|
||||
passport.use('jwt', jwtStrategy())
|
||||
server.express.use(passport.initialize())
|
||||
|
||||
server.express.post('/graphql', passport.authenticate(['jwt'], { session: false }))
|
||||
|
||||
// session middleware
|
||||
// server.express.use(session({
|
||||
// name: 'qid',
|
||||
// secret: process.env.JWT_SECRET,
|
||||
// resave: true,
|
||||
// saveUninitialized: true,
|
||||
// cookie: {
|
||||
// secure: process.env.NODE_ENV === 'production',
|
||||
// maxAge: ms('1d')
|
||||
// }
|
||||
// }))
|
||||
|
||||
const serverConfig = {
|
||||
port: 4000
|
||||
// cors: {
|
||||
// credentials: true,
|
||||
// origin: [process.env.CLIENT_URI] // your frontend url.
|
||||
// }
|
||||
}
|
||||
|
||||
server.start(serverConfig, options => {
|
||||
console.log(`Server ready at ${process.env.GRAPHQL_URI} 🚀`);
|
||||
})
|
||||
|
||||
17
src/jwt/generateToken.js
Normal file
17
src/jwt/generateToken.js
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
import jwt from 'jsonwebtoken'
|
||||
import ms from 'ms'
|
||||
|
||||
// Generate an Access Token for the given User ID
|
||||
export default function generateJwt(user) {
|
||||
const token = jwt.sign(user, process.env.JWT_SECRET, {
|
||||
expiresIn: ms('1d'),
|
||||
issuer: process.env.GRAPHQL_URI,
|
||||
audience: process.env.CLIENT_URI,
|
||||
subject: user.id.toString()
|
||||
})
|
||||
// jwt.verify(token, process.env.JWT_SECRET, (err, data) => {
|
||||
// console.log('token verification:', err, data)
|
||||
// })
|
||||
return token
|
||||
}
|
||||
29
src/jwt/strategy.js
Normal file
29
src/jwt/strategy.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { Strategy } from 'passport-jwt'
|
||||
|
||||
const cookieExtractor = (req) => {
|
||||
var token = null
|
||||
if (req && req.cookies) {
|
||||
token = req.cookies['jwt']
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const options = {
|
||||
jwtFromRequest: cookieExtractor,
|
||||
secretOrKey: process.env.JWT_SECRET,
|
||||
issuer: process.env.GRAPHQL_URI,
|
||||
audience: process.env.CLIENT_URI
|
||||
}
|
||||
|
||||
return new Strategy(options,
|
||||
(JWTPayload, next) => {
|
||||
// usually this would be a database call:
|
||||
// var user = users[_.findIndex(users, {id: JWTPayload.id})]
|
||||
if (true) {
|
||||
next(null, {})
|
||||
} else {
|
||||
next(null, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1,24 +1,29 @@
|
||||
|
||||
const replaceURL = (url) => {
|
||||
return url.replace('https://api-alpha.human-connection.org/uploads', 'http://localhost:3000/uploads')
|
||||
export const fixUrl = (url) => {
|
||||
return url.replace('https://api-alpha.human-connection.org', 'http://localhost:3000')
|
||||
}
|
||||
const fixImageURLs = (result, resolve, root, args, context, info) => {
|
||||
|
||||
if (result && typeof result === 'string' && result.indexOf('https://api-alpha.human-connection.org/uploads') === 0) {
|
||||
result = replaceURL(result)
|
||||
const fixImageURLs = (result, recursive) => {
|
||||
if (result && typeof result === 'string' && result.indexOf('https://api-alpha.human-connection.org') === 0) {
|
||||
result = fixUrl(result)
|
||||
} else if (result && Array.isArray(result)) {
|
||||
result.forEach((res, index) => {
|
||||
result[index] = fixImageURLs(result[index], true)
|
||||
})
|
||||
} else if (result && typeof result === 'object') {
|
||||
Object.keys(result).forEach(key => {
|
||||
result[key] = fixImageURLs(result[key])
|
||||
result[key] = fixImageURLs(result[key], true)
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: async (resolve, root, args, context, info) => {
|
||||
const result = await resolve(root, args, context, info)
|
||||
return fixImageURLs(result)
|
||||
},
|
||||
Query: async (resolve, root, args, context, info) => {
|
||||
let result = await resolve(root, args, context, info)
|
||||
|
||||
return fixImageURLs(result, resolve, root, args, context, info)
|
||||
return fixImageURLs(result)
|
||||
}
|
||||
}
|
||||
|
||||
13
src/middleware/index.js
Normal file
13
src/middleware/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
import passwordMiddleware from './passwordMiddleware'
|
||||
import softDeleteMiddleware from './softDeleteMiddleware'
|
||||
import sluggifyMiddleware from './sluggifyMiddleware'
|
||||
import fixImageUrlsMiddleware from './fixImageUrlsMiddleware'
|
||||
import excerptMiddleware from './excerptMiddleware'
|
||||
|
||||
export default [
|
||||
passwordMiddleware,
|
||||
sluggifyMiddleware,
|
||||
excerptMiddleware,
|
||||
fixImageUrlsMiddleware,
|
||||
softDeleteMiddleware
|
||||
]
|
||||
@ -9,7 +9,7 @@ export default {
|
||||
},
|
||||
Query: async (resolve, root, args, context, info) => {
|
||||
const result = await resolve(root, args, context, info)
|
||||
if (result.password) {
|
||||
if (result && result.password) {
|
||||
result.password = '*****'
|
||||
}
|
||||
return result
|
||||
|
||||
@ -4,18 +4,30 @@ export default {
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
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
|
||||
},
|
||||
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
|
||||
},
|
||||
User: async (resolve, root, args, context, info) => {
|
||||
// console.log('ROOT', root)
|
||||
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)
|
||||
|
||||
15
src/mocks/index.js
Normal file
15
src/mocks/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
import faker from 'faker'
|
||||
|
||||
export default {
|
||||
User: () => ({
|
||||
name: () => `${faker.name.firstName()} ${faker.name.lastName()}`,
|
||||
email: () => `${faker.internet.email()}`
|
||||
}),
|
||||
Post: () => ({
|
||||
title: () => faker.lorem.lines(1),
|
||||
slug: () => faker.lorem.slug(3),
|
||||
content: () => faker.lorem.paragraphs(5),
|
||||
contentExcerpt: () => faker.lorem.paragraphs(1)
|
||||
})
|
||||
}
|
||||
@ -1,3 +1,33 @@
|
||||
type Query {
|
||||
isLoggedIn: Boolean!
|
||||
statistics: Statistics!
|
||||
}
|
||||
type Mutation {
|
||||
login(email: String!, password: String!): LoggedInUser
|
||||
signup(email: String!, password: String!): Boolean!
|
||||
}
|
||||
type LoggedInUser {
|
||||
id: ID!
|
||||
slug: String!
|
||||
name: String!
|
||||
avatar:String!
|
||||
email: String!
|
||||
role: String!
|
||||
token: String!
|
||||
}
|
||||
|
||||
type Statistics {
|
||||
countUsers: Int!
|
||||
countPosts: Int!
|
||||
countComments: Int!
|
||||
countNotifications: Int!
|
||||
countOrganizations: Int!
|
||||
countProjects: Int!
|
||||
countInvites: Int!
|
||||
countFollows: Int!
|
||||
countShouts: Int!
|
||||
}
|
||||
|
||||
enum VisibilityEnum {
|
||||
Public
|
||||
Friends
|
||||
@ -27,13 +57,13 @@ type WrittenComment @relation(name: "WROTE") {
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
name: String @default(to: "Anonymus")
|
||||
name: String
|
||||
email: String
|
||||
slug: String
|
||||
password: String!
|
||||
avatar: String
|
||||
deleted: Boolean @default(to: false)
|
||||
disabled: Boolean @default(to: false)
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
role: UserGroupEnum
|
||||
|
||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||
@ -45,11 +75,12 @@ type User {
|
||||
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
|
||||
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(r)")
|
||||
|
||||
contributions: [WrittenPost]!
|
||||
#contributions: [WrittenPost]!
|
||||
#contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||
# @cypher(
|
||||
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
|
||||
# )
|
||||
contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
|
||||
contributionsCount: Int! @cypher(statement: """
|
||||
MATCH (this)-[:WROTE]->(r:Post)
|
||||
WHERE (NOT exists(r.deleted) OR r.deleted = false)
|
||||
@ -58,10 +89,10 @@ type User {
|
||||
)
|
||||
|
||||
comments: [WrittenComment]!
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) RETURN COUNT(r)")
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
|
||||
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) RETURN COUNT(r)")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
|
||||
organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT")
|
||||
organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT")
|
||||
@ -81,8 +112,10 @@ type Post {
|
||||
contentExcerpt: String
|
||||
image: String
|
||||
visibility: VisibilityEnum
|
||||
deleted: Boolean @default(to: false)
|
||||
disabled: Boolean @default(to: false)
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
relatedContributions: [Post]! @cypher(statement: """
|
||||
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||
@ -97,7 +130,7 @@ type Post {
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) RETURN COUNT(r)")
|
||||
|
||||
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) RETURN COUNT(r)")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
}
|
||||
|
||||
type Comment {
|
||||
@ -106,8 +139,8 @@ type Comment {
|
||||
content: String!
|
||||
contentExcerpt: String
|
||||
post: Post @relation(name: "COMMENT", direction: "OUT")
|
||||
deleted: Boolean @default(to: false)
|
||||
disabled: Boolean @default(to: false)
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
}
|
||||
|
||||
type Category {
|
||||
@ -130,12 +163,12 @@ type Badge {
|
||||
}
|
||||
|
||||
enum BadgeTypeEnum {
|
||||
Role
|
||||
Crowdfunding
|
||||
role
|
||||
crowdfunding
|
||||
}
|
||||
enum BadgeStatusEnum {
|
||||
Permanent
|
||||
Temorary
|
||||
permanent
|
||||
temorary
|
||||
}
|
||||
|
||||
type Organization {
|
||||
@ -146,8 +179,8 @@ type Organization {
|
||||
slug: String
|
||||
description: String!
|
||||
descriptionExcerpt: String
|
||||
deleted: Boolean @default(to: false)
|
||||
disabled: Boolean @default(to: false)
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
|
||||
tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
@ -159,5 +192,5 @@ type Tag {
|
||||
taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN")
|
||||
taggedOrganizations: [Organization]! @relation(name: "TAGGED", direction: "IN")
|
||||
taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(r) RETURN COUNT(r)")
|
||||
deleted: Boolean @default(to: false)
|
||||
deleted: Boolean
|
||||
}
|
||||
|
||||
@ -4,28 +4,28 @@ export default `
|
||||
mutation {
|
||||
|
||||
# Users
|
||||
u1: CreateUser(id: "u1", name: "Peter Lustig", password: "1234", email: "admin@test.de", avatar: "${faker.internet.avatar()}", role: Admin) {
|
||||
u1: CreateUser(id: "u1", name: "Peter Lustig", password: "1234", email: "admin@example.org", avatar: "${faker.internet.avatar()}", role: admin) {
|
||||
id
|
||||
name
|
||||
email
|
||||
avatar
|
||||
role
|
||||
}
|
||||
u2: CreateUser(id: "u2", name: "Bob der Bausmeister", password: "1234", email: "moderator@test.de", avatar: "${faker.internet.avatar()}", role: Moderator) {
|
||||
u2: CreateUser(id: "u2", name: "Bob der Bausmeister", password: "1234", email: "moderator@example.org", avatar: "${faker.internet.avatar()}", role: moderator) {
|
||||
id
|
||||
name
|
||||
email
|
||||
avatar
|
||||
role
|
||||
}
|
||||
u3: CreateUser(id: "u3", name: "Jenny Rostock", password: "1234", email: "user@test.de", avatar: "${faker.internet.avatar()}", role: Admin) {
|
||||
u3: CreateUser(id: "u3", name: "Jenny Rostock", password: "1234", email: "user@example.org", avatar: "${faker.internet.avatar()}", role: user) {
|
||||
id
|
||||
name
|
||||
email
|
||||
avatar
|
||||
role
|
||||
}
|
||||
u4: CreateUser(id: "u4", name: "Angie Banjie", password: "1234", email: "Angie_Banjie@yahoo.com", avatar: "${faker.internet.avatar()}", role: User) {
|
||||
u4: CreateUser(id: "u4", name: "Angie Banjie", password: "1234", email: "angie@example.org", avatar: "${faker.internet.avatar()}", role: user) {
|
||||
id
|
||||
name
|
||||
email
|
||||
@ -36,12 +36,12 @@ export default `
|
||||
u1_blacklist_u4: AddUserBlacklisted(from: { id: "u1" }, to: { id: "u4" }) { from { id } }
|
||||
|
||||
# Badges
|
||||
b1: CreateBadge(id: "b1", key: "indiegogo_en_racoon", type: Crowdfunding, status: Permanent, icon: "indiegogo_en_racoon") { id }
|
||||
b2: CreateBadge(id: "b2", key: "indiegogo_en_rabbit", type: Crowdfunding, status: Permanent, icon: "indiegogo_en_rabbit") { id }
|
||||
b3: CreateBadge(id: "b3", key: "indiegogo_en_wolf", type: Crowdfunding, status: Permanent, icon: "indiegogo_en_wolf") { id }
|
||||
b4: CreateBadge(id: "b4", key: "indiegogo_en_bear", type: Crowdfunding, status: Permanent, icon: "indiegogo_en_bear") { id }
|
||||
b5: CreateBadge(id: "b5", key: "indiegogo_en_turtle", type: Crowdfunding, status: Permanent, icon: "indiegogo_en_turtle") { id }
|
||||
b6: CreateBadge(id: "b6", key: "indiegogo_en_rhino", type: Crowdfunding, status: Permanent, icon: "indiegogo_en_rhino") { id }
|
||||
b1: CreateBadge(id: "b1", key: "indiegogo_en_racoon", type: Crowdfunding, status: permanent, icon: "indiegogo_en_racoon") { id }
|
||||
b2: CreateBadge(id: "b2", key: "indiegogo_en_rabbit", type: Crowdfunding, status: permanent, icon: "indiegogo_en_rabbit") { id }
|
||||
b3: CreateBadge(id: "b3", key: "indiegogo_en_wolf", type: Crowdfunding, status: permanent, icon: "indiegogo_en_wolf") { id }
|
||||
b4: CreateBadge(id: "b4", key: "indiegogo_en_bear", type: Crowdfunding, status: permanent, icon: "indiegogo_en_bear") { id }
|
||||
b5: CreateBadge(id: "b5", key: "indiegogo_en_turtle", type: Crowdfunding, status: permanent, icon: "indiegogo_en_turtle") { id }
|
||||
b6: CreateBadge(id: "b6", key: "indiegogo_en_rhino", type: Crowdfunding, status: permanent, icon: "indiegogo_en_rhino") { id }
|
||||
|
||||
b1_u1: AddUserBadges(from: {id: "b1"}, to: {id: "u1"}) { from { id } }
|
||||
b2_u1: AddUserBadges(from: {id: "b2"}, to: {id: "u1"}) { from { id } }
|
||||
@ -72,7 +72,7 @@ export default `
|
||||
p1: CreatePost(
|
||||
id: "p1",
|
||||
title: "Gedanken eines Polizisten zum Einsatz im Hambacher Forst",
|
||||
content: "<p><strong>Diese Zukunftsstadt ist real und keine Computer-Animation – s</strong>ondern sie ist das Lebenswerk des mittlerweile über 100 Jahre alten Futuristen und Architekten Jacque Fresco aus Florida. In 35 Jahren (seit seinem 13. Lebensjahr) hat dieser zusammen mit seiner Frau seinen futuristischen Traum von einer besonderen Zukunftsstadt auf 85.000 Quadratmetern realisiert. In den Gebäuden und Gärten befinden sich u.a. ein Forschungszentrum, Vortragsräume und unzählige seiner Modelle & Architekturentwürfe.</p><br /><p>Sein zentrales Anliegen ist eine resourcenbasierte Wirtschaft und die Abschaffung von Geld und Privatbesitz. Mit Hilfe von Roboterarbeit und dem Bedingungslosen Grundeinkommen (da nach seiner Ansicht in den kommenden Jahren fast alle Jobs automatisiert werden), möchte er eine ökologische Landwirtschaft mit Permakulturen etc. und eine effiziente Energiegewinnung (ausschließlich durch regenerative Energien) schaffen. Wenige kompatible Formen in einer sparsamen Modulbauweise (in die u.a. bereits variable Service- und Reparaturelemente integriert sind) sollen insgesamt eine soziale & ökologische Utopie im Einklang mit der Natur ermöglichen.</p><br /><p>Nachfolgend der Direkt-Link auf den interessanten Artikel von Zoltan Istvan, der den Architekten und seine Frau in Florida besuchen durfte und seinen Artikel Ende 2016 auf „MOTHERBOARD“ veröffentlicht hatte:</p><br /><p>https://motherboard.vice.com/de/article/vv34nb/ich-habe-die-zukunft-besucht-in-der-wir-ohne-geld-steuern-und-besitz-leben </p><br /><p>Da soll noch jemand behaupten, es gäbe keine Utopien mehr bzw. keine Futuristen, die ihre kreativen und zukunftsfähigen Ideen (auch in ganz großem Stil) selbst in die Tat umsetzen. LG @all :) </p><br /><p><strong>Wir sind eine Menschheitsfamilie. • Wir sind eins. • Wir sind HUMAN CONNECTION</strong> ❤️</p>",
|
||||
content: "<p><strong>Diese Zukunftsstadt ist real und keine Computer-Animation</strong> – sondern sie ist das Lebenswerk des mittlerweile über 100 Jahre alten Futuristen und Architekten Jacque Fresco aus Florida. In 35 Jahren (seit seinem 13. Lebensjahr) hat dieser zusammen mit seiner Frau seinen futuristischen Traum von einer besonderen Zukunftsstadt auf 85.000 Quadratmetern realisiert. In den Gebäuden und Gärten befinden sich u.a. ein Forschungszentrum, Vortragsräume und unzählige seiner Modelle & Architekturentwürfe.</p><br /><p>Sein zentrales Anliegen ist eine resourcenbasierte Wirtschaft und die Abschaffung von Geld und Privatbesitz. Mit Hilfe von Roboterarbeit und dem Bedingungslosen Grundeinkommen (da nach seiner Ansicht in den kommenden Jahren fast alle Jobs automatisiert werden), möchte er eine ökologische Landwirtschaft mit Permakulturen etc. und eine effiziente Energiegewinnung (ausschließlich durch regenerative Energien) schaffen. Wenige kompatible Formen in einer sparsamen Modulbauweise (in die u.a. bereits variable Service- und Reparaturelemente integriert sind) sollen insgesamt eine soziale & ökologische Utopie im Einklang mit der Natur ermöglichen.</p><br /><p>Nachfolgend der Direkt-Link auf den interessanten Artikel von Zoltan Istvan, der den Architekten und seine Frau in Florida besuchen durfte und seinen Artikel Ende 2016 auf „MOTHERBOARD“ veröffentlicht hatte:</p><br /><p>https://motherboard.vice.com/de/article/vv34nb/ich-habe-die-zukunft-besucht-in-der-wir-ohne-geld-steuern-und-besitz-leben </p><br /><p>Da soll noch jemand behaupten, es gäbe keine Utopien mehr bzw. keine Futuristen, die ihre kreativen und zukunftsfähigen Ideen (auch in ganz großem Stil) selbst in die Tat umsetzen. LG @all :) </p><br /><p><strong>Wir sind eine Menschheitsfamilie. • Wir sind eins. • Wir sind HUMAN CONNECTION</strong> ❤️</p>",
|
||||
image: "https://picsum.photos/1280/1024?image=352",
|
||||
visibility: Public,
|
||||
disabled: false,
|
||||
|
||||
3862
yarn-error.log
Normal file
3862
yarn-error.log
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user