Compare commits

..

No commits in common. "998139d4d8e0a3504d28bacebe750e90a97b0e1c" and "e673df8fa67bbdca330b8c353dec24d33694c40f" have entirely different histories.

12 changed files with 55 additions and 104 deletions

View file

@ -28,5 +28,5 @@ jobs:
- name: Start API server & run tests!
run: |
python checks
python run

View file

@ -1,25 +1,14 @@
"""User management."""
import os
import sys
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(project_root)
# the code above is to allow importing from the root folder
import os
import json
import hmac
import fastapi
from db.users import UserManager
from dhooks import Webhook, Embed
from dotenv import load_dotenv
import checks.client
from db.users import UserManager
load_dotenv()
router = fastapi.APIRouter(tags=['core'])
@ -32,16 +21,11 @@ async def check_core_auth(request):
"""
received_auth = request.headers.get('Authorization')
correct_core_api = os.environ['CORE_API_KEY']
# use hmac.compare_digest to prevent timing attacks
if received_auth and hmac.compare_digest(received_auth, correct_core_api):
if received_auth != os.getenv('CORE_API_KEY'):
return fastapi.Response(status_code=403, content='Invalid or no API key given.')
@router.get('/users')
async def get_users(discord_id: int, incoming_request: fastapi.Request):
"""Returns a user by their discord ID. Requires a core API key."""
auth = await check_core_auth(incoming_request)
if auth:
return auth
@ -54,10 +38,8 @@ async def get_users(discord_id: int, incoming_request: fastapi.Request):
return user
async def new_user_webhook(user: dict) -> None:
"""Runs when a new user is created."""
dhook = Webhook(os.environ['DISCORD_WEBHOOK__USER_CREATED'])
async def new_user_webhook(user: dict) -> None:
dhook = Webhook(os.getenv('DISCORD_WEBHOOK__USER_CREATED'))
embed = Embed(
description='New User',
@ -72,8 +54,6 @@ async def new_user_webhook(user: dict) -> None:
@router.post('/users')
async def create_user(incoming_request: fastapi.Request):
"""Creates a user. Requires a core API key."""
auth_error = await check_core_auth(incoming_request)
if auth_error:
@ -84,7 +64,7 @@ async def create_user(incoming_request: fastapi.Request):
discord_id = payload.get('discord_id')
except (json.decoder.JSONDecodeError, AttributeError):
return fastapi.Response(status_code=400, content='Invalid or no payload received.')
# Create the user
manager = UserManager()
user = await manager.create(discord_id)
@ -94,8 +74,6 @@ async def create_user(incoming_request: fastapi.Request):
@router.put('/users')
async def update_user(incoming_request: fastapi.Request):
"""Updates a user. Requires a core API key."""
auth_error = await check_core_auth(incoming_request)
if auth_error:
@ -107,25 +85,9 @@ async def update_user(incoming_request: fastapi.Request):
updates = payload.get('updates')
except (json.decoder.JSONDecodeError, AttributeError):
return fastapi.Response(status_code=400, content='Invalid or no payload received.')
# Update the user
manager = UserManager()
user = await manager.update_by_discord_id(discord_id, updates)
return user
@router.get('/checks')
async def run_checks(incoming_request: fastapi.Request):
"""Tests the API. Requires a core API key."""
auth_error = await check_core_auth(incoming_request)
if auth_error:
return auth_error
return {
'library': await checks.client.test_library(),
'library_moderation': await checks.client.test_library_moderation(),
'api_moderation': await checks.client.test_api_moderation(),
'models': await checks.client.test_models()
}

View file

@ -89,7 +89,7 @@ class UserManager:
db = await self._get_collection('users')
return await db.update_one({'_id': user_id}, update)
async def update_by_discord_id(self, discord_id: str, update):
async def upate_by_discord_id(self, discord_id: str, update):
db = await self._get_collection('users')
return await db.update_one({'auth.discord': str(int(discord_id))}, update)

View file

@ -24,19 +24,21 @@ app.include_router(core.router)
@app.on_event('startup')
async def startup_event():
"""Runs when the API starts up."""
# DATABASE FIX https://stackoverflow.com/questions/65970988/python-mongodb-motor-objectid-object-is-not-iterable-error-while-trying-to-f
import pydantic, bson
# pydantic.json.ENCODERS_BY_TYPE[bson.objectid.ObjectId]=str
@app.get('/')
async def root():
"""
Returns general information about the API.
Returns the root endpoint.
"""
return {
'hi': 'Welcome to the Nova API!',
'learn_more_here': 'https://nova-oss.com',
'github': 'https://github.com/novaoss/nova-api',
'core_api_docs_for_nova_developers': '/docs'
'status': 'ok',
'usage_docs': 'https://nova-oss.com',
'core_api_docs_for_developers': '/docs',
'github': 'https://github.com/novaoss/nova-api'
}
app.add_route('/{path:path}', transfer.handle, ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])

View file

@ -93,7 +93,7 @@ async def stream(
'cookies': incoming_request.cookies
})
except ValueError as exc:
webhook = dhooks.Webhook(os.environ['DISCORD_WEBHOOK__API_ISSUE'])
webhook = dhooks.Webhook(os.getenv('DISCORD_WEBHOOK__API_ISSUE'))
webhook.send(content=f'API Issue: **`{exc}`**\nhttps://i.imgflip.com/7uv122.jpg')
yield await errors.yield_error(500, 'Sorry, the API has no working keys anymore.', 'The admins have been messaged automatically.')
return

View file

@ -44,16 +44,16 @@ async def handle(incoming_request):
received_key = incoming_request.headers.get('Authorization')
if not received_key or not received_key.startswith('Bearer '):
return await errors.error(403, 'No NovaAI API key given!', 'Add \'Authorization: Bearer nv-...\' to your request headers.')
return await errors.error(401, 'No NovaAI API key given!', 'Add "Authorization: Bearer nv-..." to your request headers.')
user = await users.user_by_api_key(received_key.split('Bearer ')[1].strip())
if not user or not user['status']['active']:
return await errors.error(403, 'Invalid or inactive NovaAI API key!', 'Create a new NovaOSS API key or reactivate your account.')
return await errors.error(401, 'Invalid or inactive NovaAI API key!', 'Create a new NovaOSS API key or reactivate your account.')
ban_reason = user['status']['ban_reason']
if ban_reason:
return await errors.error(403, f'Your NovaAI account has been banned. Reason: \'{ban_reason}\'.', 'Contact the staff for an appeal.')
return await errors.error(403, f'Your NovaAI account has been banned. Reason: "{ban_reason}".', 'Contact the staff for an appeal.')
costs = config['costs']
cost = costs['other']

View file

@ -1 +0,0 @@
from . import client

View file

@ -1,2 +0,0 @@
import client
client.demo()

View file

@ -12,8 +12,5 @@ cp env/.prod.env /home/nova-prod/.env
# Change directory
cd /home/nova-prod
# Kill the production server
fuser -k 2333/tcp
# Start screen
screen -S nova-api python run prod

View file

@ -100,7 +100,7 @@ python run 1337
```
## Test if it works
`python checks`
`python tests`
## Ports
```yml

View file

@ -1,11 +1,9 @@
"""Tests the API."""
import os
import time
import openai as closedai
import httpx
import openai
import asyncio
import traceback
import time
from rich import print
from typing import List
@ -24,7 +22,7 @@ MESSAGES = [
api_endpoint = 'http://localhost:2332'
async def test_server():
def test_server():
"""Tests if the API server is running."""
try:
@ -32,7 +30,7 @@ async def test_server():
except httpx.ConnectError as exc:
raise ConnectionError(f'API is not running on port {api_endpoint}.') from exc
async def test_api(model: str=MODEL, messages: List[dict]=None) -> dict:
def test_api(model: str=MODEL, messages: List[dict]=None) -> dict:
"""Tests an API api_endpoint."""
json_data = {
@ -51,10 +49,10 @@ async def test_api(model: str=MODEL, messages: List[dict]=None) -> dict:
return response.text
async def test_library():
"""Tests if the api_endpoint is working with the OpenAI Python library."""
def test_library():
"""Tests if the api_endpoint is working with the Python library."""
completion = openai.ChatCompletion.create(
completion = closedai.ChatCompletion.create(
model=MODEL,
messages=MESSAGES
)
@ -63,13 +61,13 @@ async def test_library():
return completion['choices'][0]['message']['content']
async def test_library_moderation():
def test_library_moderation():
try:
return openai.Moderation.create('I wanna kill myself, I wanna kill myself; It\'s all I hear right now, it\'s all I hear right now')
except openai.error.InvalidRequestError:
return closedai.Moderation.create('I wanna kill myself, I wanna kill myself; It\'s all I hear right now, it\'s all I hear right now')
except closedai.error.InvalidRequestError:
return True
async def test_models():
def test_models():
response = httpx.get(
url=f'{api_endpoint}/models',
headers=HEADERS,
@ -78,7 +76,7 @@ async def test_models():
response.raise_for_status()
return response.json()
async def test_api_moderation() -> dict:
def test_api_moderation() -> dict:
"""Tests an API api_endpoint."""
response = httpx.get(
@ -92,43 +90,38 @@ async def test_api_moderation() -> dict:
# ==========================================================================================
def demo():
def test_all():
"""Runs all tests."""
try:
for _ in range(30):
if test_server():
break
print("Waiting until API Server is started up...")
time.sleep(6)
print('Waiting until API Server is started up...')
time.sleep(1)
else:
raise ConnectionError('API Server is not running.')
print('[lightblue]Running test on API server to check if its running...')
print(test_server())
print('[lightblue]Running a api endpoint to see if requests can go through...')
print(asyncio.run(test_api('gpt-3.5-turbo')))
print(test_api('gpt-3.5-trubo'))
print('[lightblue]Checking if the API works with the python library...')
print(asyncio.run(test_library()))
print(test_library())
print('[lightblue]Checking if the moderation endpoint works...')
print(asyncio.run(test_library_moderation()))
print(test_library_moderation())
print('[lightblue]Checking the /v1/models endpoint...')
print(asyncio.run(test_models()))
except Exception as exc:
print('[red]Error: ' + str(exc))
traceback.print_exc()
print(test_models())
except Exception as e:
print('[red]Error: ')
print(e)
exit(500)
openai.api_base = api_endpoint
openai.api_key = os.environ['NOVA_KEY']
HEADERS = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + openai.api_key
}
if __name__ == '__main__':
demo()
closedai.api_base = api_endpoint
closedai.api_key = os.environ['NOVA_KEY']
HEADERS = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + closedai.api_key
}
test_all()