1
Fork 0

Prepare to implement scanner

This commit is contained in:
viktorstrate 2019-07-13 15:50:59 +02:00
parent 02e657328f
commit 286cfa992b
8 changed files with 233 additions and 27 deletions

View File

@ -14,6 +14,7 @@
"author": "William Lyon",
"license": "MIT",
"dependencies": {
"@babel/node": "^7.5.0",
"@babel/plugin-transform-spread": "^7.2.2",
"apollo-boost": "^0.4.3",
"apollo-cache-inmemory": "^1.5.1",

77
api/src/Scanner.js Normal file
View File

@ -0,0 +1,77 @@
import { PubSub } from 'apollo-server'
export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS'
class PhotoScanner {
constructor(driver) {
this.driver = driver
this.isRunning = false
this.pubsub = new PubSub()
}
async scanAll() {
this.isRunning = true
this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
scannerStatusUpdate: {
progress: 0,
finished: false,
error: false,
errorMessage: '',
},
})
let session = this.driver.session()
session
.run(
'MATCH (u:User) return u.id AS id, u.username AS username, u.rootPath as path'
)
.subscribe({
onNext: function(record) {
const username = record.get('username')
const id = record.get('id')
const path = record.get('path')
if (!path) {
console.log(`User ${username}, has no root path, skipping`)
return
}
this.scanUser(id)
console.log(`Scanning ${username}...`)
},
onCompleted: () => {
session.close()
this.isRunning = false
console.log('Done scanning')
this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
scannerStatusUpdate: {
progress: 100,
finished: true,
error: false,
errorMessage: '',
},
})
},
onError: error => {
console.error(error)
this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
scannerStatusUpdate: {
progress: 0,
finished: false,
error: true,
errorMessage: error.message,
},
})
},
})
}
async scanUser(id) {}
}
export default PhotoScanner

View File

@ -1,13 +0,0 @@
import fs from "fs";
import path from "path";
/*
* Check for GRAPHQL_SCHEMA environment variable to specify schema file
* fallback to schema.graphql if GRAPHQL_SCHEMA environment variable is not set
*/
export const typeDefs = fs
.readFileSync(
process.env.GRAPHQL_SCHEMA || path.join(__dirname, "schema.graphql")
)
.toString("utf-8");

View File

