Skip to content

Commit

Permalink
Email login (#69)
Browse files Browse the repository at this point in the history
* wip-controller

* fixed imports

* change assistant handling
  • Loading branch information
amirrr authored Jul 5, 2024
1 parent b09c89d commit 50ba9dd
Show file tree
Hide file tree
Showing 19 changed files with 304 additions and 301 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@
},
"[dockerfile]": {
"editor.defaultFormatter": "ms-azuretools.vscode-docker"
}
},
"python.autoComplete.extraPaths": ["./server"],
"python.analysis.extraPaths": ["./server"]
}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ ENV FLASK_ENV production

EXPOSE 80
WORKDIR /app/api
CMD ["gunicorn", "--worker-class", "eventlet", "-w", "1", "-b", ":80", "api:app"]
CMD ["gunicorn", "--worker-class", "eventlet", "-w", "10", "-b", ":80", "api:app"]
271 changes: 12 additions & 259 deletions server/api.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
""" This module contains the Flask RESTful API endpoint for the workflow editor. """

import importlib
import os
import argparse
import json

from dotenv import load_dotenv

from flask import Flask, request, jsonify, make_response
from flask_restful import Resource, Api

from flask import Flask, request
from flask_jwt_extended import JWTManager
from flask_restful import Api
from flask_socketio import SocketIO
from flask_cors import CORS
from flask_jwt_extended import (
JWTManager,
create_access_token,
get_jwt_identity,
jwt_required,
)

# pylint: disable=import-error
from assistant import AssistantException, call_asssistant_api
from db.db import DatabaseInterface
from resources.user import User

users = User()
from controllers.assisstant import RunAssistant
from controllers.features import GetFeatures
from controllers.login import Login


load_dotenv()

Expand All @@ -41,250 +33,10 @@
jwt = JWTManager(app)


UPLOAD_DIRECTORY = "paper/"

if not os.path.exists(UPLOAD_DIRECTORY):
os.makedirs(UPLOAD_DIRECTORY)


db = DatabaseInterface()


class GetFeatures(Resource):
"""
Represents a resource for the HelloWorld endpoint.
This resource provides GET and POST methods for the HelloWorld endpoint.
"""

@jwt_required()
def get(self):
"""
Handles GET requests to the features endpoint.
Returns:
A Flask response object with a JSON representation of the response data.
"""
features_dir = "features"
py_files = []

for root, dirs, files in os.walk(features_dir):
# Skip __pycache__ directories
if "__pycache__" in dirs:
dirs.remove("__pycache__")

for file in files:
if (
file.endswith(".py")
and file != "__init__.py"
and file != "gpt_feature.py"
):
file_path = os.path.normpath(os.path.join(root, file))
relative_path = os.path.relpath(file_path, features_dir)

# Skip files with the same name as their parent directory
parent_dir = os.path.basename(os.path.dirname(file_path))
if os.path.splitext(file)[0] == parent_dir:
continue

# Replace '/' with '.'
formatted_path = relative_path.replace(os.path.sep, ".")
formatted_path = formatted_path.replace(".py", "")

py_files.append(formatted_path)

response_data = {"features": py_files}
response = make_response(jsonify(response_data))
response.status_code = 200
return response

@jwt_required()
def post(self):
"""
Handles POST requests to the HelloWorld endpoint.
Parses the request JSON data and prints the nodes and edges.
Returns:
A Flask response object with a JSON representation of the response data.
"""
user_id = get_jwt_identity()
print(f"User ID: {user_id}")
data = request.get_json()
return jsonify(data)


def load_feature(module_path):
"""Dynamically loads a feature class from a module path."""
module = importlib.import_module(module_path)
return module.Feature


class RunFeatures(Resource):
"""
Represents a resource for running the features.
This resource provides GET method for running and collecting feature prompts.
"""

def post(self):
"""
Handles GET requests to run features.
Dynamically loads the features and collects their prompt attributes.
Returns:
A Flask response object with a JSON representation of the prompts.
"""
try:
data = request.get_json()
except json.JSONDecodeError:
response_data = {"error": "Invalid JSON data."}
response = make_response(jsonify(response_data))
response.status_code = 400
return response

