Skip to content

Commit ea5d04a

Browse files
committed
Working MVP; no signup UI just the features
1 parent cc88c4e commit ea5d04a

20 files changed

+457
-231
lines changed

.vscode/tasks.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
],
1616
"problemMatcher": []
1717
},
18+
{
19+
"label": "Build React APP",
20+
"type": "shell",
21+
"command": "npm",
22+
"args": [
23+
"run",
24+
"build"
25+
],
26+
"problemMatcher": []
27+
},
1828
{
1929
"label": "Node & Edge Type Seeder",
2030
"type": "shell",

desktop/gui.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ def splash_close(window):
1616
webview.start(splash_close, (splash_screen,), http_server=False)
1717

1818
# Open main window
19-
webview.create_window('Flow Control', 'http://localhost:1212/')
19+
# webview.create_window('Flow Control', 'http://localhost:1212/')
20+
webview.create_window('Flow Control', html='./index.html')
2021
webview.start()

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"devDependencies": {
2626
"@types/react": "^18.2.53",
2727
"@types/react-dom": "^18.2.18",
28+
"@types/uuid": "^10.0.0",
2829
"@typescript-eslint/eslint-plugin": "^6.20.0",
2930
"@typescript-eslint/parser": "^6.20.0",
3031
"@vitejs/plugin-react": "^4.2.1",

server/db/models.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sqlalchemy as sql
22
from sqlalchemy.sql import text
3+
from secrets import token_urlsafe
34
from datetime import datetime, timezone
45
from sqlalchemy import create_engine, MetaData, select, String, JSON, DateTime, ForeignKey, Integer, Text, inspect, update
56
from sqlalchemy.orm import Session, DeclarativeBase, mapped_column, validates, relationship, Mapped, sessionmaker, reconstructor
@@ -11,9 +12,11 @@
1112
class Workspace(Base):
1213
__tablename__ = 'workspaces'
1314
id = sql.Column(sql.Integer, primary_key=True, autoincrement=True)
15+
slug = sql.Column(sql.String, nullable=False, unique=True, default=token_urlsafe(6))
1416
name = sql.Column(sql.String, nullable=False)
1517
description = sql.Column(sql.Text, nullable=True)
1618
icon = sql.Column(sql.String, nullable=True)
19+
default_flow_id = sql.Column(sql.Integer, nullable=True)
1720
created_at = sql.Column(sql.DateTime, default=datetime.now(timezone.utc))
1821
updated_at = sql.Column(sql.DateTime, default=datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
1922

@@ -27,12 +30,19 @@ def validate_name(self, key, name):
2730
return name
2831

2932
def __repr__(self):
30-
return f'<Workspace {self.id} {self.name} {self.description} {bool(self.icon)} {self.created_at} {self.updated_at}>'
33+
return f'<Workspace {self.id} {self.slug} {self.name} {self.description} {bool(self.icon)} {self.default_flow_id} {self.created_at} {self.updated_at}>'
34+
35+
@property
36+
def default(self):
37+
if self.default_flow_id:
38+
return session.query(Flow).filter_by(id = self.default_flow_id).first()
39+
return session.query(Flow).filter_by(default = True).where(Flow.workspace_id == self.id).first()
3140

3241

3342
class Flow(Base):
3443
__tablename__ = 'flows'
3544
id = sql.Column(sql.Integer, primary_key=True, autoincrement=True)
45+
slug = sql.Column(sql.String, nullable=False, unique=True, default=token_urlsafe(6))
3646
name = sql.Column(sql.String, nullable=False)
3747
description = sql.Column(sql.Text, nullable=True)
3848
default = sql.Column(sql.Boolean, default=False)
@@ -168,10 +178,13 @@ def __repr__(self):
168178
session = Session()
169179

170180
# Schema updates
181+
# schema_update_handler(session.execute, text('ALTER TABLE flows ADD COLUMN slug VARCHAR;'))
182+
# schema_update_handler(session.execute, text('ALTER TABLE flows ADD COLUMN position JSON;'))
171183
# schema_update_handler(session.execute, text('ALTER TABLE workspaces ADD COLUMN icon TEXT;'))
172-
# schema_update_handler(session.execute, text('ALTER TABLE node_types ADD COLUMN fields JSON;'))
173184
# schema_update_handler(session.execute, text('ALTER TABLE edge_types ADD COLUMN fields JSON;'))
174-
# schema_update_handler(session.execute, text('ALTER TABLE flows ADD COLUMN position JSON;'))
185+
# schema_update_handler(session.execute, text('ALTER TABLE node_types ADD COLUMN fields JSON;'))
186+
# schema_update_handler(session.execute, text('ALTER TABLE workspaces ADD COLUMN slug VARCHAR;'))
187+
# schema_update_handler(session.execute, text('ALTER TABLE workspaces ADD COLUMN default_flow_id INTEGER;'))
175188

176189
# Was to try to implement automatic schema updates
177190
metadata = MetaData()

server/db/seed_types.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ def seed() -> bool:
1515
NodeType(slug='source', name='Source', description='A card node with 4 source handles no targets.', fields={'title': 'text', 'body': 'textarea'}),
1616
NodeType(slug='card', name='Card', description='Header and body node with 1 source and 1 target handle.', fields={'title': 'text', 'body': 'textarea'}),
1717
NodeType(slug='default', name='Default', description='A simple node with 1 target and source handle each.', fields={'label': 'textarea'}), # Built-in node type
18+
NodeType(slug='subflow', name='SubFlow', description='A SubFlow node used to denote the entry point to a subflow.', fields={'flow': 'text', 'title': 'text', 'body': 'textarea'}), # Built-in node type
1819
]
19-
try:
20-
for node_type in node_types:
20+
for node_type in node_types:
21+
try:
22+
print(node_type)
2123
session.add(node_type)
2224
session.commit()
2325
new_nodes += 1
24-
except:
25-
pass
26+
except Exception as e:
27+
session.rollback()
28+
print(e)
2629

2730
# Create the edge types
2831
edge_types = [
@@ -32,13 +35,14 @@ def seed() -> bool:
3235
EdgeType(slug='smoothstep', name='Smooth Step', description='the bends in this step edge are rounded.'), # Built-in edge type
3336
]
3437

35-
try:
36-
for edge_type in edge_types:
38+
for edge_type in edge_types:
39+
try:
3740
session.add(edge_type)
3841
session.commit()
3942
new_edges += 1
40-
except:
41-
pass
43+
except:
44+
session.rollback()
45+
pass
4246

4347
# Finished database seeding
4448
print(f'...database seeded successfully. {new_nodes} new nodes and {new_edges} new edges added.')

server/gql/functions.gql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ type Query {
1717
getWorkspaces: WorkspaceResults!
1818

1919
# Flow
20-
getFlow(id: ID!): FlowResult!
21-
getFlows(workspace_id: ID): FlowResults!
2220
getDefaultFlow(workspace_id: ID): FlowResult!
21+
getFlow(slug: String, workspace_id: ID!): FlowResult!
22+
getFlows(workspace_id: ID, slugs: [String]): FlowResults!
2323

2424
# Node Type
2525
getNodeType(id: ID!): NodeTypeResult!

server/gql/schema.gql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ type Status {
1111
# Worksapce Type
1212
type Worksapce {
1313
id: ID!
14+
slug: String!
1415
name: String!
1516
description: String
1617
icon: String
18+
default: Flow
1719
created_at: String!
1820
updated_at: String!
1921
}
@@ -35,6 +37,7 @@ type WorkspaceResults {
3537
# Flow Type
3638
type Flow {
3739
id: ID!
40+
slug: String!
3841
name: String!
3942
description: String
4043
default: Boolean!

server/resolvers.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def getWorkspace(_, info, **kwargs):
3030

3131
@response_handler
3232
def getWorkspaces(_, info, **kwargs):
33-
worksapces = session.query(Workspace).all()
34-
return {'workspaces': worksapces}
33+
workspaces = session.query(Workspace).all()
34+
return {'workspaces': workspaces}
3535

3636
@response_handler
3737
def createWorkspace(_, info, **kwargs):
@@ -44,6 +44,7 @@ def createWorkspace(_, info, **kwargs):
4444
# Create the default main flow for the workspace
4545
flow = Flow(name='Main', workspace=workspace, description='The main flow for this workspace.', default=True)
4646
session.add(flow)
47+
workspace.default = flow
4748
session.commit()
4849

4950
# Return the new workspace
@@ -67,12 +68,17 @@ def deleteWorkspace(_, info, **kwargs):
6768
# Flow Resolvers
6869
@response_handler
6970
def getFlow(_, info, **kwargs):
70-
flow = session.query(Flow).filter_by(id=kwargs.get('id')).first()
71+
flow = session.query(Flow).filter_by(slug=kwargs.get('slug'), workspace_id=kwargs.get('workspace_id')).first()
7172
return {'flow': flow}
7273

7374
@response_handler
7475
def getFlows(_, info, **kwargs):
75-
flows = session.query(Flow).filter_by(workspace_id=kwargs.get('workspace_id')).all()
76+
if workspace_id := kwargs.get('workspace_id', None):
77+
flows = session.query(Flow).filter_by(workspace_id=workspace_id).all()
78+
elif slugs := kwargs.get('slugs', None):
79+
flows = session.query(Flow).filter(Flow.slug.in_(slugs)).all()
80+
else:
81+
raise Exception('Invalid query parameters.')
7682
return {'flows': flows}
7783

7884
@response_handler
@@ -124,10 +130,18 @@ def getNodes(_, info, **kwargs):
124130

125131
@response_handler
126132
def createNode(_, info, **kwargs):
133+
# Create a new node model
127134
node = Node(**kwargs)
128135
node.node = unquote(kwargs.get('node', '{}'))
129136
session.add(node)
130137
session.commit()
138+
139+
# If the node is a subflow, create a new flow for it
140+
if node.node_type.slug == 'subflow':
141+
flow = Flow(name=loads(node.node)['data'].get('flow', 'SubFlow'), workspace=node.workspace, description=loads(node.node)['data'].get('body', 'A subflow.'), default=False, slug=loads(node.node)['data']['slug'])
142+
session.add(flow)
143+
session.commit()
144+
131145
updateFlowandWorksapceTS(node)
132146
session.commit()
133147
return {'node': node}

server/server.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1+
# import sys
2+
# sys.path.append('./dist')
3+
14
import json
25
import asyncio
36
import logging
47
import aiohttp
58
from aiohttp import web, ClientSession
6-
from ariadne import explorer, graphql_sync, ObjectType, MutationType, gql, load_schema_from_path, snake_case_fallback_resolvers, make_executable_schema
9+
from ariadne import graphql_sync, ObjectType, MutationType, gql, load_schema_from_path, snake_case_fallback_resolvers, make_executable_schema
10+
11+
if __name__ == '__main__':
12+
from ariadne import explorer
713

814
from server.resolvers import *
915

1016

1117
# Aiohttp webserver setup
1218
routes = web.RouteTableDef()
13-
explorer_html = explorer.ExplorerGraphiQL().html(None)
19+
if __name__ == '__main__':
20+
explorer_html = explorer.ExplorerGraphiQL().html(None)
21+
1422
HEADERS = {
1523
"Access-Control-Allow-Origin": "*",
1624
"Access-Control-Allow-Headers": "*",
@@ -73,7 +81,10 @@
7381

7482

7583
# Build schema
76-
type_defs = gql(load_schema_from_path("server/gql/"))
84+
if __name__ == '__main__' or __name__ == 'server.server':
85+
type_defs = gql(load_schema_from_path("server/gql/"))
86+
else:
87+
type_defs = gql(load_schema_from_path("gql/"))
7788
schema = make_executable_schema(
7889
type_defs, query, mutation, snake_case_fallback_resolvers
7990
)
@@ -102,27 +113,38 @@ async def graphql_api(request: web.Request) -> web.Response:
102113
async def graphql_api_options(_: web.Request) -> web.Response:
103114
return web.json_response({"message": "Accept all hosts"}, headers=HEADERS)
104115

105-
@routes.route("*", "/{tail:.*}")
106-
async def proxy(request: web.Request) -> web.Response:
107-
async with ClientSession() as session:
108-
async with session.request(
109-
method=request.method,
110-
url=f"http://localhost:5173{request.path_qs}",
111-
headers={key: value for key, value in request.headers.items()},
112-
data=await request.read(),
113-
) as response:
114-
return web.Response(
115-
body=await response.read(),
116-
status=response.status,
117-
headers={key: value for key, value in response.headers.items()},
118-
)
116+
# @routes.route("*", "/{tail:.*}")
117+
# async def proxy(request: web.Request) -> web.Response:
118+
# async with ClientSession() as session:
119+
# async with session.request(
120+
# method=request.method,
121+
# url=f"http://localhost:5173{request.path_qs}",
122+
# headers={key: value for key, value in request.headers.items()},
123+
# data=await request.read(),
124+
# ) as response:
125+
# return web.Response(
126+
# body=await response.read(),
127+
# status=response.status,
128+
# headers={key: value for key, value in response.headers.items()},
129+
# )
130+
131+
# Server index.html file
132+
@routes.get('/')
133+
async def index(request: web.Request):
134+
return web.FileResponse('index.html')
119135

120136
# Web App initialization
121137
app = web.Application()
122138
app.add_routes(routes)
123139

140+
def run_app():
141+
logging.basicConfig(level=logging.WARNING)
142+
asyncio.get_event_loop().set_debug(enabled=True)
143+
web.run_app(app, host='localhost', port=1212, shutdown_timeout=1)
144+
124145
# Run the app
125-
if __name__ == '__main__':
146+
print(__name__)
147+
if __name__ == '__main__' or __name__ == 'server.server':
126148
logging.basicConfig(level=logging.DEBUG)
127149
asyncio.get_event_loop().set_debug(enabled=True)
128150
web.run_app(app, host='localhost', port=1212, shutdown_timeout=1)

0 commit comments

Comments
 (0)