@ -1,10 +1,15 @@
import { typeDefs } from './graphql-schema'
import fs from 'fs'
import path from 'path'
import { ApolloServer } from 'apollo-server-express'
import express from 'express'
import bodyParser from 'body-parser'
import { v1 as neo4j } from 'neo4j-driver'
import { makeAugmentedSchema } from 'neo4j-graphql-js'
import dotenv from 'dotenv'
import http from 'http'
import PhotoScanner from './Scanner'
import { getUserFromToken, getTokenFromBearer } from './token'
// set environment variables from ../.env
dotenv.config()
@ -20,7 +25,14 @@ app.use(bodyParser.json())
* https://grandstack.io/docs/neo4j-graphql-js-api.html#makeaugmentedschemaoptions-graphqlschema
*/
import users from './resolvers/users'
const typeDefs = fs
.readFileSync(
process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')
)
.toString('utf-8')
import usersResolver from './resolvers/users'
import scannerResolver from './resolvers/scanner'
const schema = makeAugmentedSchema({
typeDefs,
@ -30,10 +42,17 @@ const schema = makeAugmentedSchema({
hasRole: true,
},
mutation: false,
query: {
exclude: ['ScannerResult', 'AuthorizeResult', 'Subscription'],
},
},
resolvers: {
Mutation: {
...users.mutation,
...usersResolver.mutation,
...scannerResolver.mutation,
},
Subscription: {
...scannerResolver.subscription,
},
},
})
@ -51,6 +70,8 @@ const driver = neo4j.driver(
)
)
const scanner = new PhotoScanner(driver)
/*
* Create a new ApolloServer instance, serving the GraphQL schema
* created using makeAugmentedSchema above and injecting the Neo4j driver
@ -58,22 +79,50 @@ const driver = neo4j.driver(
* generated resolvers to connect to the database.
*/
const server = new ApolloServer({
context: ({ req }) => Object.assign(req, { driver }),
context: async function({ req }) {
let user = null
if (req && req.headers.authorization) {
const token = getTokenFromBearer(req.headers.authorization)
user = await getUserFromToken(token, driver)
}
return { ...req, driver, scanner, user }
},
schema: schema,
introspection: true,
playground: true,
subscriptions: {
onConnect: async (connectionParams, webSocket) => {
const token = getTokenFromBearer(connectionParams.Authorization)
const user = await getUserFromToken(token, driver)
return {
token,
user,
}
},
},
})
// Specify port and path for GraphQL endpoint
const port = process.env.GRAPHQL_LISTEN_PORT || 4001
const path = '/graphql'
const graphPath = '/graphql'
/*
* Optionally, apply Express middleware for authentication, etc
* This also also allows us to specify a path for the GraphQL endpoint
*/
server.applyMiddleware({ app, path })
server.applyMiddleware({ app, graphPath })
app.listen({ port, path }, () => {
console.log(`GraphQL server ready at http://localhost:${port}${path}`)
const httpServer = http.createServer(app)
server.installSubscriptionHandlers(httpServer)
httpServer.listen({ port, graphPath }, () => {
console.log(
`🚀 GraphQL endpoint ready at http://localhost:${port}${server.graphqlPath}`
)
console.log(
`🚀 Subscriptions ready at ws://localhost:${port}${server.subscriptionsPath}`
)
})

View File

@ -0,0 +1,34 @@
import { EVENT_SCANNER_PROGRESS } from '../scanner'
const mutation = {
async scanAll(root, args, ctx, info) {
if (ctx.scanner.isRunning) {
return {
finished: false,
error: true,
errorMessage: 'Scanner already running',
}
}
ctx.scanner.scanAll()
return {
finished: false,
error: false,
progress: 0,
}
},
}
const subscription = {
scannerStatusUpdate: {
subscribe(root, args, ctx, info) {
return ctx.scanner.pubsub.asyncIterator([EVENT_SCANNER_PROGRESS])
},
},
}
export default {
mutation,
subscription,
}

View File

@ -36,12 +36,12 @@ const mutation = {
let { username, password } = args
let session = ctx.driver.session()
let result = await session.run(
let findResult = await session.run(
'MATCH (usr:User {username: {username} }) RETURN usr',
{ username }
)
if (result.records.length > 0) {
if (findResult.records.length > 0) {
return {
success: false,
status: 'Username is already taken',
@ -49,17 +49,21 @@ const mutation = {
}
}
await session.run(
'CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n',
const registerResult = await session.run(
'CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n.id',
{ username, password, id: uuid() }
)
let id = registerResult.records[0].get('n.id')
const token = jwt.sign({ id }, process.env.JWT_SECRET)
session.close()
return {
success: true,
status: 'User created',
token: 'yay',
token: token,
}
},
}

View File

@ -36,9 +36,22 @@ type AuthorizeResult {
token: String
}
type ScannerResult {
finished: Boolean!
error: Boolean!
errorMessage: String
progress: Float
}
type Subscription {
scannerStatusUpdate: ScannerResult
}
type Mutation {
authorizeUser(username: String!, password: String!): AuthorizeResult!
registerUser(username: String!, password: String!): AuthorizeResult!
scanAll: ScannerResult! @isAuthenticated
}
type Query {

41
api/src/token.js Normal file
View File

@ -0,0 +1,41 @@
import jwt from 'jsonwebtoken'
export const getUserFromToken = async function(token, driver) {
const tokenContent = jwt.verify(token, process.env.JWT_SECRET)
const userId = tokenContent.id
const session = driver.session()
const userResult = await session.run(
'MATCH (u:User {id: {userId}}) RETURN u',
{
userId,
}
)
if (userResult.records.length == 0) {
throw new Error(`User doesn't exist anymore`)
}
const user = userResult.records[0].toObject()
session.close()
return user
}
export const getTokenFromBearer = bearer => {
let token = bearer
if (!token) {
throw new Error('Missing auth token')
}
if (!token.toLowerCase().startsWith('bearer ')) {
throw new Error('Invalid auth token')
}
token = token.substr(7)
return token
}