Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/backend_build_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Check Rust Backend Build

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
check:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Install Rust
uses: actions-rs/toolchain@v1

Check warning on line 17 in .github/workflows/backend_build_check.yml

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.github/workflows/backend_build_check.yml#L17

An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release.
with:
toolchain: stable
override: true
components: rustfmt, clippy

- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}

- name: Cache cargo index
uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}

# - name: Cache cargo build
# uses: actions/cache@v3
# with:
# path: meshwork/backend_rs/target
# key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

- name: Run cargo build
working-directory: meshwork/backend_rs
run: cargo build
38 changes: 0 additions & 38 deletions .github/workflows/build_test_react.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ wheels/
.zed
.ruff_cache
.ropeproject

# Rust
target
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
- [x] frontend and backend API connection
- [x] node connection
- [x] task editing
- [ ] task layout
- [x] task layout
- [ ] task graph creation (multigraph support/auth)
- [ ] move to neo4j
15 changes: 14 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: meshwork
services:
backend:
build: ./meshwork/backend
build: ./meshwork/backend_rs
ports:
- "8000:8000"
frontend:
Expand All @@ -10,6 +10,19 @@ services:
- "3000:3000"
depends_on:
- backend
neo4j:
image: neo4j:latest
volumes:
- /$HOME/neo4j/logs:/logs
- /$HOME/neo4j/config:/config
- /$HOME/neo4j/data:/data
- /$HOME/neo4j/plugins:/plugins
environment:
- NEO4J_AUTH=neo4j/your_password
ports:
- "7474:7474"
- "7687:7687"
restart: always

networks:
default:
52 changes: 42 additions & 10 deletions meshwork/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,48 @@
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def setup_examples() -> tuple[TaskGraph, User]:
"""Initialize example tasks and users"""
example_graph = TaskGraph()
example_graph.id = "abc123"
user = User(id=123, name="John Doe", email="john@example.com", graphs=[example_graph.id])
task_1 = Task(name="Create pretty task nodes", description="Learn UI and do it!", status=Status.TODO, tags=["UI", "UX"], users=["Andrew"])
user = User(
id=123, name="John Doe", email="john@example.com", graphs=[example_graph.id]
)
task_1 = Task(
name="Create pretty task nodes",
description="Learn UI and do it!",
status=Status.TODO,
tags=["UI", "UX"],
users=["Andrew"],
)
logger.info(f"Created task {task_1.id}")
task_2 = Task(name="Add CI/CD support", description="Things to test your Svelte", status=Status.TODO, tags=["CI/CD"], users=["Andrew"])
task_2 = Task(
name="Add CI/CD support",
description="Things to test your Svelte",
status=Status.TODO,
tags=["CI/CD"],
users=["Andrew"],
)
logger.info(f"Created task {task_2.id}")
task_3 = Task(name="Release v0.1", description="Hooray!", status=Status.TODO, tags=["v0.1"], depends_on=[task_1.id, task_2.id])
task_3 = Task(
name="Release v0.1",
description="Hooray!",
status=Status.TODO,
tags=["v0.1"],
depends_on=[task_1.id, task_2.id],
)
logger.info(f"Created task {task_3.id}")
example_graph.add_task(task_1)
example_graph.add_task(task_2)
example_graph.add_task(task_3)
return example_graph, user


app = FastAPI()
TG, user = setup_examples()
logger.info(f"Initialized graph {TG.id}")
task_graphs = {
TG.id: TG
}
task_graphs = {TG.id: TG}

# Configure CORS
app.add_middleware(
Expand All @@ -43,10 +63,12 @@ def setup_examples() -> tuple[TaskGraph, User]:
allow_headers=["*"],
)


class ConnectNodesRequest(BaseModel):
node_dependency: str
node_dependee: str


@app.get("/")
async def root():
return {"message": "Hello World"}
Expand All @@ -63,6 +85,7 @@ async def create_graph():
task_graphs[new_graph.id] = new_graph
return {"ID": new_graph.id}


@app.post("/v0/{graph_id}/task/add/")
async def add_task(graph_id: str, task: Task):
task_graphs[graph_id].add_task(task)
Expand All @@ -73,6 +96,7 @@ async def add_task(graph_id: str, task: Task):
async def get_all_tasks(graph_id: str) -> list[Task]:
return task_graphs[graph_id].get_all_tasks()


