Initial
This commit is contained in:
parent
5713fa2448
commit
e320a1bdbe
16
config_example.json
Normal file
16
config_example.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"locations": {
|
||||||
|
"data": "data"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"key_expired": "API key expired",
|
||||||
|
"key_invalid": "Invalid API key",
|
||||||
|
"key_valid": "Valid API key",
|
||||||
|
"bad_request": "Bad request. Read the docs at photos.end-play.xyz/docs",
|
||||||
|
"ip_blacklisted": "Your IP is blacklisted. Make sure you are using correct API address.",
|
||||||
|
"credentials_invalid": "Incorrect user or password",
|
||||||
|
"user_already_exists": "User with this username already exists.",
|
||||||
|
"email_confirmed": "Email confirmed. You can now log in.",
|
||||||
|
"email_code_invalid": "Confirmation code is invalid."
|
||||||
|
}
|
||||||
|
}
|
1
data/api_keys.json
Normal file
1
data/api_keys.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
1
data/expired_keys.json
Normal file
1
data/expired_keys.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
75
modules/app.py
Normal file
75
modules/app.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from os import path
|
||||||
|
from fastapi import FastAPI, Security, HTTPException
|
||||||
|
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
||||||
|
from fastapi.security import APIKeyQuery, APIKeyHeader, APIKeyCookie
|
||||||
|
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
|
||||||
|
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||||
|
|
||||||
|
from modules.utils import configGet, jsonLoad
|
||||||
|
|
||||||
|
app = FastAPI(title="Stardew Sync", docs_url=None, redoc_url=None, version="0.1")
|
||||||
|
|
||||||
|
api_key_query = APIKeyQuery(name="apikey", auto_error=False)
|
||||||
|
api_key_header = APIKeyHeader(name="apikey", auto_error=False)
|
||||||
|
api_key_cookie = APIKeyCookie(name="apikey", auto_error=False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_api_keys() -> list:
|
||||||
|
return jsonLoad(path.join(configGet("data", "locations"), "api_keys.json"))
|
||||||
|
|
||||||
|
def get_all_expired_keys() -> list:
|
||||||
|
return jsonLoad(path.join(configGet("data", "locations"), "expired_keys.json"))
|
||||||
|
|
||||||
|
# def check_project_key(project: str, apikey: APIKey) -> bool:
|
||||||
|
# keys = jsonLoad(path.join(configGet("data", "locations"), "api_keys.json"))
|
||||||
|
# if apikey in keys:
|
||||||
|
# if keys[apikey] != []:
|
||||||
|
# if project in keys[apikey]:
|
||||||
|
# return True
|
||||||
|
# else:
|
||||||
|
# return False
|
||||||
|
# else:
|
||||||
|
# return False
|
||||||
|
# else:
|
||||||
|
# return False
|
||||||
|
|
||||||
|
|
||||||
|
async def get_api_key(
|
||||||
|
api_key_query: str = Security(api_key_query),
|
||||||
|
api_key_header: str = Security(api_key_header),
|
||||||
|
api_key_cookie: str = Security(api_key_cookie),
|
||||||
|
) -> str:
|
||||||
|
|
||||||
|
keys = get_all_api_keys()
|
||||||
|
expired = get_all_expired_keys()
|
||||||
|
|
||||||
|
def is_valid(key):
|
||||||
|
return True if key in keys else False
|
||||||
|
|
||||||
|
if is_valid(api_key_query):
|
||||||
|
return api_key_query
|
||||||
|
elif is_valid(api_key_header):
|
||||||
|
return api_key_header
|
||||||
|
elif is_valid(api_key_cookie):
|
||||||
|
return api_key_cookie
|
||||||
|
else:
|
||||||
|
if (api_key_query in expired) or (api_key_header in expired) or (api_key_cookie in expired):
|
||||||
|
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail=configGet("key_expired", "messages"))
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=configGet("key_invalid", "messages"))
|
||||||
|
|
||||||
|
@app.get("/docs", include_in_schema=False)
|
||||||
|
async def custom_swagger_ui_html():
|
||||||
|
return get_swagger_ui_html(
|
||||||
|
openapi_url=app.openapi_url, # type: ignore
|
||||||
|
title=app.title + " - Documentation",
|
||||||
|
swagger_favicon_url="/favicon.ico"
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/redoc", include_in_schema=False)
|
||||||
|
async def custom_redoc_html():
|
||||||
|
return get_redoc_html(
|
||||||
|
openapi_url=app.openapi_url, # type: ignore
|
||||||
|
title=app.title + " - Documentation",
|
||||||
|
redoc_favicon_url="/favicon.ico"
|
||||||
|
)
|
47
modules/extensions_loader.py
Normal file
47
modules/extensions_loader.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from importlib.util import module_from_spec, spec_from_file_location
|
||||||
|
from os import getcwd, path, walk
|
||||||
|
|
||||||
|
#=================================================================================
|
||||||
|
|
||||||
|
# Import functions
|
||||||
|
# Took from https://stackoverflow.com/a/57892961
|
||||||
|
def get_py_files(src):
|
||||||
|
cwd = getcwd()
|
||||||
|
py_files = []
|
||||||
|
for root, dirs, files in walk(src):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".py"):
|
||||||
|
py_files.append(path.join(cwd, root, file))
|
||||||
|
return py_files
|
||||||
|
|
||||||
|
|
||||||
|
def dynamic_import(module_name, py_path):
|
||||||
|
try:
|
||||||
|
module_spec = spec_from_file_location(module_name, py_path)
|
||||||
|
module = module_from_spec(module_spec) # type: ignore
|
||||||
|
module_spec.loader.exec_module(module) # type: ignore
|
||||||
|
return module
|
||||||
|
except SyntaxError:
|
||||||
|
print(f"Could not load extension {module_name} due to invalid syntax. Check logs/errors.log for details.", flush=True)
|
||||||
|
return
|
||||||
|
except Exception as exp:
|
||||||
|
print(f"Could not load extension {module_name} due to {exp}", flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def dynamic_import_from_src(src, star_import = False):
|
||||||
|
my_py_files = get_py_files(src)
|
||||||
|
for py_file in my_py_files:
|
||||||
|
module_name = path.split(py_file)[-1][:-3]
|
||||||
|
print(f"Importing {module_name} extension...", flush=True)
|
||||||
|
imported_module = dynamic_import(module_name, py_file)
|
||||||
|
if imported_module != None:
|
||||||
|
if star_import:
|
||||||
|
for obj in dir(imported_module):
|
||||||
|
globals()[obj] = imported_module.__dict__[obj]
|
||||||
|
else:
|
||||||
|
globals()[module_name] = imported_module
|
||||||
|
print(f"Successfully loaded {module_name} extension", flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
#=================================================================================
|
61
modules/utils.py
Normal file
61
modules/utils.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from typing import Any, Union
|
||||||
|
from ujson import loads, dumps, JSONDecodeError
|
||||||
|
from traceback import print_exc
|
||||||
|
|
||||||
|
# Print to stdout and then to log
|
||||||
|
def logWrite(message: str, debug: bool = False) -> None:
|
||||||
|
# save to log file and rotation is to be done
|
||||||
|
# logAppend(f'{message}', debug=debug)
|
||||||
|
print(f"{message}", flush=True)
|
||||||
|
|
||||||
|
def jsonLoad(filepath: str) -> Any:
|
||||||
|
"""Load json file
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* filepath (`str`): Path to input file
|
||||||
|
|
||||||
|
### Returns:
|
||||||
|
* `Any`: Some json deserializable
|
||||||
|
"""
|
||||||
|
with open(filepath, "r", encoding='utf8') as file:
|
||||||
|
try:
|
||||||
|
output = loads(file.read())
|
||||||
|
except JSONDecodeError:
|
||||||
|
logWrite(f"Could not load json file {filepath}: file seems to be incorrect!\n{print_exc()}")
|
||||||
|
raise
|
||||||
|
except FileNotFoundError:
|
||||||
|
logWrite(f"Could not load json file {filepath}: file does not seem to exist!\n{print_exc()}")
|
||||||
|
raise
|
||||||
|
file.close()
|
||||||
|
return output
|
||||||
|
|
||||||
|
def jsonSave(contents: Union[list, dict], filepath: str) -> None:
|
||||||
|
"""Save contents into json file
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* contents (`Union[list, dict]`): Some json serializable
|
||||||
|
* filepath (`str`): Path to output file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(filepath, "w", encoding='utf8') as file:
|
||||||
|
file.write(dumps(contents, ensure_ascii=False, indent=4))
|
||||||
|
file.close()
|
||||||
|
except Exception as exp:
|
||||||
|
logWrite(f"Could not save json file {filepath}: {exp}\n{print_exc()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
def configGet(key: str, *args: str) -> Any:
|
||||||
|
"""Get value of the config key
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* key (`str`): The last key of the keys path.
|
||||||
|
* *args (`str`): Path to key like: dict[args][key].
|
||||||
|
|
||||||
|
### Returns:
|
||||||
|
* `Any`: Value of provided key
|
||||||
|
"""
|
||||||
|
this_dict = jsonLoad("config.json")
|
||||||
|
this_key = this_dict
|
||||||
|
for dict_key in args:
|
||||||
|
this_key = this_key[dict_key]
|
||||||
|
return this_key[key]
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
fastapi[all]
|
||||||
|
ujson~=5.7.0
|
||||||
|
pydantic~=1.10.4
|
||||||
|
apscheduler~=3.9.1.post1
|
25
sync_api.py
Normal file
25
sync_api.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from os import makedirs, path
|
||||||
|
from modules.app import app
|
||||||
|
from modules.utils import configGet
|
||||||
|
from modules.extensions_loader import dynamic_import_from_src
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
|
makedirs(configGet("data", "locations"), exist_ok=True)
|
||||||
|
|
||||||
|
for entry in [path.join(configGet("data", "locations"), "api_keys.json"), path.join(configGet("data", "locations"), "expired_keys.json")]:
|
||||||
|
mode = 'r' if path.exists(entry) else 'w'
|
||||||
|
with open(entry, mode) as f:
|
||||||
|
try:
|
||||||
|
f.write("[]")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/favicon.ico", response_class=FileResponse, include_in_schema=False)
|
||||||
|
async def favicon():
|
||||||
|
return FileResponse("favicon.ico")
|
||||||
|
|
||||||
|
|
||||||
|
#=================================================================================
|
||||||
|
dynamic_import_from_src("extensions", star_import = True)
|
||||||
|
#=================================================================================
|
Loading…
Reference in New Issue
Block a user