Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepaid database #27

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ data/
chroma/
venv/
uploads/
test/.test_tokens.json
62 changes: 43 additions & 19 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,57 @@ FROM python:3.10.13

# Set environment variables for configuration
ENV FLASK_APP=main.py
# ENV GOOGLE_APPLICATION_CREDENTIALS=/app/res/service_account.json

# Set default values for environment variables
ENV FLASK_ENV=production

# Set the working directory inside the container
WORKDIR /app

# Mount the volume to /app/data
VOLUME ["/app/data"]

# Copy the project files to the working directory
RUN apt-get update && apt-get install -y \
libgeos-dev \
libpq-dev \
gcc \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt --no-deps && \
pip install --no-cache-dir -r requirements.txt

COPY . /app

RUN apt-get update && apt-get install -y \
libgeos-dev
# Install the project dependencies
# RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# anthropic package is not installed with the previous command
RUN pip install --no-cache-dir anthropic
RUN pip install --no-cache-dir vertexai

# Expose the port on which the Flask app will run
RUN chmod +x start.sh

EXPOSE 8080

CMD ["./start.sh"]


# # Copy the project files to the working directory
# COPY . /app

# RUN apt-get update && apt-get install -y \
# libgeos-dev
# # Install the project dependencies
# # RUN pip install --no-cache-dir -r requirements.txt

# COPY requirements.txt /app/
# RUN pip install --no-cache-dir -r requirements.txt --no-deps && \
# pip install --no-cache-dir -r requirements.txt



# RUN pip install --no-cache-dir -r requirements.txt
# # anthropic package is not installed with the previous command
# RUN pip install --no-cache-dir anthropic
# RUN pip install --no-cache-dir vertexai

# # Ensure the start script is executable
# RUN chmod +x start.sh

# # Expose the port on which the Flask app will run
# EXPOSE 8080

# Run the Flask app when the container starts
# CMD ["flask", "--app", "main.py", "run", "--host=0.0.0.0"]
CMD ["python", "main.py"]
# # Run the Flask app when the container starts
# # CMD ["flask", "--app", "main.py", "run", "--host=0.0.0.0"]
# CMD ["./start.sh"]

7 changes: 7 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ google-generativeai = "==0.5.0"
yfinance = "==0.2.38"
flask-httpauth = "==4.8.0"
lumaai = "==1.0.2"
flask-jwt-extended = "==4.7.1"
eth-account = "==0.13.4"
web3 = "==7.6.0"
werkzeug = "==2.3.7"
flask-migrate = "*"
alembic = "*"
psycopg2-binary = "*"

[dev-packages]

Expand Down
2,078 changes: 1,409 additions & 669 deletions Pipfile.lock

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion apis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@
from .openai_resource import api as openai_namespace
from .anthropic import api as anthropic_namespace, AnthropicCompletionRes
from .luma import api as lumaai_namespace
from .auth import api as auth_resource

authorizations = {
'Bearer': {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization'
}
}

api = Api(
title='LLMs Api Hub',
version='1.0',
description='Large Language Models (LLM) API Hub',
authorizations=authorizations,
security='Bearer'
)

api.add_namespace(vertex_namespace)
Expand All @@ -20,4 +30,5 @@
api.add_namespace(openai_namespace)
api.add_namespace(anthropic_namespace)
api.add_namespace(lumaai_namespace)
api.add_namespace(xai_namespace)
api.add_namespace(xai_namespace)
api.add_namespace(auth_resource)
8 changes: 7 additions & 1 deletion apis/anthropic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
from .anthropic_resource import api, AnthropicCompletionRes
from .anthropic_helper import anthropicHelper
from .anthropic_helper import anthropicHelper

__all__ = [
'api',
'AnthropicCompletionRes',
'anthropicHelper'
]
8 changes: 7 additions & 1 deletion apis/anthropic/anthropic_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import anthropic
import json
import os
from ..auth import require_any_auth, require_token
from services import telegram_report_error
from .anthropic_helper import anthropicHelper as helper
from app_types import ToolsBetaMessage
Expand Down Expand Up @@ -97,6 +98,7 @@ def create_message(text):
@api.route('/completions')
class AnthropicCompletionRes(Resource):

@require_token
def post(self):
"""
Endpoint to handle Anthropic request.
Expand Down Expand Up @@ -142,6 +144,7 @@ class AnthropicCompletionToolRes(Resource):
runningTools = []
tools = helper.get_claude_tools_definition()

@require_token
def post(self):
"""
Endpoint to handle Anthropic requests with Tools.
Expand Down Expand Up @@ -280,7 +283,8 @@ def __tool_request_handler(self, data, tool_execution_id, context):
@api.route('/completions/tools/<tool_execution_id>')
class CheckToolExecution(Resource):

@api.doc(params={"tool_execution_id": msg.API_DOC_PARAMS_COLLECTION_NAME})
@api.doc(params={"tool_execution_id": msg.API_DOC_PARAMS_COLLECTION_NAME})
@require_token
def get(self, tool_execution_id):
if (tool_execution_id):
tool = helper.get_running_tool(tool_execution_id)
Expand Down Expand Up @@ -318,6 +322,7 @@ class AnthropicPDFSummary(Resource):
"model": msg.API_DOC_PARAMS_MODEL,
"maxTokens": msg.API_DOC_PARAMS_MAX_TOKENS,
"system": msg.API_DOC_PARAMS_SYSTEM})
@require_token
def post(self):
"""
Endpoint to handle PDF parser and summary.
Expand Down Expand Up @@ -383,6 +388,7 @@ def extract_value(self, text, start_key, end_key):
"jobDescription": msg.API_DOC_PARAMS_JOB_DESCRIPTION,
"model": msg.API_DOC_PARAMS_MODEL,
"maxTokens": msg.API_DOC_PARAMS_MAX_TOKENS})
@require_token
def post(self):
"""
Analyze a given CV with a given Job Description.
Expand Down
11 changes: 11 additions & 0 deletions apis/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .auth_resource import api
from .auth_helper import AuthHelper
from .auth_middleware import require_any_auth, require_jwt, require_token

__all__ = [
'api',
'AuthHelper',
'require_any_auth',
'require_jwt',
'require_token'
]
137 changes: 137 additions & 0 deletions apis/auth/auth_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from sqlalchemy.exc import IntegrityError
from flask_jwt_extended import create_access_token, create_refresh_token
from eth_account.messages import encode_defunct
from web3 import Web3
from typing import Optional, Tuple
import logging
import uuid
from datetime import datetime, timedelta
from models.auth import SignInRequests, Users, Tokens
from models import db
from config import config

class AuthHelper:
def __init__(self):
self.config = config
self.w3 = Web3(Web3.HTTPProvider(config.WEB3_PROVIDER_URL))

async def get_user(self, address: str) -> Optional[Users]:
"""Get user by address."""
return Users.query.filter_by(address=address.lower()).first()

async def get_user_by_username(self, username: str) -> Optional[Users]:
"""Get user by username."""
return Users.query.filter_by(username=username).first()

async def create_user(self, address: str) -> Users:
"""Create a new user."""
address = address.lower()
username = Users.generate_username(address)

if await self.get_user_by_username(username):
username = address

user = Users(
address=address,
username=username
)

try:
db.session.add(user)
db.session.commit()
return user
except IntegrityError:
db.session.rollback()
raise ValueError("User already exists")

def get_sign_in_request(self, address: str) -> Optional[SignInRequests]:
"""Get existing sign in request for address"""
return SignInRequests.query.filter_by(
address=address.lower()
).first()

def create_sign_in_request(self, address: str) -> SignInRequests:
"""Create new sign in request with nonce"""
SignInRequests.query.filter_by(address=address.lower()).delete()

nonce = uuid.uuid4().int % 1000000 # Generate 6-digit nonce
request = SignInRequests(
address=address.lower(),
nonce=nonce
)
db.session.add(request)
db.session.commit()
return request

def verify_signature(self, address: str, signature: str, nonce: int) -> bool:
"""Verify wallet signature matches address"""
try:
message = f"I'm signing my one-time nonce: {nonce}"
encoded_message = encode_defunct(text=message)
recovered_address = self.w3.eth.account.recover_message(
encoded_message,
signature=signature
)
return recovered_address.lower() == address.lower()
except Exception as e:
logging.error(f"Signature verification failed: {str(e)}", exc_info=True)
return False

def delete_sign_in_request(self, address: str):
"""Delete sign in request after use"""
SignInRequests.query.filter_by(address=address.lower()).delete()
db.session.commit()

async def get_or_create_user(self, address: str) -> Users:
"""Get existing user or create new one"""
user = await self.get_user(address)
if user:
return user

try:
user = await self.create_user(address)
return user
except Exception as e:
db.session.rollback()
raise e

def validate_token_jti(self, jti: str, user_id: int) -> bool:
"""Validate that the JTI exists and belongs to the user"""
token = Tokens.query.filter_by(
jti=jti,
user_id=user_id
).first()
return token is not None

def generate_tokens(self, user_id: int) -> Tuple[str, str]:
"""Generate access and refresh tokens"""
jti = str(uuid.uuid4())

# Store token record
token = Tokens(user_id=user_id, jti=jti)
db.session.add(token)
db.session.commit()

access_token = create_access_token(
identity=str(user_id),
additional_claims={
'jti': jti,
'type': 'access'
},
fresh=False
)

refresh_token = create_refresh_token(
identity=str(user_id),
additional_claims={
'jti': jti,
'type': 'refresh'
}
)

return access_token, refresh_token

def revoke_token(self, jti: str):
"""Revoke a token by deleting it"""
Tokens.query.filter_by(jti=jti).delete()
db.session.commit()
Loading