@app.get("/v0/{graph_id}/task/{task_id}")
async def get_task(graph_id: str, task_id: str) -> Task:
return task_graphs[graph_id].get_task(task_id)
Expand All @@ -89,12 +113,20 @@ async def edit_task(graph_id: str, task_id: str, new_task: Task):
task_graphs[graph_id].edit_task(task_id, new_task)
return {"message": f"Task {task_id} edited"}


@app.post("/v0/{graph_id}/connect_nodes/")
async def connect_nodes(graph_id: str, request: ConnectNodesRequest):
task_graphs[graph_id].connect_nodes(request.node_dependency, request.node_dependee)
return {"message": f"Nodes {request.node_dependency} and {request.node_dependee} connected"}
return {
"message": f"Nodes {request.node_dependency} and {request.node_dependee} connected"
}


@app.post("/v0/{graph_id}/disconnect_nodes/")
async def disconnect_nodes(graph_id: str, request: ConnectNodesRequest):
task_graphs[graph_id].disconnect_nodes(request.node_dependency, request.node_dependee)
return {"message": f"Nodes {request.node_dependency} and {request.node_dependee} disconnected"}
task_graphs[graph_id].disconnect_nodes(
request.node_dependency, request.node_dependee
)
return {
"message": f"Nodes {request.node_dependency} and {request.node_dependee} disconnected"
}
87 changes: 87 additions & 0 deletions meshwork/backend/core/db_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import os
import logging
from typing import Optional, List, Dict, Any

Check warning on line 3 in meshwork/backend/core/db_handler.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

meshwork/backend/core/db_handler.py#L3

'typing.Optional' imported but unused (F401)
from contextlib import contextmanager

from dotenv import load_dotenv
from neo4j import GraphDatabase, Driver, Session

Check warning on line 7 in meshwork/backend/core/db_handler.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

meshwork/backend/core/db_handler.py#L7

'neo4j.Driver' imported but unused (F401)
from core.task import Task, Status

Check warning on line 8 in meshwork/backend/core/db_handler.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

meshwork/backend/core/db_handler.py#L8

'core.task.Task' imported but unused (F401)
from core.user import User

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class Neo4jHandler:
def __init__(self) -> None:
load_dotenv()
self.uri = os.getenv("NEO4J_URI")
self.auth = (os.getenv("NEO4J_USERNAME"), os.getenv("NEO4J_PASSWORD"))

def connect(self):
self.driver = GraphDatabase.driver(self.uri, auth=self.auth)
self.driver.verify_connectivity()
logger.info("Connected to Neo4j backend.")

def close(self):
if self.driver:
self.driver.close()
logger.info("Disconnected from Neo4j backend.")

def create_example(self):
pass

@contextmanager
def session(self):
with self.driver.session() as session:
yield session

def create_user(self, user: User) -> str:
with self.session() as session:
try:
result = session.run(
"""
CREATE (u:User {id: $id, name: $name, email: $email, created_at: datetime()}) RETURN u.id
""",
id=user.id,
name=user.name,
email=user.email,
)
return result.single()["u.id"]
except Exception as e:
logger.error(f"Failed to create user: {e}")
raise

def create_task_graph(self, user_id: str, graph_name: str) -> str:
with self.session() as session:
try:
result = session.run(
"""
MATCH (u:User {id: $user_id})
CREATE (tg:TaskGraph {id: randomUUID(), name: $graph_name, created_at: datetime()})
CREATE (u)-[:OWNS]->(tg)
CREATE (u)-[:PART_OF]->(tg)
RETURN tg.id
""",
user_id=user_id,
graph_name=graph_name,
)
return result.single()["tg.id"]
except Exception as e:
logger.error(f"Failed to create task graph: {e}")
raise

def get_user_graphs(self, user_id: str) -> List[Dict[str, Any]]:
with self.session() as session:
try:
result = session.run(
"""
MATCH (u:User {id: $user_id})-[:PART_OF]->(tg:TaskGraph)
RETURN tg.id as id, tg.name as name, tg.created_at as created_at
""",
user_id=user_id,
)
return [record.data() for record in result]
except Exception as e:
logger.error(f"Failed to get user graphs: {e}")
raise
Loading
Loading