try:
features = data["features"]
if not features:
raise KeyError
except KeyError:
response_data = {"error": "No features provided."}
response = make_response(jsonify(response_data))
response.status_code = 400
return response
# Assuming `features` is obtained from the GetFeatures endpoint,
# this is a mock to demonstrate
features = [
"features.condition.condition",
"features.condition.condition_name",
"features.condition.condition_description",
"features.condition.condition_type",
"features.condition.condition_message",
]

prompts = []
for idx, feature_path in enumerate(features):
feature_class = load_feature(feature_path)
feature_instance = feature_class()
# Assuming your class has an attribute or method to get the prompt
prompts.append({"id": idx, "prompt": feature_instance.feature_prompt})

response_data = {"prompts": prompts}
response = make_response(jsonify(response_data))
response.status_code = 200
return response


class RunAssistant(Resource):
"""
Represents a resource for handling file uploads.
Methods:
- post: Handles the POST request for file uploads.
"""

def post(self):
"""
Handles the POST request for file uploads.
Returns:
- JSON response containing the status of the upload:
- If successful, returns {"message": "File successfully uploaded", "path": file_path}.
- If there is an error, returns {"error": "No file part"} or {"error": "No selected file"}.
"""
sid = request.form.get("sid")

if "file" not in request.files:
return jsonify({"error": "No file part"})
file = request.files["file"]
if file.filename == "":
return jsonify({"error": "No selected file"})
if file:
file_path = os.path.join(UPLOAD_DIRECTORY, file.filename)
file.save(file_path)
try:
result = call_asssistant_api(file_path, sid, socketio)

print("result : ", json.dumps(result))
socketio.emit(
"status",
{"status": "Fetching all features...", "progress": 0},
to=sid,
)

response_data = {
"message": "File successfully uploaded",
"file_name": file.filename,
"experiments": result["experiments"],
}

# Delete the uploaded
if os.path.isfile(file_path):
os.remove(file_path)
print("File removed from local storage successfully")
else:
# If it fails, inform the user.
print("Error: %s file not found" % file_path)

response = make_response(jsonify(response_data))
response.status_code = 200
return response
except AssistantException as e:
response_data = {"error": str(e)}
response = make_response(jsonify(response_data))
response.status_code = 500
return response
else:
response_data = {"error": "File upload failed"}
response = make_response(jsonify(response_data))
response.status_code = 400
return response


class Login(Resource):
"""
Represents a resource for handling user login.
Methods:
- post: Handles the POST request for user login.
"""

def post(self):
"""
Handles the POST request for user login.
Returns:
- JSON response containing the status of the login:
- If successful, returns {"message": "User successfully logged in"}.
- If there is an error, returns {"error": "Invalid username or password"}.
"""
try:
data = request.get_json()
except json.JSONDecodeError:
response_data = {"error": "Invalid JSON data."}
response = make_response(jsonify(response_data))
response.status_code = 400
return response

email = data["email"]
magic_link = data["magic_link"]

user = users.find_by_email(email)

print("user", user)

if user and user["magic_link"] == magic_link:
access_token = create_access_token(identity=user["username"])
response = make_response(
jsonify(
{
"message": "User successfully logged in",
"access_token": access_token,
}
)
)
response.status_code = 200
return response
else:
response_data = {"error": "Invalid username or password"}
response = make_response(jsonify(response_data))
response.status_code = 401
return response


api.add_resource(GetFeatures, "/api/features")
api.add_resource(RunFeatures, "/api/run")
api.add_resource(RunAssistant, "/api/run_assistant")
api.add_resource(
RunAssistant, "/api/run_assistant", resource_class_kwargs={"socketio": socketio}
)
api.add_resource(Login, "/api/login")


Expand Down Expand Up @@ -314,6 +66,7 @@ def index():

@app.errorhandler(404)
def not_found(e):
"""reroute to index.html for all other routes"""
return app.send_static_file("index.html")


Expand Down
Loading

0 comments on commit 50ba9dd

Please sign in to comment.