How to get tree structure that has access rights on each node

Viewed 128

I have a tree structure like a folder structure so with a project with nested project without a depth limit, each node has access rights on them.

Here is my graph:

enter image description here

Here is my query:

MATCH (a:Account {name: "bob"})-[r:VIEWER | EDITOR]->(c:Project)

MATCH (c)<-[:IS_PARENT*]-(p)
WHERE (p)<-[:VIEWER | EDITOR]-(a)

WITH TYPE(r) as relation, p, collect(distinct c) AS children

RETURN {name: p.name, Children: [c in children | {name: c.name, access:relation}]}

Here is my result:

enter image description here

And this is what I want to get:

enter image description here

My problem is that the result is split in two results, and nested child isn't nested in cohort.

An other thing that is tricky is that I don't want to get a node if I don't have a relation with it.

For example here I removed the relation between bob and cohort:

enter image description here

So I must not get cohort in my result, like this:

enter image description here

Here is my data if you want to try:

MERGE (project:Project:RootProject {name: "project test"})
MERGE (child1:Project {name: "cohort"})
MERGE (child2:Project {name: "protocol"})
MERGE (child3:Project {name: "experience"})
MERGE (child4:Project {name: "nested child"})

MERGE (project)-[:IS_PARENT]->(child1)
MERGE (project)-[:IS_PARENT]->(child2)
MERGE (project)-[:IS_PARENT]->(child3)
MERGE (child1)-[:IS_PARENT]->(child4)

MERGE (bob:Account {name: "bob"})
 MERGE (bob)-[:EDITOR]->(child4)
 MERGE (bob)-[:EDITOR]->(child2)
 MERGE (bob)-[:VIEWER]->(child3)
MERGE (bob)-[:VIEWER]->(child1)
 MERGE (bob)-[:VIEWER]->(project)

I have tried a lot of things but I never get a good result.

2 Answers

Here is my answer. The main thing is to construct the json object as parent then grandparent projects to the root project rather than mixing both (line 10). Notice that I removed getting VIEWER or EDITOR relationship since removing them is also faster.

//Get the root project from bob
MATCH (a:Account {name: "bob"})-[r]->(root:RootProject)
WITH a, root, {name:root.name, access:type(r)} as rootProject
//Get only those projects without nested parent 
MATCH (a)-[r]->(p:Project) WHERE EXISTS((p)-[:IS_PARENT]-(root))
WITH a, rootProject, p, type(r) as relations
//Get those projects with another parent (or grand parent of root project)
OPTIONAL MATCH (p)-[:IS_PARENT]->(gp:Project)<-[r]-(a)
//Collect the children and grandchildren
WITH rootProject, collect({children: {name: p.name, access:relations}, grandchild:(case when gp is null then [] else [{name:gp.name, access:type(r)}] end)}) as allChildren
RETURN {name: rootProject.name, access: rootProject.access, children: [c in allChildren]} as projects

It may not meet your expectation, but how about this for scalability?

MATCH (a:Account {name: "bob"})-[r:VIEWER|EDITOR]->(c:Project)

MATCH path=(p)-[:IS_PARENT*]->(c)
WHERE (p)<-[:VIEWER | EDITOR]-(a)

WITH COLLECT(path) AS paths
CALL apoc.convert.toTree(paths, false, {
  nodes: {Project: ['name']},
  rels:  {IS_PARENT: ['name']}
}) YIELD value
RETURN value
Related