Skip to content

Commit

Permalink
Merge pull request #508 from FalkorDB/add-export-for-rw-and-ro-and-pr…
Browse files Browse the repository at this point in the history
…ovide-to-ro-a-wat-to-run-queries

Fix #507 #506 #484 add export for rw and ro and provide to ro a way to run queries
  • Loading branch information
AviAvni authored Oct 7, 2024
2 parents 30b4254 + 82842bb commit e52f7c0
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 39 deletions.
8 changes: 6 additions & 2 deletions app/api/graph/[graph]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,18 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s

const query = request.nextUrl.searchParams.get("query")
const create = request.nextUrl.searchParams.get("create")
const role = request.nextUrl.searchParams.get("role")

if (!query) throw new Error("Missing parameter 'query'")

if (create === "false" && (await client.list()).some((g) => g === graphId))
return NextResponse.json({}, { status: 200 })

const graph = client.selectGraph(graphId)
const result = await graph.query(query)

const result = role === "Read-Only"
? await graph.roQuery(query)
: await graph.query(query)

if (!result) throw new Error("something went wrong")

Expand Down
4 changes: 2 additions & 2 deletions app/api/user/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface CreateUser {
export const ROLE = new Map<string, string[]>(
[
["Admin", ["on", "~*", "&*", "+@all"]],
["Read-Write", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info", "+graph.delete", "+graph.query", "+graph.profile"]],
["Read-Only", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info"]]
["Read-Write", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info", "+dump", "+graph.delete", "+graph.query", "+graph.profile"]],
["Read-Only", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info", "+dump"]]
]
)
12 changes: 7 additions & 5 deletions app/components/EditorComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useEffect, useRef, useState } from "react"
import * as monaco from "monaco-editor";
import { prepareArg, securedFetch } from "@/lib/utils";
import { Maximize2 } from "lucide-react";
import { Session } from "next-auth";
import { Graph } from "../api/graph/model";
import Button from "./ui/Button";

Expand All @@ -25,6 +26,7 @@ interface Props {
runQuery: (query: string) => void
graph: Graph
isCollapsed: boolean
data: Session | null
}

const monacoOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
Expand Down Expand Up @@ -196,7 +198,7 @@ const getEmptySuggestions = (): Suggestions => ({
functions: []
})

export default function EditorComponent({ currentQuery, historyQueries, setCurrentQuery, maximize, runQuery, graph, isCollapsed }: Props) {
export default function EditorComponent({ currentQuery, historyQueries, setCurrentQuery, maximize, runQuery, graph, isCollapsed, data }: Props) {

const [query, setQuery] = useState(currentQuery)
const [monacoInstance, setMonacoInstance] = useState<Monaco>()
Expand Down Expand Up @@ -272,7 +274,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre
const addLabelsSuggestions = async (sug: monaco.languages.CompletionItem[]) => {
const labelsQuery = `CALL db.labels()`

await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(labelsQuery)}`, {
await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(labelsQuery)}&role=${data?.user.role}`, {
method: "GET"
}).then((res) => res.json()).then((json) => {
json.result.data.forEach(({ label }: { label: string }) => {
Expand All @@ -290,7 +292,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre
const addRelationshipTypesSuggestions = async (sug: monaco.languages.CompletionItem[]) => {
const relationshipTypeQuery = `CALL db.relationshipTypes()`

await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(relationshipTypeQuery)}`, {
await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(relationshipTypeQuery)}&role=${data?.user.role}`, {
method: "GET"
}).then((res) => res.json()).then((json) => {
json.result.data.forEach(({ relationshipType }: { relationshipType: string }) => {
Expand All @@ -308,7 +310,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre
const addPropertyKeysSuggestions = async (sug: monaco.languages.CompletionItem[]) => {
const propertyKeysQuery = `CALL db.propertyKeys()`

await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(propertyKeysQuery)}`, {
await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(propertyKeysQuery)}&role=${data?.user.role}`, {
method: "GET"
}).then((res) => res.json()).then((json) => {
json.result.data.forEach(({ propertyKey }: { propertyKey: string }) => {
Expand All @@ -325,7 +327,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre

const addFunctionsSuggestions = async (functions: monaco.languages.CompletionItem[]) => {
const proceduresQuery = `CALL dbms.procedures() YIELD name`
await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(proceduresQuery)}`, {
await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(proceduresQuery)}&role=${data?.user.role}`, {
method: "GET"
}).then((res) => res.json()).then((json) => {
[...json.result.data.map(({ name }: { name: string }) => name), ...FUNCTIONS].forEach((name: string) => {
Expand Down
17 changes: 11 additions & 6 deletions app/graph/GraphDataPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { ElementDataDefinition, prepareArg, securedFetch, Toast } from "@/lib/utils";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { ChevronRight, PlusCircle, Trash2 } from "lucide-react";
import { Session } from "next-auth";
import Button from "../components/ui/Button";
import Input from "../components/ui/Input";
import { Graph } from "../api/graph/model";
Expand All @@ -16,6 +17,7 @@ interface Props {
onExpand: () => void;
graph: Graph;
onDeleteElement?: () => Promise<void>;
data: Session | null;
}

const excludedProperties = new Set([
Expand All @@ -26,9 +28,10 @@ const excludedProperties = new Set([
"label",
"target",
"source",
"name",
]);

export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, graph }: Props) {
export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, graph, data }: Props) {

const [attributes, setAttributes] = useState<string[]>([]);
const [editable, setEditable] = useState<string>("");
Expand All @@ -40,7 +43,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,
const type = !("source" in obj)

useEffect(() => {
setAttributes(Object.keys(obj).filter((key) => !excludedProperties.has(key)));
setAttributes(Object.keys(obj).filter((key) => !excludedProperties.has(key) || (key === "name" && obj.name !== obj.id)));
setLabel(type ? obj.category : obj.label);
}, [obj, type]);

Expand All @@ -52,9 +55,9 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,
})).ok

if (success) {
graph.Elements.forEach(({ data }) => {
if (data.id !== id) return
data[key] = val
graph.Elements.forEach(({ data: d }) => {
if (d.id !== id) return
d[key] = val
})
setObj((prev) => ({ ...prev, [key]: val }))
setNewVal("")
Expand Down Expand Up @@ -116,7 +119,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,
</div>
<div className="w-1 grow flex gap-2">
{
editable === key ?
editable === key && data?.user.role !== "Read-Only" ?
<Input
ref={(ref) => ref?.focus()}
variant="Small"
Expand Down Expand Up @@ -202,6 +205,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,
label="Add Value"
icon={<PlusCircle />}
onClick={() => setIsAddValue(true)}
disabled={data?.user.role === "Read-Only"}
/>
</div>
</ul>
Expand All @@ -211,6 +215,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,
icon={<Trash2 />}
label="Delete"
onClick={onDeleteElement}
disabled={data?.user.role === "Read-Only"}
/>
</div>
</div>
Expand Down
9 changes: 7 additions & 2 deletions app/graph/GraphView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ImperativePanelHandle } from "react-resizable-panels";
import { ChevronLeft, Maximize2, Minimize2 } from "lucide-react"
import { cn, ElementDataDefinition, prepareArg, securedFetch } from "@/lib/utils";
import dynamic from "next/dynamic";
import { Session } from "next-auth";
import { Category, Graph } from "../api/graph/model";
import DataPanel from "./GraphDataPanel";
import Labels from "./labels";
Expand Down Expand Up @@ -95,14 +96,15 @@ function getStyle() {

const getElementId = (element: ElementDataDefinition) => element.source ? { id: element.id?.slice(1), query: "()-[e]-()" } : { id: element.id, query: "(e)" }

const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount }: {
const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, data }: {
graph: Graph
selectedElement: ElementDataDefinition | undefined
setSelectedElement: Dispatch<SetStateAction<ElementDataDefinition | undefined>>
runQuery: (query: string) => Promise<void>
historyQuery: string
historyQueries: string[]
fetchCount: () => void
data: Session | null
}, ref) => {

const [query, setQuery] = useState<string>("")
Expand Down Expand Up @@ -349,13 +351,15 @@ const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQ
historyQueries={historyQueries}
runQuery={runQuery}
setCurrentQuery={setQuery}
data={data}
/>
<div className="flex items-center justify-between">
<Toolbar
disabled={!graph.Id}
deleteDisabled={Object.values(selectedElements).length === 0 && !selectedElement}
deleteDisabled={(Object.values(selectedElements).length === 0 && !selectedElement) || data?.user.role === "Read-Only"}
onDeleteElement={handelDeleteElement}
chartRef={chartRef}
addDisabled
/>
{
isCollapsed && graph.Id &&
Expand Down Expand Up @@ -436,6 +440,7 @@ const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQ
onExpand={onExpand}
graph={graph}
onDeleteElement={handelDeleteElement}
data={data}
/>
}
</ResizablePanel>
Expand Down
8 changes: 5 additions & 3 deletions app/graph/Selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Editor } from "@monaco-editor/react";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { editor } from "monaco-editor";
import { Toast, cn, prepareArg, securedFetch } from "@/lib/utils";
import { Session } from "next-auth";
import Combobox from "../components/ui/combobox";
import { Graph, Query } from "../api/graph/model";
import UploadGraph from "../components/graph/UploadGraph";
Expand All @@ -15,7 +16,7 @@ import Duplicate from "./Duplicate";
import SchemaView from "../schema/SchemaView";
import View from "./View";

export default function Selector({ onChange, graphName, queries, runQuery, edgesCount, nodesCount, setGraph, graph }: {
export default function Selector({ onChange, graphName, queries, runQuery, edgesCount, nodesCount, setGraph, graph, data }: {
/* eslint-disable react/require-default-props */
onChange: (selectedGraphName: string) => void
graphName: string
Expand All @@ -25,6 +26,7 @@ export default function Selector({ onChange, graphName, queries, runQuery, edges
nodesCount: number
setGraph: (graph: Graph) => void
graph: Graph
data: Session | null
}) {

const [options, setOptions] = useState<string[]>([]);
Expand Down Expand Up @@ -64,7 +66,7 @@ export default function Selector({ onChange, graphName, queries, runQuery, edges
const handleOnChange = async (name: string) => {
if (runQuery) {
const q = 'MATCH (n)-[e]-(m) return n,e,m'
const result = await securedFetch(`api/graph/${prepareArg(name)}_schema/?query=${prepareArg(q)}&create=false`, {
const result = await securedFetch(`api/graph/${prepareArg(name)}_schema/?query=${prepareArg(q)}&create=false&role=${data?.user.role}`, {
method: "GET"
})

Expand Down Expand Up @@ -256,7 +258,7 @@ export default function Selector({ onChange, graphName, queries, runQuery, edges
/>
</DialogTrigger>
<DialogComponent className="h-[90%] w-[90%]" title={`${selectedValue} Schema`}>
<SchemaView schema={schema} />
<SchemaView schema={schema} data={data}/>
</DialogComponent>
</Dialog>
</div>
Expand Down
10 changes: 7 additions & 3 deletions app/graph/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useCallback, useEffect, useState } from "react";
import { Toast, prepareArg, securedFetch } from "@/lib/utils";
import { ElementDataDefinition } from "cytoscape";
import { useSession } from "next-auth/react";
import Selector from "./Selector";
import Header from "../components/Header";
import { Graph, Query } from "../api/graph/model";
Expand All @@ -17,18 +18,19 @@ export default function Page() {
const [queries, setQueries] = useState<Query[]>([])
const [historyQuery, setHistoryQuery] = useState<string>("")
const [selectedElement, setSelectedElement] = useState<ElementDataDefinition>();
const { data } = useSession()


const fetchCount = useCallback(async () => {
if (!graphName) return
const q1 = "MATCH (n) RETURN COUNT(n) as nodes"
const q2 = "MATCH ()-[e]->() RETURN COUNT(e) as edges"

const nodes = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q1}`, {
const nodes = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q1}&role=${data?.user.role}`, {
method: "GET"
})).json()

const edges = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q2}`, {
const edges = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q2}&role=${data?.user.role}`, {
method: "GET"
})).json()

Expand Down Expand Up @@ -56,7 +58,7 @@ export default function Page() {
return null
}

const result = await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(query)}`, {
const result = await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(query)}&role=${data?.user.role}`, {

method: "GET"
})
Expand Down Expand Up @@ -99,6 +101,7 @@ export default function Page() {
nodesCount={nodesCount}
setGraph={setGraph}
graph={graph}
data={data}

/>
<GraphView
Expand All @@ -109,6 +112,7 @@ export default function Page() {
historyQuery={historyQuery}
historyQueries={queries.map(({ text }) => text)}
fetchCount={fetchCount}
data={data}
/>
</div>
</div>
Expand Down
Loading

0 comments on commit e52f7c0

Please sign in to comment.