Scanner work + image processing
This commit is contained in:
parent
286cfa992b
commit
f0bf1c2dc4
|
@ -1,5 +1,7 @@
|
||||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
/api/src/cache/
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,21 @@
|
||||||
"apollo-server": "^2.6.2",
|
"apollo-server": "^2.6.2",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
|
"cookie-parser": "^1.4.4",
|
||||||
"core-js": "^3.1.4",
|
"core-js": "^3.1.4",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
|
"exiftool-vendored": "^8.15.0",
|
||||||
|
"fs-extra": "^8.1.0",
|
||||||
"graphql-tag": "^2.10.1",
|
"graphql-tag": "^2.10.1",
|
||||||
|
"image-size": "^0.7.4",
|
||||||
|
"img-type": "^0.1.13",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"lodash": "^4.17.14",
|
||||||
"neo4j-driver": "^1.7.3",
|
"neo4j-driver": "^1.7.3",
|
||||||
"neo4j-graphql-js": "^2.6.3",
|
"neo4j-graphql-js": "^2.6.3",
|
||||||
"node-fetch": "^2.3.0",
|
"node-fetch": "^2.3.0",
|
||||||
"regenerator-runtime": "^0.13.2",
|
"regenerator-runtime": "^0.13.2",
|
||||||
|
"sharp": "^0.22.1",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
|
import fs from 'fs-extra'
|
||||||
|
import path from 'path'
|
||||||
|
import { resolve as pathResolve, basename as pathBasename } from 'path'
|
||||||
import { PubSub } from 'apollo-server'
|
import { PubSub } from 'apollo-server'
|
||||||
|
import imgType from 'img-type'
|
||||||
|
import uuid from 'uuid'
|
||||||
|
import { exiftool } from 'exiftool-vendored'
|
||||||
|
import sharp from 'sharp'
|
||||||
|
import config from './config'
|
||||||
|
|
||||||
export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS'
|
export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS'
|
||||||
|
|
||||||
|
@ -7,6 +15,11 @@ class PhotoScanner {
|
||||||
this.driver = driver
|
this.driver = driver
|
||||||
this.isRunning = false
|
this.isRunning = false
|
||||||
this.pubsub = new PubSub()
|
this.pubsub = new PubSub()
|
||||||
|
|
||||||
|
this.scanAll = this.scanAll.bind(this)
|
||||||
|
this.scanAlbum = this.scanAlbum.bind(this)
|
||||||
|
this.scanUser = this.scanUser.bind(this)
|
||||||
|
this.processImage = this.processImage.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
async scanAll() {
|
async scanAll() {
|
||||||
|
@ -23,24 +36,20 @@ class PhotoScanner {
|
||||||
|
|
||||||
let session = this.driver.session()
|
let session = this.driver.session()
|
||||||
|
|
||||||
session
|
session.run('MATCH (u:User) return u').subscribe({
|
||||||
.run(
|
onNext: record => {
|
||||||
'MATCH (u:User) return u.id AS id, u.username AS username, u.rootPath as path'
|
const user = record.toObject().u.properties
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
onNext: function(record) {
|
|
||||||
const username = record.get('username')
|
|
||||||
const id = record.get('id')
|
|
||||||
const path = record.get('path')
|
|
||||||
|
|
||||||
if (!path) {
|
console.log('USER', user)
|
||||||
console.log(`User ${username}, has no root path, skipping`)
|
|
||||||
|
if (!user.rootPath) {
|
||||||
|
console.log(`User ${user.username}, has no root path, skipping`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scanUser(id)
|
this.scanUser(user)
|
||||||
|
|
||||||
console.log(`Scanning ${username}...`)
|
console.log(`Scanning ${user.username}...`)
|
||||||
},
|
},
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
session.close()
|
session.close()
|
||||||
|
@ -71,7 +80,172 @@ class PhotoScanner {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async scanUser(id) {}
|
async scanUser(user) {
|
||||||
|
console.log('Scanning path', user.rootPath)
|
||||||
|
|
||||||
|
const driver = this.driver
|
||||||
|
const scanAlbum = this.scanAlbum
|
||||||
|
|
||||||
|
async function scanPath(path) {
|
||||||
|
const list = fs.readdirSync(path)
|
||||||
|
|
||||||
|
let foundImage = false
|
||||||
|
|
||||||
|
for (const item of list) {
|
||||||
|
const itemPath = pathResolve(path, item)
|
||||||
|
// console.log(`Scanning item ${itemPath}...`)
|
||||||
|
const stat = fs.statSync(itemPath)
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
// console.log(`Entering directory ${itemPath}`)
|
||||||
|
const imagesInDirectory = await scanPath(itemPath)
|
||||||
|
|
||||||
|
if (imagesInDirectory) {
|
||||||
|
console.log(`Found album at ${itemPath}`)
|
||||||
|
const session = driver.session()
|
||||||
|
|
||||||
|
const findAlbumResult = await session.run(
|
||||||
|
'MATCH (a:Album { path: {path} }) RETURN a',
|
||||||
|
{
|
||||||
|
path: itemPath,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('FIND ALBUM RESULT', findAlbumResult.records)
|
||||||
|
|
||||||
|
if (findAlbumResult.records.length != 0) {
|
||||||
|
console.log('Album already exists')
|
||||||
|
|
||||||
|
const album = findAlbumResult.records[0].toObject().a.properties
|
||||||
|
scanAlbum(album)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Adding album')
|
||||||
|
const albumId = uuid()
|
||||||
|
const albumResult = await session.run(
|
||||||
|
`MATCH (u:User { id: {userId} })
|
||||||
|
CREATE (a:Album { id: {id}, title: {title}, path: {path} })
|
||||||
|
CREATE (u)-[own:OWNS]->(a)
|
||||||
|
RETURN a`,
|
||||||
|
{
|
||||||
|
id: albumId,
|
||||||
|
userId: user.id,
|
||||||
|
title: item,
|
||||||
|
path: itemPath,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const album = albumResult.records[0].toObject().a.properties
|
||||||
|
scanAlbum(album)
|
||||||
|
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundImage && (await imgType.isImg(itemPath))) {
|
||||||
|
foundImage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundImage
|
||||||
|
}
|
||||||
|
|
||||||
|
await scanPath(user.rootPath)
|
||||||
|
console.log('User scan complete')
|
||||||
|
}
|
||||||
|
|
||||||
|
async scanAlbum(album) {
|
||||||
|
const { title, path, id } = album
|
||||||
|
console.log('Scanning album', title)
|
||||||
|
|
||||||
|
const list = fs.readdirSync(path)
|
||||||
|
|
||||||
|
for (const item of list) {
|
||||||
|
const itemPath = pathResolve(path, item)
|
||||||
|
|
||||||
|
if (await imgType.isImg(itemPath)) {
|
||||||
|
const session = this.driver.session()
|
||||||
|
|
||||||
|
const photoResult = await session.run(
|
||||||
|
`MATCH (p:Photo {path: {imgPath} })<--(a:Album {id: {albumId}}) RETURN p`,
|
||||||
|
{
|
||||||
|
imgPath: itemPath,
|
||||||
|
albumId: id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (photoResult.records.length != 0) {
|
||||||
|
console.log(`Photo already exists ${item}`)
|
||||||
|
} else {
|
||||||
|
console.log(`Found new image at ${itemPath}`)
|
||||||
|
const imageId = uuid()
|
||||||
|
await session.run(
|
||||||
|
`MATCH (a:Album { id: {albumId} })
|
||||||
|
CREATE (p:Photo {id: {id}, path: {path}, title: {title} })
|
||||||
|
CREATE (a)-[:CONTAINS]->(p)`,
|
||||||
|
{
|
||||||
|
id: imageId,
|
||||||
|
path: itemPath,
|
||||||
|
title: item,
|
||||||
|
albumId: id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.processImage(imageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processImage(id) {
|
||||||
|
console.log('Processing image')
|
||||||
|
const session = this.driver.session()
|
||||||
|
|
||||||
|
const result = await session.run('MATCH (p:Photo { id: {id} }) return p', {
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
const photo = result.records[0].get('p').properties
|
||||||
|
|
||||||
|
console.log('PHOTO', photo.path)
|
||||||
|
|
||||||
|
const imagePath = path.resolve(config.cachePath, 'images', id)
|
||||||
|
|
||||||
|
await fs.remove(imagePath)
|
||||||
|
await fs.mkdirp(imagePath)
|
||||||
|
|
||||||
|
const type = await imgType.getType(photo.path)
|
||||||
|
|
||||||
|
let resizeBaseImg = photo.path
|
||||||
|
|
||||||
|
const rawTypes = ['cr2', 'arw', 'crw', 'dng']
|
||||||
|
|
||||||
|
if (rawTypes.includes(type)) {
|
||||||
|
console.log('Processing RAW image')
|
||||||
|
|
||||||
|
const extractedPath = path.resolve(imagePath, 'extracted.jpg')
|
||||||
|
await exiftool.extractPreview(photo.path, extractedPath)
|
||||||
|
|
||||||
|
resizeBaseImg = extractedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize image
|
||||||
|
console.log('Resizing image', resizeBaseImg)
|
||||||
|
await sharp(resizeBaseImg)
|
||||||
|
.resize(1440, 1080, { fit: 'inside', withoutEnlargement: true })
|
||||||
|
.toFile(path.resolve(imagePath, 'thumbnail.jpg'))
|
||||||
|
|
||||||
|
await sharp(resizeBaseImg)
|
||||||
|
.jpeg({ quality: 85 })
|
||||||
|
.toFile(path.resolve(imagePath, 'original.jpg'))
|
||||||
|
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
console.log('Processing done')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PhotoScanner
|
export default PhotoScanner
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
cachePath: path.resolve(__dirname, 'cache'),
|
||||||
|
}
|
101
api/src/index.js
101
api/src/index.js
|
@ -1,13 +1,16 @@
|
||||||
import fs from 'fs'
|
import fs from 'fs-extra'
|
||||||
import path from 'path'
|
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 cookieParser from 'cookie-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 http from 'http'
|
||||||
import PhotoScanner from './Scanner'
|
import PhotoScanner from './Scanner'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import config from './config'
|
||||||
|
|
||||||
import { getUserFromToken, getTokenFromBearer } from './token'
|
import { getUserFromToken, getTokenFromBearer } from './token'
|
||||||
|
|
||||||
|
@ -16,6 +19,7 @@ dotenv.config()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(bodyParser.json())
|
app.use(bodyParser.json())
|
||||||
|
app.use(cookieParser())
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create an executable GraphQL schema object from GraphQL type definitions
|
* Create an executable GraphQL schema object from GraphQL type definitions
|
||||||
|
@ -33,6 +37,9 @@ const typeDefs = fs
|
||||||
|
|
||||||
import usersResolver from './resolvers/users'
|
import usersResolver from './resolvers/users'
|
||||||
import scannerResolver from './resolvers/scanner'
|
import scannerResolver from './resolvers/scanner'
|
||||||
|
import photosResolver from './resolvers/photos'
|
||||||
|
|
||||||
|
const resolvers = [usersResolver, scannerResolver, photosResolver]
|
||||||
|
|
||||||
const schema = makeAugmentedSchema({
|
const schema = makeAugmentedSchema({
|
||||||
typeDefs,
|
typeDefs,
|
||||||
|
@ -42,19 +49,12 @@ const schema = makeAugmentedSchema({
|
||||||
hasRole: true,
|
hasRole: true,
|
||||||
},
|
},
|
||||||
mutation: false,
|
mutation: false,
|
||||||
query: {
|
query: false,
|
||||||
exclude: ['ScannerResult', 'AuthorizeResult', 'Subscription'],
|
// query: {
|
||||||
},
|
// exclude: ['ScannerResult', 'AuthorizeResult', 'Subscription'],
|
||||||
},
|
// },
|
||||||
resolvers: {
|
|
||||||
Mutation: {
|
|
||||||
...usersResolver.mutation,
|
|
||||||
...scannerResolver.mutation,
|
|
||||||
},
|
|
||||||
Subscription: {
|
|
||||||
...scannerResolver.subscription,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
resolvers: resolvers.reduce((prev, curr) => _.merge(prev, curr), {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -72,6 +72,10 @@ const driver = neo4j.driver(
|
||||||
|
|
||||||
const scanner = new PhotoScanner(driver)
|
const scanner = new PhotoScanner(driver)
|
||||||
|
|
||||||
|
// Specify port and path for GraphQL endpoint
|
||||||
|
const port = process.env.GRAPHQL_LISTEN_PORT || 4001
|
||||||
|
const graphPath = '/graphql'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
|
@ -81,13 +85,21 @@ const scanner = new PhotoScanner(driver)
|
||||||
const server = new ApolloServer({
|
const server = new ApolloServer({
|
||||||
context: async function({ req }) {
|
context: async function({ req }) {
|
||||||
let user = null
|
let user = null
|
||||||
|
let token = null
|
||||||
|
|
||||||
if (req && req.headers.authorization) {
|
if (req && req.headers.authorization) {
|
||||||
const token = getTokenFromBearer(req.headers.authorization)
|
token = getTokenFromBearer(req.headers.authorization)
|
||||||
user = await getUserFromToken(token, driver)
|
user = await getUserFromToken(token, driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...req, driver, scanner, user }
|
return {
|
||||||
|
...req,
|
||||||
|
driver,
|
||||||
|
scanner,
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
endpoint: `http://localhost:${port}`,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
schema: schema,
|
schema: schema,
|
||||||
introspection: true,
|
introspection: true,
|
||||||
|
@ -105,16 +117,59 @@ const server = new ApolloServer({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Specify port and path for GraphQL endpoint
|
|
||||||
const port = process.env.GRAPHQL_LISTEN_PORT || 4001
|
|
||||||
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, graphPath })
|
server.applyMiddleware({ app, graphPath })
|
||||||
|
|
||||||
|
app.use('/images/:id/:image', async function(req, res) {
|
||||||
|
const { id, image } = req.params
|
||||||
|
|
||||||
|
console.log('image', image)
|
||||||
|
|
||||||
|
if (image != 'original.jpg' && image != 'thumbnail.jpg') {
|
||||||
|
return res.status(404).send('Image not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = req.cookies.token
|
||||||
|
user = await getUserFromToken(token, driver)
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(401).send(err.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = driver.session()
|
||||||
|
|
||||||
|
const result = await session.run(
|
||||||
|
'MATCH (p:Photo { id: {id} }) MATCH (p)<-[:CONTAINS]-(:Album)<-[:OWNS]-(u:User) RETURN p as photo, u.id as userId',
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.records.length == 0) {
|
||||||
|
return res.status(404).send(`Image not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = result.records[0].get('userId')
|
||||||
|
const photo = result.records[0].get('photo')
|
||||||
|
|
||||||
|
if (userId != user.id) {
|
||||||
|
return res.status(401).send(`Image not owned by you`)
|
||||||
|
}
|
||||||
|
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
const imagePath = path.resolve(config.cachePath, 'images', id, image)
|
||||||
|
const imageFound = await fs.exists(imagePath)
|
||||||
|
|
||||||
|
if (!imageFound) {
|
||||||
|
console.log('Image not found', imagePath)
|
||||||
|
await scanner.processImage(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sendFile(imagePath)
|
||||||
|
})
|
||||||
|
|
||||||
const httpServer = http.createServer(app)
|
const httpServer = http.createServer(app)
|
||||||
server.installSubscriptionHandlers(httpServer)
|
server.installSubscriptionHandlers(httpServer)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { cypherQuery } from 'neo4j-graphql-js'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
import fs from 'fs-extra'
|
||||||
|
import path from 'path'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
const imageSize = promisify(require('image-size'))
|
||||||
|
|
||||||
|
function injectAt(query, index, injection) {
|
||||||
|
return query.substr(0, index) + injection + query.substr(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Query = {
|
||||||
|
myAlbums: async function(root, args, ctx, info) {
|
||||||
|
const query = cypherQuery(args, ctx, info)
|
||||||
|
|
||||||
|
const whereSplit = query[0].indexOf('RETURN')
|
||||||
|
|
||||||
|
query[0] = injectAt(
|
||||||
|
query[0],
|
||||||
|
whereSplit,
|
||||||
|
`MATCH (u:User { id: {userid} }) WHERE (u)-[:OWNS]->(album) `
|
||||||
|
)
|
||||||
|
query[1].userid = ctx.user.id
|
||||||
|
console.log(query)
|
||||||
|
|
||||||
|
const addIDSplit = query[0].indexOf('album_photos {') + 14
|
||||||
|
|
||||||
|
console.log('ID SPLIT', query[0].substr(0, addIDSplit))
|
||||||
|
query[0] = injectAt(query[0], addIDSplit, `.id,`)
|
||||||
|
|
||||||
|
const session = ctx.driver.session()
|
||||||
|
|
||||||
|
const result = await session.run(...query)
|
||||||
|
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
return result.records.map(record => record.get('album'))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Photo = {
|
||||||
|
async original(root, args, ctx, info) {
|
||||||
|
const imgPath = path.resolve(
|
||||||
|
config.cachePath,
|
||||||
|
'images',
|
||||||
|
root.id,
|
||||||
|
'original.jpg'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!(await fs.exists(imgPath))) {
|
||||||
|
await ctx.scanner.processImage(root.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { width, height } = await imageSize(imgPath)
|
||||||
|
return {
|
||||||
|
path: `${ctx.endpoint}/images/${root.id}/original.jpg`,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async thumbnail(root, args, ctx, info) {
|
||||||
|
return {
|
||||||
|
path: `${ctx.endpoint}/images/${root.id}/thumbnail.jpg`,
|
||||||
|
width: 120,
|
||||||
|
height: 240,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Query,
|
||||||
|
Photo,
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { EVENT_SCANNER_PROGRESS } from '../scanner'
|
import { EVENT_SCANNER_PROGRESS } from '../scanner'
|
||||||
|
|
||||||
const mutation = {
|
const Mutation = {
|
||||||
async scanAll(root, args, ctx, info) {
|
async scanAll(root, args, ctx, info) {
|
||||||
if (ctx.scanner.isRunning) {
|
if (ctx.scanner.isRunning) {
|
||||||
return {
|
return {
|
||||||
|
@ -20,7 +20,7 @@ const mutation = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscription = {
|
const Subscription = {
|
||||||
scannerStatusUpdate: {
|
scannerStatusUpdate: {
|
||||||
subscribe(root, args, ctx, info) {
|
subscribe(root, args, ctx, info) {
|
||||||
return ctx.scanner.pubsub.asyncIterator([EVENT_SCANNER_PROGRESS])
|
return ctx.scanner.pubsub.asyncIterator([EVENT_SCANNER_PROGRESS])
|
||||||
|
@ -29,6 +29,6 @@ const subscription = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mutation,
|
Mutation,
|
||||||
subscription,
|
Subscription,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import uuid from 'uuid'
|
import uuid from 'uuid'
|
||||||
|
|
||||||
const mutation = {
|
const Mutation = {
|
||||||
async authorizeUser(root, args, ctx, info) {
|
async authorizeUser(root, args, ctx, info) {
|
||||||
|
console.log('Authorize user')
|
||||||
let { username, password } = args
|
let { username, password } = args
|
||||||
|
|
||||||
let session = ctx.driver.session()
|
let session = ctx.driver.session()
|
||||||
|
@ -69,5 +70,5 @@ const mutation = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mutation,
|
Mutation,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,28 +6,42 @@ enum Role {
|
||||||
type User @isAuthenticated {
|
type User @isAuthenticated {
|
||||||
id: ID!
|
id: ID!
|
||||||
username: String!
|
username: String!
|
||||||
|
#: password: String
|
||||||
albums: [Album] @relation(name: "OWNS", direction: "OUT")
|
albums: [Album] @relation(name: "OWNS", direction: "OUT")
|
||||||
rootPath: String! @hasRole(roles: [Admin])
|
rootPath: String! @hasRole(roles: [Admin])
|
||||||
admin: Boolean
|
admin: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Album @isAuthenticated {
|
type Album {
|
||||||
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")
|
||||||
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Photo @isAuthenticated {
|
type UnimportedAlbum {
|
||||||
|
id: ID!
|
||||||
|
relativePath: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type PhotoURL {
|
||||||
|
path: String
|
||||||
|
width: Int
|
||||||
|
height: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Photo {
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String
|
title: String
|
||||||
path: String!
|
original: PhotoURL @neo4j_ignore
|
||||||
|
thumbnail: PhotoURL @neo4j_ignore
|
||||||
album: Album! @relation(name: "CONTAINS", direction: "IN")
|
album: Album! @relation(name: "CONTAINS", direction: "IN")
|
||||||
}
|
}
|
||||||
|
|
||||||
type SiteInfo {
|
type SiteInfo {
|
||||||
signupEnabled: Boolean!
|
signupEnabled: Boolean!
|
||||||
firstSignup: Boolean!
|
initialSetup: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizeResult {
|
type AuthorizeResult {
|
||||||
|
@ -49,18 +63,15 @@ type Subscription {
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
authorizeUser(username: String!, password: String!): AuthorizeResult!
|
authorizeUser(username: String!, password: String!): AuthorizeResult!
|
||||||
|
@neo4j_ignore
|
||||||
registerUser(username: String!, password: String!): AuthorizeResult!
|
registerUser(username: String!, password: String!): AuthorizeResult!
|
||||||
|
@neo4j_ignore
|
||||||
|
|
||||||
scanAll: ScannerResult! @isAuthenticated
|
scanAll: ScannerResult! @isAuthenticated @neo4j_ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
siteInfo: SiteInfo
|
siteInfo: SiteInfo
|
||||||
}
|
|
||||||
|
|
||||||
# type Query {
|
myAlbums: [Album] @isAuthenticated
|
||||||
# usersBySubstring(substring: String): [User]
|
}
|
||||||
# @cypher(
|
|
||||||
# statement: "MATCH (u:User) WHERE u.name CONTAINS $substring RETURN u"
|
|
||||||
# )
|
|
||||||
# }
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
const neo4j = require('neo4j-graphql-js')
|
|
|
@ -14,10 +14,10 @@ export const getUserFromToken = async function(token, driver) {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (userResult.records.length == 0) {
|
if (userResult.records.length == 0) {
|
||||||
throw new Error(`User doesn't exist anymore`)
|
throw new Error(`User was not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = userResult.records[0].toObject()
|
let user = userResult.records[0].toObject().u.properties
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"apollo-link-error": "^1.1.11",
|
"apollo-link-error": "^1.1.11",
|
||||||
"apollo-link-http": "^1.5.15",
|
"apollo-link-http": "^1.5.15",
|
||||||
"babel-plugin-styled-components": "^1.10.6",
|
"babel-plugin-styled-components": "^1.10.6",
|
||||||
|
"cookie": "^0.4.0",
|
||||||
"graphql": "^14.2.1",
|
"graphql": "^14.2.1",
|
||||||
"graphql-tag": "^2.10.1",
|
"graphql-tag": "^2.10.1",
|
||||||
"parcel-bundler": "^1.12.3",
|
"parcel-bundler": "^1.12.3",
|
||||||
|
|
|
@ -1814,6 +1814,11 @@ convert-source-map@^1.1.0, convert-source-map@^1.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.1"
|
safe-buffer "~5.1.1"
|
||||||
|
|
||||||
|
cookie@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||||
|
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||||
|
|
||||||
copy-descriptor@^0.1.0:
|
copy-descriptor@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||||
|
|
Loading…
Reference in New Issue