Prepare to implement scanner
This commit is contained in:
parent
02e657328f
commit
286cfa992b
|
@ -14,6 +14,7 @@
|
||||||
"author": "William Lyon",
|
"author": "William Lyon",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/node": "^7.5.0",
|
||||||
"@babel/plugin-transform-spread": "^7.2.2",
|
"@babel/plugin-transform-spread": "^7.2.2",
|
||||||
"apollo-boost": "^0.4.3",
|
"apollo-boost": "^0.4.3",
|
||||||
"apollo-cache-inmemory": "^1.5.1",
|
"apollo-cache-inmemory": "^1.5.1",
|
||||||
|
|
|
@ -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
|
|
@ -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");
|
|
|
@ -1,10 +1,15 @@
|
||||||
import { typeDefs } from './graphql-schema'
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import bodyParser from 'body-parser'
|
import bodyParser from 'body-parser'
|
||||||
import { v1 as neo4j } from 'neo4j-driver'
|
import { v1 as neo4j } from 'neo4j-driver'
|
||||||
import { makeAugmentedSchema } from 'neo4j-graphql-js'
|
import { makeAugmentedSchema } from 'neo4j-graphql-js'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
import http from 'http'
|
||||||
|
import PhotoScanner from './Scanner'
|
||||||
|
|
||||||
|
import { getUserFromToken, getTokenFromBearer } from './token'
|
||||||
|
|
||||||
// set environment variables from ../.env
|
// set environment variables from ../.env
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
@ -20,7 +25,14 @@ app.use(bodyParser.json())
|
||||||
* https://grandstack.io/docs/neo4j-graphql-js-api.html#makeaugmentedschemaoptions-graphqlschema
|
* 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({
|
const schema = makeAugmentedSchema({
|
||||||
typeDefs,
|
typeDefs,
|
||||||
|
@ -30,10 +42,17 @@ const schema = makeAugmentedSchema({
|
||||||
hasRole: true,
|
hasRole: true,
|
||||||
},
|
},
|
||||||
mutation: false,
|
mutation: false,
|
||||||
|
query: {
|
||||||
|
exclude: ['ScannerResult', 'AuthorizeResult', 'Subscription'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolvers: {
|
resolvers: {
|
||||||
Mutation: {
|
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
|
* Create a new ApolloServer instance, serving the GraphQL schema
|
||||||
* created using makeAugmentedSchema above and injecting the Neo4j driver
|
* created using makeAugmentedSchema above and injecting the Neo4j driver
|
||||||
|
@ -58,22 +79,50 @@ const driver = neo4j.driver(
|
||||||
* generated resolvers to connect to the database.
|
* generated resolvers to connect to the database.
|
||||||
*/
|
*/
|
||||||
const server = new ApolloServer({
|
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,
|
schema: schema,
|
||||||
introspection: true,
|
introspection: true,
|
||||||
playground: 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
|
// Specify port and path for GraphQL endpoint
|
||||||
const port = process.env.GRAPHQL_LISTEN_PORT || 4001
|
const port = process.env.GRAPHQL_LISTEN_PORT || 4001
|
||||||
const path = '/graphql'
|
const graphPath = '/graphql'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Optionally, apply Express middleware for authentication, etc
|
* Optionally, apply Express middleware for authentication, etc
|
||||||
* This also also allows us to specify a path for the GraphQL endpoint
|
* 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 }, () => {
|
const httpServer = http.createServer(app)
|
||||||
console.log(`GraphQL server ready at http://localhost:${port}${path}`)
|
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}`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -36,12 +36,12 @@ const mutation = {
|
||||||
let { username, password } = args
|
let { username, password } = args
|
||||||
|
|
||||||
let session = ctx.driver.session()
|
let session = ctx.driver.session()
|
||||||
let result = await session.run(
|
let findResult = await session.run(
|
||||||
'MATCH (usr:User {username: {username} }) RETURN usr',
|
'MATCH (usr:User {username: {username} }) RETURN usr',
|
||||||
{ username }
|
{ username }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result.records.length > 0) {
|
if (findResult.records.length > 0) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
status: 'Username is already taken',
|
status: 'Username is already taken',
|
||||||
|
@ -49,17 +49,21 @@ const mutation = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await session.run(
|
const registerResult = await session.run(
|
||||||
'CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n',
|
'CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n.id',
|
||||||
{ username, password, id: uuid() }
|
{ username, password, id: uuid() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let id = registerResult.records[0].get('n.id')
|
||||||
|
|
||||||
|
const token = jwt.sign({ id }, process.env.JWT_SECRET)
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: 'User created',
|
status: 'User created',
|
||||||
token: 'yay',
|
token: token,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Album @isAuthenticated {
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String
|
title: String
|
||||||
photos: [Photo] @relation(name: "CONTAINS", direction: "OUT")
|
photos: [Photo] @relation(name: "CONTAINS", direction: "OUT")
|
||||||
owner: User! @ relation(name: "OWNS", direction: "IN")
|
owner: User! @relation(name: "OWNS", direction: "IN")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Photo @isAuthenticated {
|
type Photo @isAuthenticated {
|
||||||
|
@ -36,9 +36,22 @@ type AuthorizeResult {
|
||||||
token: String
|
token: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScannerResult {
|
||||||
|
finished: Boolean!
|
||||||
|
error: Boolean!
|
||||||
|
errorMessage: String
|
||||||
|
progress: Float
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription {
|
||||||
|
scannerStatusUpdate: ScannerResult
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
authorizeUser(username: String!, password: String!): AuthorizeResult!
|
authorizeUser(username: String!, password: String!): AuthorizeResult!
|
||||||
registerUser(username: String!, password: String!): AuthorizeResult!
|
registerUser(username: String!, password: String!): AuthorizeResult!
|
||||||
|
|
||||||
|
scanAll: ScannerResult! @isAuthenticated
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue