diff --git a/sg-backend/.gitignore b/sg-backend/.gitignore index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d268899c834ef02ce9194e04741764f1548a7e8 100644 --- a/sg-backend/.gitignore +++ b/sg-backend/.gitignore @@ -0,0 +1 @@ +__pycache/ \ No newline at end of file diff --git a/sg-backend/.idea/.gitignore b/sg-backend/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..13566b81b018ad684f3a35fee301741b2734c8f4 --- /dev/null +++ b/sg-backend/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/sg-backend/.idea/discord.xml b/sg-backend/.idea/discord.xml new file mode 100644 index 0000000000000000000000000000000000000000..d8e9561668720df93b3f31794ece7217fb8ff4b0 --- /dev/null +++ b/sg-backend/.idea/discord.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="DiscordProjectSettings"> + <option name="show" value="PROJECT_FILES" /> + <option name="description" value="" /> + </component> +</project> \ No newline at end of file diff --git a/sg-backend/.idea/inspectionProfiles/Project_Default.xml b/sg-backend/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000000000000000000000000000000000..9d56344ac99ce720772ce70790d2c6472631a702 --- /dev/null +++ b/sg-backend/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ +<component name="InspectionProjectProfileManager"> + <profile version="1.0"> + <option name="myName" value="Project Default" /> + <inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoredIdentifiers"> + <list> + <option value="ratelimit.decorators.ratelimit.ALL" /> + </list> + </option> + </inspection_tool> + </profile> +</component> \ No newline at end of file diff --git a/sg-backend/.idea/inspectionProfiles/profiles_settings.xml b/sg-backend/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/sg-backend/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ +<component name="InspectionProjectProfileManager"> + <settings> + <option name="USE_PROJECT_PROFILE" value="false" /> + <version value="1.0" /> + </settings> +</component> \ No newline at end of file diff --git a/sg-backend/.idea/misc.xml b/sg-backend/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..cd8c9b64aefa6a6bbe91995a088b6c17e0061c25 --- /dev/null +++ b/sg-backend/.idea/misc.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectRootManager" version="2" project-jdk-name="Poetry (sg-backend)" project-jdk-type="Python SDK" /> +</project> \ No newline at end of file diff --git a/sg-backend/.idea/modules.xml b/sg-backend/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..be6a241a5a06d33356a000d2c53544ddc01dde3e --- /dev/null +++ b/sg-backend/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/sg-backend.iml" filepath="$PROJECT_DIR$/.idea/sg-backend.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/sg-backend/.idea/sg-backend.iml b/sg-backend/.idea/sg-backend.iml new file mode 100644 index 0000000000000000000000000000000000000000..d0876a78d06ac03b5d78c8dcdb95570281c6f1d6 --- /dev/null +++ b/sg-backend/.idea/sg-backend.iml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="PYTHON_MODULE" version="4"> + <component name="NewModuleRootManager"> + <content url="file://$MODULE_DIR$" /> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/sg-backend/.idea/vcs.xml b/sg-backend/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..6c0b8635858dc7ad44b93df54b762707ce49eefc --- /dev/null +++ b/sg-backend/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/sg-backend/db.py b/sg-backend/db.py index 1193b6986e44c961c5cf417f60d6144a3dbf8066..51d91b329326c288ce3e8d6a043d669ed95a2f39 100644 --- a/sg-backend/db.py +++ b/sg-backend/db.py @@ -1,7 +1,7 @@ from neo4j import GraphDatabase -class HelloWorldExample: +class Neo4j: def __init__(self, uri, user, password): self.driver = GraphDatabase.driver(uri, auth=(user, password)) @@ -19,4 +19,32 @@ class HelloWorldExample: result = tx.run("CREATE (a:Greeting) " "SET a.message = $message " "RETURN a.message + ', from node ' + id(a)", message=message) - return result.single()[0] \ No newline at end of file + return result.single()[0] + + def get_graph_data(self): + with self.driver.session() as session: + # results = session.run("MATCH (n)-[r]->(m) RETURN n,r,m") + results = session.run("""CALL apoc.export.json.all(null,{useTypes:true, stream: true, jsonFormat: "JSON"}) + YIELD data + RETURN data""") + return results.data() + + def get_softwares(self): + with self.driver.session() as session: + results = session.run("MATCH (n:Software) RETURN n") + return results.data() + + def get_softwares_shortest_path(self, search): + with self.driver.session() as session: + # results = session.run("""MATCH (n:Software)-[r:DEPENDS_ON*]-(m:Software) + # WHERE n.name = $software1 AND m.name = $software2 + # RETURN n,r,m""", software1=SoftwareSearch.software1, + # software2=SoftwareSearch.software2) + results = session.run("""MATCH (S1:Software {name: $software1}), (S2:Software {name: $software2}), + p = shortestPath((S1)-[*]-(S2)) + RETURN p""", + software1=search.software1, + software2=search.software2) + data = results.data()[0]['p'] + return [node for node in data if + type(node) is dict] # [node._properties.get('name') for node in results.graph().nodes] \ No newline at end of file diff --git a/sg-backend/main.py b/sg-backend/main.py index caa72e7ed8d7f75d41fb881630485953076ac004..0964d406fecd6e0643456bfbea81307d5bc8947f 100644 --- a/sg-backend/main.py +++ b/sg-backend/main.py @@ -1,7 +1,33 @@ +import json +import os + from fastapi import FastAPI +from pydantic import BaseModel +from starlette.middleware.cors import CORSMiddleware + +from db import Neo4j + + +class SoftwareSearch(BaseModel): + software1: str + software2: str + +driver = Neo4j(os.environ.get("NEO4J_URI"), os.environ.get("NEO4J_USER"), os.environ.get("NEO4J_PASSWORD")) app = FastAPI() +origins = [ + os.getenv("FRONTEND_URL"), +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + @app.get("/") async def root(): @@ -10,4 +36,23 @@ async def root(): @app.get("/hello/{name}") async def say_hello(name: str): - return {"message": f"Hello {name}"} \ No newline at end of file + return {"message": f"Hello {name}"} + + +@app.get("/graph") +async def graph(): + results = driver.get_graph_data() + return json.loads(results[0]['data']) + + +@app.get("/software") +async def software(): + results = driver.get_softwares() + return {"softwares": json.loads(json.dumps(results))} + + +@app.post("/software-search") +async def software_search(search: SoftwareSearch): + print(search.software1, search.software2) + res = driver.get_softwares_shortest_path(search) + return {"softwares": res} \ No newline at end of file diff --git a/sg-backend/poetry.lock b/sg-backend/poetry.lock index 89de34e3886a008796f34c60224c29ce3ea5851b..85f00f4412394e4603a2f3a828e2e73b85718b59 100644 --- a/sg-backend/poetry.lock +++ b/sg-backend/poetry.lock @@ -68,6 +68,17 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "neo4j" +version = "5.0.1" +description = "Neo4j Bolt driver for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytz = "*" + [[package]] name = "pydantic" version = "1.10.2" @@ -83,6 +94,14 @@ typing-extensions = ">=4.1.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pytz" +version = "2022.2.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "sniffio" version = "1.3.0" @@ -131,7 +150,7 @@ standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "788d09d1be0c296c1d42382d6b13643f667a655479b71b3fb4c34cb721056840" +content-hash = "92d7313563fc036bb36381d94d139862865b3a018e3849a340b8bc16513df739" [metadata.files] anyio = [ @@ -158,6 +177,9 @@ idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +neo4j = [ + {file = "neo4j-5.0.1.tar.gz", hash = "sha256:2330d1b8295b6afb39f23a001f5b0aecae6ca5895cc5b7af3413e326bbd1979c"}, +] pydantic = [ {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, @@ -196,6 +218,10 @@ pydantic = [ {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, ] +pytz = [ + {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, + {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, +] sniffio = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, diff --git a/sg-backend/pyproject.toml b/sg-backend/pyproject.toml index 6e872c7bc9da7db684590ac8b9630567250b8ba9..b387455006abe661b14c57c9d8de62297cb55c8f 100644 --- a/sg-backend/pyproject.toml +++ b/sg-backend/pyproject.toml @@ -4,14 +4,15 @@ version = "0.1.0" description = "" authors = ["Guillaume Schurck <g.schurck@gmail.com>"] readme = "README.md" -packages = [{include = "sg_backend"}] +packages = [] [tool.poetry.dependencies] python = "^3.10" fastapi = "^0.85.0" uvicorn = "^0.18.3" +neo4j = "^5.0.1" [build-system] requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/sg-backend/test_main.http b/sg-backend/test_main.http index c7ccc0f77dce8d7979c4f14427dff4954cd01acc..7a5db99e8b1627dbe891974940a33b39a4e3cc72 100644 --- a/sg-backend/test_main.http +++ b/sg-backend/test_main.http @@ -2,8 +2,13 @@ GET http://127.0.0.1:8000/ Accept: application/json + ### GET http://127.0.0.1:8000/hello/User Accept: application/json -### \ No newline at end of file + +### + +GET http://127.0.0.1:8000/graph +Accept: application/json \ No newline at end of file