diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 4eeafeb..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "daily" diff --git a/.gitignore b/.gitignore index 4519709..b6e4761 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -6,7 +5,7 @@ __pycache__/ # C extensions *.so -/logs/ + # Distribution / packaging .Python build/ @@ -21,15 +20,11 @@ parts/ sdist/ var/ wheels/ +pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg -.idea -/.idea/ -*.xml -*.iml -/.vscode/ MANIFEST # PyInstaller @@ -55,7 +50,6 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ -cover/ # Translations *.mo @@ -78,7 +72,6 @@ instance/ docs/_build/ # PyBuilder -.pybuilder/ target/ # Jupyter Notebook @@ -87,13 +80,9 @@ target/ # IPython profile_default/ ipython_config.py -.env.heroku -.env.development # pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -113,13 +102,10 @@ celerybeat.pid *.sage.py # Environments -.env.development +.env .venv -.env.development -.env.production env/ venv/ -venv-11/ ENV/ env.bak/ venv.bak/ @@ -141,18 +127,3 @@ dmypy.json # Pyre type checker .pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ -app.yaml -/.idea/deployment.xml -/.idea/sshConfigs.xml -/.idea/webServers.xml -/.nasdaq -/.idea/ -/.env.local -/logs/ -/logs/ diff --git a/README.md b/README.md index 5c21870..0fea337 100644 --- a/README.md +++ b/README.md @@ -1,220 +1,2 @@ -# EOD STOCK API - API--GATEWAY-VERSION 0.0.1 - -**api-gateway** is a Python-based API Gateway built using the FastAPI framework. It provides several key features to -secure and manage your API endpoints. - -## Features - -### API key based authorization - -API key based authorization ensures that only authorized clients can access your API endpoints. -When a client sends a request to the API gateway, it checks the API key provided in the request headers and -verifies it against a list of authorized API keys. - -```python -def auth_and_rate_limit(func): - # noinspection PyTypeChecker - async def return_kwargs(kwargs): - request: Request = kwargs.get('request') - api_key = request.query_params.get('api_key') - path = kwargs.get('path') - return api_key, path - - async def rate_limiter(api_key): - """ - **rate_limiter** - this only rate limits clients by api keys, - there is also a regional rate limiter and a global rate limit both created so that the gateway - does not end up taking too much traffic and is able to recover from DDOS attacks easily. - - --> the rate_limiter has a side effect of also authorizing the client based on API Keys - - this method applies the actual rate_limiter per client basis""" - # Rate Limiting Section - async with apikeys_lock: - api_keys_model_dict: dict[str, str | int] = api_keys_lookup(api_key) - now = time.monotonic() - duration: int = api_keys_model_dict.get('duration') - limit: int = api_keys_model_dict.get('rate_limit') - last_request_timestamp: float = api_keys_model_dict.get('last_request_timestamp') - # Note that APiKeysModel must be updated with plan rate_limit - if now - last_request_timestamp > duration: - api_keys_model_dict['requests_count'] = 0 - if api_keys_model_dict['requests_count'] >= limit: - time_left = last_request_timestamp + duration - now - mess: str = f"EOD Stock API - Rate Limit Exceeded. Please wait {time_left:.0f} seconds before making " \ - f"another request, or upgrade your plan to better take advantage of extra resources " \ - f"available on better plans." - - rate_limit_dict = {'duration': duration, 'rate_limit': limit, 'time_left': f"{time_left:.0f}"} - raise RateLimitExceeded(rate_limit=rate_limit_dict, status_code=status.HTTP_429_TOO_MANY_REQUESTS, - detail=mess) - # NOTE Updating number of requests and timestamp - api_keys_model_dict['requests_count'] += 1 - # noinspection PyTypeChecker - api_keys_model_dict['last_request_timestamp'] = now - api_keys[api_key] = api_keys_model_dict - - @wraps(func) - async def wrapper(*args, **kwargs): - """main wrapper""" - api_key, path = await return_kwargs(kwargs) - - path = f"/api/v1/{path}" - api_key_found = api_key in api_keys - if not api_key_found: - await cache_api_keys_func() # Update api_keys if the key is not found - api_key_found = api_key in api_keys - - if not api_key_found: - # user not authorized to access this routes - mess = "EOD Stock API - Invalid API Key, or Cancelled API Key please subscribe to get a valid API Key" - raise NotAuthorized(message=mess) - - # actual rate limiter - await rate_limiter(api_key) - - # Authorization Section - # Use asyncio.gather to run is_resource_authorized and monthly_credit_available concurrently - is_authorized_task = asyncio.create_task(is_resource_authorized(path_param=path, api_key=api_key)) - monthly_credit_task = asyncio.create_task(monthly_credit_available(api_key=api_key)) - is_authorized, monthly_credit = await asyncio.gather(is_authorized_task, monthly_credit_task) - - if is_authorized and monthly_credit: - return await func(*args, **kwargs) - - if not is_authorized: - mess: str = "EOD Stock API - Request not Authorized, Either you are not subscribed to any plan or you " \ - "need to upgrade your subscription" - raise NotAuthorized(message=mess) - - if not monthly_credit: - mess: str = f"EOD Stock API - Your Monthly plan request limit has been reached. " \ - f"please upgrade your plan, to take advantage of our soft limits" - raise NotAuthorized(message=mess) - - return wrapper - -``` - -### Regional edge server based request throttling - -The regional edge server based request throttling feature ensures that a client cannot overwhelm the API gateway -with too many requests. - -The API gateway keeps track of the number of requests coming from each edge IP address and enforces a limit on the -number of requests that can be made in a given time period. if the limit is exceeded the requests will be throttled -this will not affect other clients making use of our services from regions where there is no huge traffic - -```python -@app.middleware(middleware_type="http") -async def edge_request_throttle(request: Request, call_next): - """ - Middleware will throttle requests if they are more than 100 requests per second - per edge, other edge servers may be serviced just as before but the one server - where higher traffic is coming from will be limited - """ - # rate limit by Edge Server IP Address, this will have the effect of throttling entire regions if flooding requests - # are mainly coming from such regions - ip_address = await cf_firewall.get_edge_server_ip(headers=request.headers) - if ip_address not in ip_rate_limits: - # This will throttle the connection if there is too many requests coming from only one edge server - ip_rate_limits[ip_address] = RateLimit() - - is_throttled = False - if await ip_rate_limits[ip_address].is_limit_exceeded(): - await ip_rate_limits[ip_address].ip_throttle(edge_ip=ip_address, request=request) - is_throttled = True - # continue with the request - # either the request was throttled and now proceeding or all is well - app_logger.info(f"On Entry to : {request.url.path}") - response = await call_next(request) - - # attaching a header showing throttling was in effect and proceeding - if is_throttled: - response.headers["X-Request-Throttled-Time"] = f"{ip_rate_limits[ip_address].throttle_duration} Seconds" - return response - -``` - -### API key based client request rate limiting - -API key based client request rate limiting provides an additional layer of protection against DDoS attacks by limiting -the number of requests a client can make in a given time period. - -The API Gateway checks the number of requests made by each client using their API key and enforces a limit on the -number of requests that can be made in a given time period. - - -### Regex based request filtering - -Regex based request filtering ensures that only whitelisted requests can reach the API gateway. The API gateway checks -the request URL against a list of regular expressions and rejects any requests that do not match any of the -regular expressions. The regular expressions matches pre configured url routes -```python -# dicts of Known Routes being serviced by the gateway example -route_regexes = { - "home": "^/$", - "all_general_fundamentals": "^/api/v1/fundamental/general$", - ...} - - def __init__(self): - ... - self.compiled_patterns = [re.compile(_regex) for _regex in route_regexes.values()] - - async def path_matches_known_route(self, path: str): - """ - **path_matches_known_route** - helps to filter out malicious paths based on regex matching - parameters: - path: this is the path parameter of the request being requested - - """ - # NOTE: that at this stage if this request is not a get then it has already been rejected - # NOTE: this will return true if there is at least one route that matches with the requested path. - # otherwise it will return false and block the request - return any(pattern.match(path) for pattern in self.compiled_patterns) - -``` - -### Resource based request authorization - -Resource based request authorization allows you to control which API resources can be accessed by each client. -The API gateway checks the API key or username provided in the request headers and verifies it against a -list of authorized clients for the specific resource. - -## Getting started - -To get started with **api-gateway**, follow these steps: - -1. Clone the repository: -https://github.com/MJ-API-Development/api-gateway - -The API gateway should now be accessible at -https://gateway.eod-stock-api.site - -## Contributing - -If you want to contribute to **api-gateway**, please follow these steps: - -- Fork the repository. -- Create a new branch for your feature or bug fix: -- Make your changes and commit them: -- Push your changes to your fork: -- Create a pull request to the main repository. - - -## Links -- [EOD Stock API - Intelligent Stock Market API](https://eod-stock-api.site) -- [PythonSDK - Intelligent Stock Market API](https://pypi.org/project/Intelligent-Stock-Market-API/) -- [Developers Blog - EOD Stock API](https://eod-stock-api.site/blog) -- [EOD STOCK API - Gateway Server](https://gateway.eod-stock-api.site) -- [EOD Stock API - Redoc Documentations](https://eod-stock-api.site/redoc) - -## Community -- [Slack Channel](https://join.slack.com/t/eod-stock-apisite/shared_invite/zt-1uelcf229-c_6QAgWFNyVfXKZr1hYYoQ) -- [StackOverflow](https://stackoverflowteams.com/c/eod-stock-market-api) -- [Quora](https://eodstockmarketapi.quora.com/) - -## License -**api-gateway** is licensed under the MIT License. See the `LICENSE` file for details. +# api-gateway +Python + FastAPI Gateway diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 034e848..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,21 +0,0 @@ -# Security Policy - -## Supported Versions - -Use this section to tell people about which versions of your project are -currently being supported with security updates. - -| Version | Supported | -| ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | - -## Reporting a Vulnerability - -Use this section to tell people how to report a vulnerability. - -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. diff --git a/app.py b/app.py deleted file mode 100644 index 7f6770d..0000000 --- a/app.py +++ /dev/null @@ -1,15 +0,0 @@ -import socket -# noinspection PyUnresolvedReferences -from src.main.main import app -from src.config import config_instance -import uvicorn - -# this will create database tables if they do not already exist - - -if __name__ == '__main__': - # Start the FastAPI app - if config_instance().DEVELOPMENT_SERVER_NAME.casefold() == socket.gethostname().casefold(): - uvicorn.run("app:app", host="127.0.0.1", port=8080, reload=True, workers=1) - else: - uvicorn.run("app:app", host="0.0.0.0", port=8080, reload=False, workers=3) diff --git a/app.py.lprof b/app.py.lprof deleted file mode 100644 index 26001fe..0000000 Binary files a/app.py.lprof and /dev/null differ diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index fa75440..0000000 Binary files a/requirements.txt and /dev/null differ diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/authentication/__init__.py b/src/authentication/__init__.py deleted file mode 100644 index 7c0ff6d..0000000 --- a/src/authentication/__init__.py +++ /dev/null @@ -1,64 +0,0 @@ -import hashlib -import hmac -from functools import wraps - -from fastapi import Request - -from src.authorize.authorize import NotAuthorized -from src.config import config_instance -from src.database.apikeys.keys import ApiKeyModel, sessions - - -def authenticate_admin(func): - @wraps(func) - async def wrapper(*args, **kwargs): - request: Request = kwargs.get('request') - api_key = request.query_params.get('api_key') - with next(sessions) as session: - api_keys_model = await ApiKeyModel.get_by_apikey(api_key=api_key, session=session) - if api_keys_model.account.is_admin: - return await func(*args, **kwargs) - - raise NotAuthorized(message="This Resource is only Accessible to Admins") - - return wrapper - - -def authenticate_app(func): - """ - this will only authenticate application example client and admin app - :param func: - :return: - """ - - @wraps(func) - async def wrapper(*args, **kwargs): - request: Request = kwargs.get('request') - if await verify_signature(request=request): - return await func(*args, **kwargs) - raise NotAuthorized(message="This Resource is only Accessible to Admins") - return wrapper - - -def authenticate_cloudflare_workers(func): - @wraps(func) - async def _cloudflare_auth(*args, **kwargs): - request: Request = kwargs.get('request') - secret_token = request.headers.get('X-SECRET-KEY') - this_secret_token = config_instance().CLOUDFLARE_SETTINGS.CLOUDFLARE_SECRET_KEY - - if hmac.compare_digest(this_secret_token, secret_token): - return await func(*args, **kwargs) - else: - raise NotAuthorized(message="Invalid X-SECRET-KEY header") - - return _cloudflare_auth - - -async def verify_signature(request): - secret_key = config_instance().SECRET_KEY - request_header = request.headers.get('X-SIGNATURE', '') - data_str, signature_header = request_header.split('|') - _signature = hmac.new(secret_key.encode('utf-8'), data_str.encode('utf-8'), hashlib.sha256).hexdigest() - result = hmac.compare_digest(_signature, signature_header) - return result diff --git a/src/authorize/__init__.py b/src/authorize/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/authorize/authorize.py b/src/authorize/authorize.py deleted file mode 100644 index 8accb60..0000000 --- a/src/authorize/authorize.py +++ /dev/null @@ -1,265 +0,0 @@ -import asyncio -import time -from functools import wraps - -from fastapi import HTTPException, Request -from starlette import status - -from src.authorize.resources import get_resource_name, resource_name_request_size -from src.cache.cache import redis_cache, redis_cached_ttl -from src.database.apikeys.keys import api_keys, cache_api_keys, ApiKeyModel, apikeys_lock -from src.database.database_sessions import sessions -from src.database.plans.plans import Subscriptions, Plans, PlanType -from src.utils.my_logger import init_logger - -lock = asyncio.Lock() - -api_keys_lookup = api_keys.get -cache_api_keys_func = cache_api_keys - -take_credit_queue = [] - -ONE_DAY: int = 60 * 60 * 24 -auth_logger = init_logger("auth-logger") - -# Define cache keys -API_KEYS_TO_PLANS_KEY = "api_keys_to_plans" -API_KEYS_TO_SUBSCRIPTIONS_KEY = "api_keys_to_subscriptions" - - -class NotAuthorized(Exception): - """Custom NotAuthorized Class""" - - def __init__(self, message): - super().__init__(message) - self.status_code = 401 - self.message = message - - -async def get_plans_dict(api_key: str): - # Retrieve plans dict from Redis - return await redis_cache.get(key=f"{API_KEYS_TO_PLANS_KEY}:{api_key}") - # Convert Redis byte strings to regular strings and return - # return {key.decode(): value.decode() for key, value in plans_dict.items()} - - -async def add_to_api_keys_to_plans_cache(api_key: str, plans_dict: dict): - # Convert keys and values to byte strings for Redis - # plans_dict_redis = {key.encode(): str(value).encode() for key, value in plans_dict.items()} - # Set Redis hash - await redis_cache.set(key=f"{API_KEYS_TO_PLANS_KEY}:{api_key}", value=plans_dict) - - -async def get_subscriptions_dict(api_key: str): - # Retrieve subscriptions dict from Redis - return await redis_cache.get(key=f"{API_KEYS_TO_SUBSCRIPTIONS_KEY}:{api_key}") - # Convert Redis byte strings to regular strings and return - # return {key.decode(): value.decode() for key, value in subscriptions_dict.items()} - - -async def add_to_api_keys_to_subscriptions_cache(api_key: str, subscriptions_dict: dict): - # Convert keys and values to byte strings for Redis - # subscriptions_dict_redis = {key.encode(): str(value).encode() for key, value in subscriptions_dict.items()} - # Set Redis hash - await redis_cache.set(key=f"{API_KEYS_TO_SUBSCRIPTIONS_KEY}:{api_key}", value=subscriptions_dict) - - -class RateLimitExceeded(HTTPException): - def __init__(self, rate_limit: dict[str, str | int], detail: str, status_code: int): - super().__init__(detail=detail, status_code=status_code) - self.rate_limit = rate_limit - - -async def load_plans_by_api_keys() -> None: - with next(sessions) as session: - for api_key in api_keys: - client_api_model: ApiKeyModel = await ApiKeyModel.get_by_apikey(api_key=api_key, session=session) - subscription_instance: Subscriptions = client_api_model.subscription - if not subscription_instance: - return False - plan_instance: Plans = await Plans.get_plan_by_plan_id(plan_id=subscription_instance.plan_id, - session=session) - await add_to_api_keys_to_plans_cache(api_key=api_key, plans_dict=plan_instance.to_dict()) - await add_to_api_keys_to_subscriptions_cache(api_key=api_key, - subscriptions_dict=subscription_instance.to_dict()) - - -@redis_cached_ttl(ttl=ONE_DAY) -async def is_resource_authorized(path_param: str, api_key: str) -> bool: - """ - given a url and api_key check if the user can access the resource - associated with the path parameter, - can access means the plan the client is subscribed to has access to resource - and also the client is fully paid up on the plan - :param path_param: - :param api_key: - :return: True if Authorized - """ - with next(sessions) as session: - client_api_model: ApiKeyModel = await ApiKeyModel.get_by_apikey(api_key=api_key, session=session) - if client_api_model is None: - # Note: it can happen that apikey has changed since the last update - return False - - subscription: Subscriptions = client_api_model.subscription - - if not subscription: - # TODO very important to trigger an email here indicating the user has not yet paid for services - return False - is_active = await subscription.is_active(session=session) - - resource_name = await get_resource_name(path=path_param) - can_access_resource = await subscription.can_access_resource(resource_name=resource_name, session=session) - return is_active and can_access_resource - - -async def monthly_credit_available(api_key: str) -> bool: - """ - **monthly_plan_limit** - Check the subscribed plan check the monthly plan limit - check if there are still requests left - :param api_key: used to identify the user plan - :return: True if credit is available - """ - # Need to speed this function up considerably - plan_dict = await get_plans_dict(api_key=api_key) - subscription_dict = await get_subscriptions_dict(api_key=api_key) - - if plan_dict.get("plan_limit") <= subscription_dict.get("api_requests_balance"): - # if hard_limit is true then monthly credit is not available - return not plan_dict.get("plan_limit_type") == PlanType.hard_limit - return True - - -async def create_take_credit_args(api_key: str, path: str): - """ - **take_credit** - will add the arguments to the processing queue for the take_credit method - :param path: - :param api_key: - :return: None - """ - async with lock: - take_credit_queue.append(dict(api_key=api_key, path=path)) - - -async def take_credit_method(api_key: str, path: str): - """ - this method will update the request balance on the in memory - dict, and then also update the database model - :param path: - :param api_key: the api_key associated with the request - :return: None - """ - resource_name: str = await get_resource_name(path=path) - request_credit: int = resource_name_request_size.get(resource_name, 1) - subscription_dict = await get_subscriptions_dict(api_key=api_key) - - subscription_dict["api_requests_balance"] -= request_credit - await add_to_api_keys_to_subscriptions_cache(api_key=api_key, subscriptions_dict=subscription_dict) - - with next(sessions) as session: - # TO Update subscription Model - # TODO create a queue and put this in will make sense as demand for services increases - await Subscriptions.update_subscription(subscription_data=subscription_dict, session=session) - - -async def process_credit_queue(): - """ - will run on the main app, as a separate thread to process - request credit continuously - :return: - """ - while True: - if take_credit_queue: - async with lock: - args = take_credit_queue.pop() - if args: - await take_credit_method(**args) - await asyncio.sleep(5) - - -def auth_and_rate_limit(func): - # noinspection PyTypeChecker - async def return_kwargs(kwargs): - request: Request = kwargs.get('request') - api_key = request.query_params.get('api_key') - path = kwargs.get('path') - return api_key, path - - async def rate_limiter(api_key): - """ - **rate_limiter** - this only rate limits clients by api keys, - there is also a regional rate limiter and a global rate limit both created so that the gateway - does not end up taking too much traffic and is able to recover from DDOS attacks easily. - - --> the rate_limiter has a side effect of also authorizing the client based on API Keys - - this method applies the actual rate_limiter per client basis""" - # Rate Limiting Section - async with apikeys_lock: - api_keys_model_dict: dict[str, str | int] = api_keys_lookup(api_key) - now = time.monotonic() - duration: int = api_keys_model_dict.get('duration') - limit: int = api_keys_model_dict.get('rate_limit') - last_request_timestamp: float = api_keys_model_dict.get('last_request_timestamp') - # Note that APiKeysModel must be updated with plan rate_limit - if now - last_request_timestamp > duration: - api_keys_model_dict['requests_count'] = 0 - if api_keys_model_dict['requests_count'] >= limit: - time_left = last_request_timestamp + duration - now - mess: str = f"EOD Stock API - Rate Limit Exceeded. Please wait {time_left:.0f} seconds before making " \ - f"another request, or upgrade your plan to better take advantage of extra resources " \ - f"available on better plans." - - rate_limit_dict = {'duration': duration, 'rate_limit': limit, 'time_left': f"{time_left:.0f}"} - raise RateLimitExceeded(rate_limit=rate_limit_dict, status_code=status.HTTP_429_TOO_MANY_REQUESTS, - detail=mess) - # NOTE Updating number of requests and timestamp - api_keys_model_dict['requests_count'] += 1 - # noinspection PyTypeChecker - api_keys_model_dict['last_request_timestamp'] = now - api_keys[api_key] = api_keys_model_dict - - @wraps(func) - async def wrapper(*args, **kwargs): - """main wrapper""" - api_key, path = await return_kwargs(kwargs) - auth_logger.info(f"path: {path}, api_key: {api_key}") - path = f"/api/v1/{path}" - api_key_found = api_key in api_keys - if not api_key_found: - await cache_api_keys_func() # Update api_keys if the key is not found - api_key_found = api_key in api_keys - - if not api_key_found: - # user not authorized to access this routes - mess = "EOD Stock API - Invalid API Key, or Cancelled API Key please subscribe to get a valid API Key" - raise NotAuthorized(message=mess) - - # actual rate limiter - await rate_limiter(api_key) - - # Authorization Section - # Use asyncio.gather to run is_resource_authorized and monthly_credit_available concurrently - is_authorized_task = asyncio.create_task(is_resource_authorized(path_param=path, api_key=api_key)) - - monthly_credit_task = asyncio.create_task(monthly_credit_available(api_key=api_key)) - - is_authorized, monthly_credit = await asyncio.gather(is_authorized_task, monthly_credit_task) - auth_logger.info(f"is authorized: {is_authorized}, monthly credit: {monthly_credit}") - if is_authorized and monthly_credit: - return await func(*args, **kwargs) - - if not is_authorized: - mess: str = "EOD Stock API - Request not Authorized, Either you are not subscribed to any plan or you " \ - "need to upgrade your subscription" - raise NotAuthorized(message=mess) - - if not monthly_credit: - mess: str = f"EOD Stock API - Your Monthly plan request limit has been reached. " \ - f"please upgrade your plan, or buy extra Credits" - raise NotAuthorized(message=mess) - - return wrapper diff --git a/src/authorize/resources.py b/src/authorize/resources.py deleted file mode 100644 index 52ecce5..0000000 --- a/src/authorize/resources.py +++ /dev/null @@ -1,147 +0,0 @@ -ONE_DAY = 60 * 60 * 24 - -resource_paths: dict[str, str] = { - - "stocks.complete": "/api/v1/stocks", - "stocks.code": "/api/v1/stock/code/", - "stocks.options": "/api/v1/stocks/options/stock/", - "stocks.currency": "/api/v1/stocks/currency/", - "stocks.country": "/api/v1/stocks/country/", - "options.contracts": "/api/v1/stocks/contract/", - - "eod.all": "/api/v1/eod/", - - "exchange.complete": "/api/v1/exchanges", - "exchange.companies": "/api/v1/exchange/listed-companies/", - "exchange.stocks": "/api/v1/exchange/listed-stocks/", - "exchange.stocks.code": "/api/v1/stocks/exchange/code/", - "exchange.stocks.id": "/api/v1/stocks/exchange/id/", - "exchange.code": "/api/v1/exchange/code/", - "exchange.id": "/api/v1/exchange/id/", - "exchange.with_tickers.code": "/api/v1/exchange/exchange-with-tickers/code/", - - "financial_statements.balance_sheet.annual": "/api/v1/fundamentals/annual-balance-sheet/", - "financial_statements.balance_sheet.quarterly": "/api/v1/fundamentals/quarterly-balance-sheet/", - "financial_statements.company": "/api/v1/fundamentals/financial-statements/company-statement/", - "financial_statements.stock_code.date": "/api/v1/fundamentals/financial-statements/filing-date-ticker/", - "financial_statements.income_statement.id": "/api/v1/fundamentals/financials/income-statements/", - "financial_statements.stock_code.date_range": "/api/v1/fundamentals/financial-statements/ticker-date-range/", - "financial_statements.term": "/api/v1/fundamentals/financial-statements/by-term", - "financial_statements.year": "/api/v1/fundamentals/financial-statements/exchange-year/", - - "fundamentals.complete": "/api/v1/fundamental/company/", - "fundamentals.general": "/api/v1/fundamental/general", - "fundamentals.analyst_ranks.exchange": "/api/v1/fundamentals/exchange-analyst-rankings/exchange-code/", - "fundamentals.insider.stock_code": "/api/v1/fundamentals/company-insider-transactions/stock-code/", - "fundamentals.tech_indicators.stock_code": "/api/v1/fundamentals/tech-indicators-by-company/stock-code/", - "fundamentals.tech_indicators.exchange_code": "/api/v1/fundamentals/tech-indicators-by-exchange/exchange-code/", - "fundamentals.valuations.stock_code": "/api/v1/fundamentals/company-valuations/stock-code/", - "fundamentals.valuations.exchange": "/api/v1/fundamentals/exchange-valuations/exchange-code/", - "fundamentals.company_details.id": "/api/v1/fundamentals/company-details/id/", - "fundamentals.company_details.stock_code": "/api/v1/fundamentals/company-details/stock/", - "fundamentals.highlights.id": "/api/v1/fundamentals/highlights/id/", - "fundamentals.company_address.id": "/api/v1/fundamentals/company-address/id/", - "fundamentals.highlights.stock_code": "/api/v1/fundamentals/highlights/stock/", - "fundamentals.company_address.stock_code": "/api/v1/fundamentals/company-address/stock/", - - "news.article": "/api/v1/news/article/", - "news.articles.bound": "/api/v1/news/articles-bounded/", - "news.articles.date": "/api/v1/news/articles-by-date/", - "news.articles.publisher": "/api/v1/news/articles-by-publisher/", - "news.articles.stock_code": "/api/v1/news/articles-by-ticker/", - "news.articles.paged": "/api/v1/news/articles-by-page/", - "news.publishers": "/api/v1/news/list-publishers", - "news.articles.exchange.paged": "/api/v1/news/articles-by-exchange/", - - "social.trend_setters.stock_code": "/api/v1/sentiment/trend-setters/stock/", - - "sentiment_analysis.stock_code": "/api/v1/sentiment/trending/stock/", - "sentiment_analysis.tweeter.stock_code": "/api/v1/sentiment/tweet/stock/", - -} - - -def path_to_resource() -> dict[str, str]: - """ - reverses the resource to path dictionary - into a path to resource - :return: - """ - _dict = dict() - - for key, value in resource_paths.items(): - _dict.update({value: key}) - return _dict - - -async def get_resource_name(path: str) -> str: - path_dict = path_to_resource() - i = 1 - while len(path.split("/")) > 2: - try: - print(path) - return path_dict[path] - except KeyError: - path = "/".join(path.split("/")[:-i]) - i += 1 - path = f"{path}/" - return None - -resource_name_request_size: dict[str, int] = { - - "stocks.code": 1, - "stocks.options": 1, - "stocks.complete": 25, - "stocks.currency": 25, - "stocks.country": 25, - - "exchange.code": 1, - "exchange.id": 1, - "options.contracts": 5, - "exchange.with_tickers.code": 15, - "exchange.complete": 2, - "exchange.companies": 3, - "exchange.stocks": 10, - "exchange.stocks.code": 10, - "exchange.stocks.id": 10, - - "eod.all": 15, - - "financial_statements.balance_sheet.annual": 15, - "financial_statements.balance_sheet.quarterly": 15, - "financial_statements.tech_indicators.exchange_code": 25, - "financial_statements.term": 25, - "financial_statements.year": 25, - "financial_statements.stock_code.date": 25, - "financial_statements.income_statement.id": 5, - "financial_statements.stock_code.date_range": 15, # X result size - "financial_statements.company": 12, - - "fundamentals.general": 10, - "fundamentals.complete": 15, - "fundamentals.analyst_ranks": 25, - "fundamentals.insider.stock_code": 3, - "fundamentals.tech_indicators.stock_code": 3, - "fundamentals.valuations.stock_code": 3, - "fundamentals.tech-indicators.exchange": 25, - "fundamentals.company": 12, - "fundamentals.highlights.id": 3, - "fundamentals.company_address.id": 1, - "fundamentals.company_details": 5, - "fundamentals.highlights.stock_code": 2, - "fundamentals.company_address.stock_code": 1, - "fundamentals.valuations.exchange": 25, - - "news.article": 1, - "news.articles.bound": 1, # 1 x bound size - "news.articles.date": 3, - "news.articles.publisher": 5, - "news.articles.stock_code": 5, - "news.articles.paged": 5, - "news.publishers": 3, - "news.articles.exchange.paged": 25, - - "sentiment_analysis.stock_code": 3, # X result size - "sentiment_analysis.tweeter.stock_code": 3, # x result size - "social.trend_setters.stock_code": 3, # X result size, -} diff --git a/src/bootstrap.py b/src/bootstrap.py deleted file mode 100644 index 13ee872..0000000 --- a/src/bootstrap.py +++ /dev/null @@ -1,12 +0,0 @@ - - -def create_tables(): - from src.database.apikeys.keys import ApiKeyModel - from src.database.account.account import Account - from src.database.plans.plans import Subscriptions, Plans, Payments, Invoices - ApiKeyModel.create_if_not_exists() - Account.create_if_not_exists() - Subscriptions.create_if_not_exists() - Plans.create_if_not_exists() - Payments.create_if_not_exists() - Invoices.create_if_not_exists() diff --git a/src/cache/__init__.py b/src/cache/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/cache/aio.py b/src/cache/aio.py deleted file mode 100644 index 98bb3a4..0000000 --- a/src/cache/aio.py +++ /dev/null @@ -1,91 +0,0 @@ -import ast -import threading -from typing import Optional - -from aiocache.backends.memory import SimpleMemoryBackend as MemoryBackend -from aiocache.backends.redis import RedisBackend - -from src.config import config_instance -from src.utils.my_logger import init_logger -from src.utils.utils import camel_to_snake - -MEM_CACHE_SIZE = config_instance().CACHE_SETTINGS.MAX_CACHE_SIZE -EXPIRATION_TIME = config_instance().CACHE_SETTINGS.CACHE_DEFAULT_TIMEOUT - - -class Cache: - """ - A class to handle caching of data, both in-memory and in Redis. - The class provides thread-safe caching and automatic cache eviction when full. - - :param max_size: The maximum size of the in-memory cache. The default is set in the config file. - :param expiration_time: The expiration time of each cache entry, in seconds. The default is set in the config - file. - :param use_redis: Indicates whether to use a Redis cache. The default is False. - """ - - def __init__( - self, - cache_name: str = "mem", - max_size: int = MEM_CACHE_SIZE, - expiration_time: int = EXPIRATION_TIME, - use_redis: bool = False, - redis_host: Optional[str] = None, - redis_port: Optional[int] = None, - redis_password: Optional[str] = None, - ): - """ - Initializes the cache and creates a cacheio client with either a Redis backend or a Memory backend. - - If Redis fails, the cache will fall back to using an in-memory cache. - - :param cache_name: The name of the cache. This will be used as a prefix for Redis keys. - :param max_size: The maximum size of the in-memory cache. - :param expiration_time: The expiration time of each cache entry, in seconds. - :param use_redis: Indicates whether to use a Redis cache. - :param redis_host: The hostname of the Redis server. - :param redis_port: The port number of the Redis server. - :param redis_password: The password to use when connecting to the Redis server. - """ - self.max_size = max_size - self.expiration_time = expiration_time - self._cache_name = cache_name - self._use_redis = use_redis - self._logger = init_logger(camel_to_snake(self.__class__.__name__)) - if self._use_redis: - redis_host = config_instance().REDIS_CACHE.CACHE_REDIS_HOST - redis_port = config_instance().REDIS_CACHE.CACHE_REDIS_PORT - redis_password = config_instance().REDIS_CACHE.REDIS_PASSWORD - redis_db = config_instance().REDIS_CACHE.CACHE_REDIS_DB - try: - self._cache = RedisBackend( - endpoint=redis_host, - port=redis_port, - password=redis_password) - config_instance().DEBUG and self._logger.info("Using Redis -- Connection Successful") - except Exception: - config_instance().DEBUG and self._logger.error(msg="Redis failed to connect....") - self._use_redis = False - if not self._use_redis: - self._cache = MemoryBackend() - self._lock = threading.Lock() - - async def get(self, key: str, default=None): - with self._lock: - value = await self._cache.get(key, default=default) - if not isinstance(value, dict): - if value: - value = ast.literal_eval(value) - return value - - async def set(self, key: str, value, expire: Optional[int] = None): - with self._lock: - await self._cache.set(key, value, ttl=expire or self.expiration_time) - - async def delete(self, key: str): - with self._lock: - await self._cache.delete(key) - - async def clear(self): - with self._lock: - await self._cache.clear() diff --git a/src/cache/cache.py b/src/cache/cache.py deleted file mode 100644 index e4f1dc1..0000000 --- a/src/cache/cache.py +++ /dev/null @@ -1,56 +0,0 @@ -import functools - -from src.cache.custom import Cache - - -async def create_key(method: str, kwargs: dict[str, str | int]) -> str: - """ - used to create keys for cache redis handler - """ - if not kwargs: - _key = "all" - else: - _key = ".".join(f"{key}={str(value)}" for key, value in kwargs.items() if value).lower() - return f"{method}.{_key}" - - -def redis_cached_ttl(ttl: int = 60 * 60 * 1): - """ - Caching decorator with a time-to-live (TTL) parameter that stores the function's return value in Redis for fast retrieval - and sets an expiration time for the cached value. - """ - - def _redis_cached(func): - @functools.wraps(func) - async def _wrapper(*args, **kwargs): - - new_kwargs = {} - for arg_index, arg_value in enumerate(args): - if isinstance(arg_value, (tuple, list)): - for i, val in enumerate(arg_value): - new_kwargs[f'arg{arg_index}_{i}'] = val - elif isinstance(arg_value, str): - new_kwargs[f'arg{arg_index}'] = arg_value - else: - # handle other data types here - pass - - new_kwargs.update({k: v for k, v in kwargs.items() if k != 'session'}) - _key = await create_key(method=func.__name__, kwargs=new_kwargs) - _data = await redis_cache.get(_key) - - if _data is None: - result = await func(*args, **kwargs) - if result: - await redis_cache.set(key=_key, value=result, ttl=ttl) - return result - return _data - - return _wrapper - - return _redis_cached - - -redis_cache = Cache(cache_name="redis", use_redis=True) -# asyncio.run(redis_cache.create_redis_pool()) -# mem_cache = Cache(cache_name="mem_cache", use_redis=False) diff --git a/src/cache/custom.py b/src/cache/custom.py deleted file mode 100644 index 5d2897c..0000000 --- a/src/cache/custom.py +++ /dev/null @@ -1,330 +0,0 @@ -import asyncio -import functools -import pickle -import threading -import time -from json import JSONDecodeError -from typing import Any, Callable - -import asyncio_redis -import redis -from redis import ConnectionError, AuthenticationError - -from src.config import config_instance -from src.utils.my_logger import init_logger -from src.utils.utils import camel_to_snake - -MEM_CACHE_SIZE = config_instance().CACHE_SETTINGS.MAX_CACHE_SIZE -EXPIRATION_TIME = config_instance().CACHE_SETTINGS.CACHE_DEFAULT_TIMEOUT - - -class RedisErrorManager: - """ - Custom Circuit Breaker Error manager for redis cache management - """ - def __init__(self, use_redis: bool = True): - self.use_redis: bool = use_redis - self._permanent_off = use_redis - - self.cache_errors: int = 0 - - self.error_threshold: int = 10 - self.min_error_threshold: int = 5 - self.initial_off_time: int = 60 - self.max_off_time: int = 3600 - self.time_since_last_error: int = 0 - - async def turn_off_redis(self, off_time: int): - self.use_redis = False - self.time_since_last_error = 0 - # additional code to shut down Redis or perform other tasks - if off_time == 0: - self._permanent_off = False - return - - await asyncio.sleep(off_time) - - async def turn_on_redis(self): - self.use_redis = True - # additional code to initialize Redis or perform other tasks - - async def check_error_threshold(self): - if self.cache_errors >= self.error_threshold and self.time_since_last_error <= self.max_off_time: - off_time = self.initial_off_time * 2 ** (self.cache_errors - self.min_error_threshold) - off_time = min(off_time, self.max_off_time) - await self.turn_off_redis(off_time) - elif self.cache_errors < self.min_error_threshold and not self.use_redis: - await self.turn_on_redis() - else: - self.time_since_last_error += 1 - - async def increment_cache_errors(self): - self.cache_errors += 1 - - async def can_use_redis(self): - return self.use_redis and self._permanent_off - - -class Cache: - """ - A class to handle caching of data, both in-memory and in Redis. - The class provides thread-safe caching and automatic cache eviction when full. - - :param max_size: The maximum size of the in-memory cache. The default is set in the config file. - :param expiration_time: The expiration time of each cache entry, in seconds. The default is set in the config - file. - :param use_redis: Indicates whether to use a Redis cache. The default is False. - """ - - def __init__(self, cache_name: str = "mem", max_size: int = MEM_CACHE_SIZE, expiration_time: int = EXPIRATION_TIME, - use_redis: bool = False): - """ - Initializes the cache and creates a redis client if use_redis=True. - IF Redis fails the cache will fall back to using in Memory Cache - """ - self.max_size = max_size - self.expiration_time = expiration_time - self._cache_name = cache_name - self.redis_errors = RedisErrorManager(use_redis=use_redis) - self._cache = {} - self._cache_lock = threading.Lock() - - self._logger = init_logger(camel_to_snake(self.__class__.__name__)) - - if self.redis_errors.use_redis: - redis_host = config_instance().REDIS_CACHE.CACHE_REDIS_HOST - redis_port = config_instance().REDIS_CACHE.CACHE_REDIS_PORT - password = config_instance().REDIS_CACHE.REDIS_PASSWORD - # Azure Redis not using username, may use it in the future in case we are changing to another redis client - # or using multiple redis clients at the same time - # noinspection PyUnusedLocal - username = config_instance().REDIS_CACHE.REDIS_USERNAME - try: - self._redis_client = redis.Redis(host=redis_host, port=redis_port, password=password) - self._redis_pool: asyncio_redis.Pool = None - # self._redis_client = redis.Redis(host=redis_host, port=redis_port, username=username, password=password) - config_instance().DEBUG and self._logger.info("Cache -- Redis connected") - except (ConnectionError, AuthenticationError): - config_instance().DEBUG and self._logger.error(msg="Redis failed to connect....") - self.redis_errors.turn_off_redis(off_time=0) - - @property - async def can_use_redis(self) -> bool: - return await self.redis_errors.can_use_redis() - - async def on_delete(self): - """ - run when you need to delete all keys - :return: - """ - try: - self._redis_client.flushall(asynchronous=True) - except ConnectionError: - config_instance().DEBUG and self._logger.error(msg="Redis failed to connect....") - # self.turn_off_redis() - - async def _serialize_value(self, value: Any, default=None) -> str: - """ - Serialize the given value to a json string. - """ - try: - return pickle.dumps(value) - except (JSONDecodeError, pickle.PicklingError): - config_instance().DEBUG and self._logger.error(f"Serializer Error") - return default - except TypeError: - config_instance().DEBUG and self._logger.error(f"Serializer Error") - return default - - async def _deserialize_value(self, value: any, default=None) -> Any: - """ - Deserialize the given json string to a python object. - """ - try: - return pickle.loads(value) - except (JSONDecodeError, pickle.UnpicklingError): - config_instance().DEBUG and self._logger.error(f"Error Deserializing Data") - return default - except TypeError: - config_instance().DEBUG and self._logger.error(f"Error Deserializing Data") - return default - - async def _set_mem_cache(self, key: str, value: Any, ttl: int = 0): - """ - **_set_mem_cache** - private method never call this code directly - :param key: - :param value: - :param ttl: - :return: - """ - with self._cache_lock: - # If the cache is full, remove the oldest entry - if len(self._cache) >= self.max_size: - await self._remove_oldest_entry() - - # creates a mem_cache item and set the timestamp and time to live based on given value or default - self._cache[key] = {'value': value, 'timestamp': time.monotonic(), 'ttl': ttl} - - async def set(self, key: str, value: Any, ttl: int = 0): - """ - Store the value in the cache. If the key already exists, the value is updated. - - :param key: str - a unique identifier for the cached value - :param value: Any - the value to be cached - :param ttl: int, optional - the time-to-live of the cached value in seconds; - if not provided, the default expiration time of the cache is used. - - If use_redis=True the value is stored in Redis, otherwise it is stored in-memory. - - :return: None - """ - value = await self._serialize_value(value, value) - # setting expiration time - exp_time = ttl if ttl else self.expiration_time - - if len(self._cache) > self.max_size: - await self._remove_oldest_entry() - - try: - if await self.redis_errors.can_use_redis(): - self._redis_client.set(key, value, ex=exp_time) - except (redis.exceptions.ConnectionError, redis.exceptions.TimeoutError): - # TODO -- keep a count of redis errors if they pass a thresh-hold then switch-off redis - await self.redis_errors.increment_cache_errors() - pass - try: - await self._set_mem_cache(key=key, value=value, ttl=exp_time) - except KeyError: - pass - - async def _get_memcache(self, key: str) -> Any: - """ - # called by get and set should not be called by user - :param key: - :return: - """ - entry = self._cache.get(key, {}) - if entry and time.monotonic() - entry['timestamp'] < self.expiration_time: - value = entry['value'] - else: - # await self.delete_key(key) - value = None - return value - - # 1 second timeout default - async def get(self, key: str, timeout=1) -> Any: - """ - *GET* - NOTE This method will time-out in 1 second if no value is returned from any cache - Retrieve the value associated with the given key within the allocated time. - If use_redis=True the value is retrieved from Redis, only if that key is not also on local memory. - - :param timeout: timeout in seconds, if time expires the method will return None - :param key = a key used to find the value to search for. - """ - - async def _async_redis_get(get: Callable, _key: str): - """async stub to fetch data from redis""" - return get(_key) - - try: - # Wait for the result of the memcache lookup with a timeout - value = await asyncio.wait_for(self._get_memcache(key=key), timeout=timeout) - except (asyncio.TimeoutError, KeyError): - # Timed out waiting for the memcache lookup, or KeyError - as a result of cache eviction - await self.redis_errors.increment_cache_errors() - value = None - - # will only try and return a value in redis if memcache value does not exist - if await self.redis_errors.can_use_redis() and (value is None): - try: - # Wait for the result of the redis lookup with a timeout - redis_get = functools.partial(_async_redis_get, get=self._redis_client.get) - value = await asyncio.wait_for(redis_get(_key=key), timeout=timeout) - except (redis.exceptions.TimeoutError, asyncio.TimeoutError): - # Timed out waiting for the redis lookup - config_instance().DEBUG and self._logger.error("Timeout Error Reading from redis") - await self.redis_errors.increment_cache_errors() - value = None - except redis.exceptions.ConnectionError: - config_instance().DEBUG and self._logger.error("ConnectionError Reading from redis") - await self.redis_errors.increment_cache_errors() - value = None - - return await self._deserialize_value(value, value) if value else None - - async def _remove_oldest_entry(self): - """ - **in-case memory is full remove oldest entries - Remove the oldest entry in the in-memory cache. - :return: - """ - with self._cache_lock: - # Find the oldest entry - oldest_entry = None - for key, value in self._cache.items(): - if oldest_entry is None: - oldest_entry = key - elif value['timestamp'] < self._cache[oldest_entry]['timestamp']: - oldest_entry = key - - # Remove the oldest entry from all caches - if oldest_entry is not None: - await self.delete_memcache_key(key=oldest_entry) - await self.delete_redis_key(key=oldest_entry) - - async def delete_redis_key(self, key): - """removes a single redis key""" - with self._cache_lock: - self._redis_client.delete(key) - - async def delete_memcache_key(self, key): - """ Note: do not use pop""" - with self._cache_lock: - del self._cache[key] - - async def delete_key(self, key): - await self.delete_redis_key(key) - await self.delete_memcache_key(key) - - async def clear_mem_cache(self): - """will completely empty mem cache""" - with self._cache_lock: - self._cache = {} - - async def clear_redis_cache(self): - with self._cache_lock: - self._redis_client.flushall(asynchronous=True) - - async def memcache_ttl_cleaner(self) -> int: - """ - **memcache_ttl_cleaner** - will run every ten minutes to clean up every expired mem cache item - expiration is dependent on ttl - :return: - """ - now = time.monotonic() - # Cache Items are no more than 1024 therefore this is justifiable - t_c = 0 - for key in list(self._cache.keys()): - # Time has progressed past the allocated time for this resource - # NOTE for those values where timeout is not previously declared the Assumption is 1 Hour - value = self._cache[key] - if value.get('timestamp', 0) + value.get('ttl', 60 * 60) < now: - await self.delete_memcache_key(key=key) - t_c += 1 - return t_c - - async def create_redis_pool(self): - """ - Eventually Use redis pools to store data to redis - :return: - """ - redis_host = config_instance().REDIS_CACHE.CACHE_REDIS_HOST - redis_port = config_instance().REDIS_CACHE.CACHE_REDIS_PORT - password = config_instance().REDIS_CACHE.REDIS_PASSWORD - self._redis_pool = await asyncio_redis.Pool.create(host=redis_host, - port=redis_port, - password=password, - poolsize=5) diff --git a/src/circuitbreaker/__init__.py b/src/circuitbreaker/__init__.py deleted file mode 100644 index d63a394..0000000 --- a/src/circuitbreaker/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -import asyncio - - -class ServiceUnavailableError(Exception): - def __init__(self, message): - super().__init__(message) - self.status_code = 503 - - -class CircuitBreaker: - def __init__(self, threshold, timeout): - self.threshold = threshold - self.timeout = timeout - self.failures = 0 - self.state = 'closed' - self.timer = None - - async def execute(self, func, *args, **kwargs): - if self.state == 'open': - try: - result = await func(*args, **kwargs) - self.failures = 0 - return result - except Exception as e: - self.failures += 1 - if self.failures >= self.threshold: - self.state = 'closed' - self.timer = asyncio.get_event_loop().call_later(self.timeout, self._set_half_open) - raise ServiceUnavailableError(message="Reason Service Raised an Exception See Logs for Details") - elif self.state == 'closed': - raise ServiceUnavailableError(message="Service is not available at the moment") - elif self.state == 'half-open': - try: - result = await func(*args, **kwargs) - self.failures = 0 - self.state = 'open' - return result - except Exception as e: - self.failures += 1 - if self.failures >= self.threshold: - self.state = 'closed' - self.timer = asyncio.get_event_loop().call_later(self.timeout, self._set_half_open) - raise ServiceUnavailableError(message="Reason Service Raised an Exception See Logs for details") - - def _set_half_open(self): - self.state = 'half-open' - self.failures = 0 - self.timer = None diff --git a/src/cloudflare_middleware/__init__.py b/src/cloudflare_middleware/__init__.py deleted file mode 100644 index 53576bf..0000000 --- a/src/cloudflare_middleware/__init__.py +++ /dev/null @@ -1,294 +0,0 @@ -import hashlib -import hmac -import ipaddress -import re - -import requests.exceptions -from CloudFlare import CloudFlare -from CloudFlare.exceptions import CloudFlareAPIError -from starlette.requests import Request - -from src.cache.cache import redis_cache -from src.config import config_instance -from src.make_request import send_request -from src.utils.my_logger import init_logger -from src.utils.utils import camel_to_snake - -EMAIL = config_instance().CLOUDFLARE_SETTINGS.EMAIL -TOKEN = config_instance().CLOUDFLARE_SETTINGS.TOKEN - -# Create a middleware function that checks the IP address of incoming requests and only allows requests from the -# Cloudflare IP ranges. Here's an example of how you could do this: -DEFAULT_IPV4 = ['173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', - '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', - '198.41.128.0/17', - '162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22'] - -# Patterns for known publicly acceptable routes -route_regexes = { - "home": "^/$", - "all_general_fundamentals": "^/api/v1/fundamental/general$", - "annual_or_quarterly_statements_by_stock_code": "^/api/v1/fundamentals/financial-statements/by-term/(20[1-9][0-9]|203[0-3])-(0[1-9]|1[0-2])-([0-2][0-9]|3[01])\.(20[1-9][0-9]|203[0-3])-(0[1-9]|1[0-2])-([0-2][0-9]|3[01])/[a-zA-Z0-9_-]{1,16}/\b(?:annual|quarterly)\b(?)<[^<]*)*<\/script>", # pattern for cross-site scripting (XSS) attack - "path_traversal": "\.\.[\\\/]?|\.[\\\/]{2,}", # pattern for path traversal attack - # "LDAP_injection": "[()\\\/*\x00-\x1f\x80-\xff]", # pattern for LDAP injection attack - "command_injection": ";\s*(?:sh|bash|cmd|powershell)\b", # pattern for command injection attack - "file_inclusion": "(?:file|php|zip|glob|data)\s*:", # pattern for file inclusion attack - "RCE_attack": "^.*\b(?:eval|assert|exec|system|passthru|popen|proc_open)\b.*$", - # pattern for remote code execution attack - "CSRF_attack": "^.*(GET|POST)\s+.*\b(?:referer|origin)\b\s*:\s*(?!https?://(?:localhost|127\.0\.0\.1)).*$", - # # pattern for cross-site request forgery attack - "SSRF_attack": "^.*\b(?:curl|wget|file_get_contents|fsockopen|stream_socket_client|socket_create)\b.*$", - # pattern for server-side request forgery attack - "CSWSH_attack": "^.*\b(?:Sec-WebSocket-Key|Sec-WebSocket-Accept)\b.*$", - "BRUTEFORCE_attack": "^.*\b(?:admin|root|test|user|guest)\b.*$", - "Credential_Stuffing": "(?:\badmin\b|\broot\b|\bpassword\b|\b123456\b|\bqwerty\b|\b123456789\b|\b12345678\b|\b1234567890\b|\b12345\b|\b1234567\b|\b12345678910\b|\b123123\b|\b1234\b|\b111111\b|\babc123\b|\bmonkey\b|\bdragon\b|\bletmein\b|\bsunshine\b|\bprincess\b|\b123456789\b|\bfootball\b|\bcharlie\b|\bshadow\b|\bmichael\b|\bjennifer\b|\bcomputer\b|\bsecurity\b|\btrustno1\b)", - # "Remote_File_Inclusion": "^.*(include|require)(_once)?\s*\(?\s*[\]\s*(https?:)?//", - "Clickjacking": ']*>", - "Server_Side_Template_Injection": "\{\{\s*(?:config|app|request|session|env|get|post|server|cookie|_|\|\|)\..+?\}\}", - "Business_Logic_Attacks": "^\b(?:price|discount|quantity|shipping|coupon|tax|refund|voucher|payment)\b", - "Javascript_injection": "(?:<\sscript\s>\s*|\blocation\b\s*=|\bwindow\b\s*.\slocation\s=|\bdocument\b\s*.\slocation\s=)", - "HTML_injection": "<\siframe\s|<\simg\s|<\sobject\s|<\sembed\s", - # "HTTP_PARAMETER_POLLUTION": '(?<=^|&)[\w-]+=[^&]*&[\w-]+=', - "DOM_BASED_XSS": "(?:\blocation\b\s*.\s*(?:hash|search|pathname)|document\s*.\s*(?:location|referrer).hash)" -} - - -class EODAPIFirewall: - """ - Attributes: - ----------- - _max_payload_size: int - The maximum payload size allowed by CloudFlare API. - cloud_flare: CloudFlare - An instance of CloudFlare class to interact with CloudFlare API. - ip_ranges: list - A list of IP ranges added to CloudFlare Firewall. - bad_addresses: set - A set of IP addresses marked as bad. - compiled_pat: - A compiled regex pattern to match IP addresses. - """ - - def __init__(self): - self._max_payload_size: int = 8 * 64 - try: - self.cloud_flare = CloudFlare(email=EMAIL, token=TOKEN) - # self.cloud_flare.api_from_openapi(url="https://www.eod-stock-api.site/open-api") - except CloudFlareAPIError: - pass - self.ip_ranges = [] - self.bad_addresses = set() - self.compiled_patterns = [re.compile(_regex) for _regex in route_regexes.values()] - self.compiled_bad_patterns = [re.compile(pattern) for pattern in malicious_patterns.values()] - self._logger = init_logger(camel_to_snake(self.__class__.__name__)) - - @staticmethod - async def get_client_ip(headers, request): - """will return the actual client ip address of the client making the request""" - ip = headers.get('cf-connecting-ip') or headers.get('x-forwarded-for') - return ip.split(',')[0] if ip else request.remote_addr - - @staticmethod - async def get_edge_server_ip(headers) -> str: - """obtains cloudflare edge server the request is being routed through""" - return headers.get("Host") if headers.get("Host") in ["localhost", "127.0.0.1"] else headers.get("x-real-ip") - - @staticmethod - async def get_ip_ranges() -> tuple[list[str], list[str]]: - """ - obtains a list of ip addresses from cloudflare edge servers - :return: - """ - _uri = 'https://api.cloudflare.com/client/v4/ips' - _headers = {'Accept': 'application/json', 'X-Auth-Email': EMAIL} - try: - response = await send_request(api_url=_uri, headers=_headers) - ipv4_cidrs = response.get('result', {}).get('ipv4_cidrs', DEFAULT_IPV4) - ipv6_cidrs = response.get('result', {}).get('ipv6_cidrs', []) - return ipv4_cidrs, ipv6_cidrs - except requests.exceptions.ConnectionError: - return [], [] - except CloudFlareAPIError: - return [], [] - - async def path_matches_known_route(self, path: str): - """ - **path_matches_known_route** - helps to filter out malicious paths based on regex matching - parameters: - path: this is the path parameter of the request being requested - - """ - # NOTE: that at this stage if this request is not a get then it has already been rejected - # NOTE: this will return true if there is at least one route that matches with the requested path. - # otherwise it will return false and block the request - return True - # return any(pattern.match(path) for pattern in self.compiled_patterns) - - async def is_request_malicious(self, headers: dict[str, str], url: Request.url, body: str | bytes): - # Check request for malicious patterns - if 'Content-Length' in headers and int(headers['Content-Length']) > self._max_payload_size: - # Request payload is too large, - self.bad_addresses.add(str(url)) - return True - - if body: - # Set default regex pattern for string-like request bodies - # StackOverflow attacks - payload_regex = "^[A-Za-z0-9+/]{1024,}={0,2}$" - if re.match(payload_regex, body): - self.bad_addresses.add(str(url)) - return True - - path = str(url.path) - return any((pattern.match(path) for pattern in self.compiled_bad_patterns)) - - # @redis_cached_ttl(ttl=60 * 30) - async def check_ip_range(self, ip: str) -> bool: - """ - This IP Range check only prevents direct server access from an Actual IP Address thereby - bypassing some of the security measures. - - checks if an ip address falls within range of those found in cloudflare edge servers - :param ip: - :return: - """ - if ip in self.bad_addresses: - self._logger.info(f"Found in bad addresses range : {ip}") - return False - - is_valid = any(ipaddress.ip_address(ip) in ipaddress.ip_network(ip_range) for ip_range in self.ip_ranges) - if not is_valid: - self.bad_addresses.add(ip) - self._logger.info(f"is valid became : {is_valid}") - return is_valid - - async def save_bad_addresses_to_redis(self) -> int: - """ - take a list of bad addresses and save to redis - :return: - """ - # This will store the list_of_bad_addresses for 3 hours - if self.bad_addresses: - THIRTY_DAYS = 60 * 60 * 24 * 30 - await redis_cache.set(key="list_of_bad_addresses", value=list(self.bad_addresses), ttl=THIRTY_DAYS) - return len(self.bad_addresses) - - async def restore_bad_addresses_from_redis(self): - """ - will retrieve the list of known bad addresses - :return: - """ - bad_addresses = await redis_cache.get(key="list_of_bad_addresses") or [] - for bad_address in bad_addresses: - self.bad_addresses.add(bad_address) - - async def confirm_signature(self, signature, request, secret): - """ - signature based request authentication works to further enhance gateway secyrity - by authenticating requests. - - requests without the correct signature will be assumed to be invalid requests and rejected. this ensures - that by the time requests reaches this gateway they went through other security checks. - - Only our Apps and Cloudflare Edge workers can sign requests. - """ - url = request.url - method = request.method.upper() - headers = request.headers - message: str = f"{method}{url}{headers}{secret}" - expected_signature = await self.sha256(message) - return hmac.compare_digest(signature, expected_signature) - - @staticmethod - async def sha256(message: str) -> str: - """ - convert a string to byte - convert to a digest using sha256 algo from hashlib then return a hex string - :param message: - :return: - """ - return hashlib.sha256(message.encode('utf-8')).digest().hex() - - async def create_signature(self, response, url: str, secret: str) -> str: - """ - creates a signature based request authentication - :param response: - :param url: - :param secret: - :return: - """ - method = response.method.upper() - headers = response.headers - message = f"{method}{url}{headers}{secret}" - return await self.sha256(message) diff --git a/src/config/__init__.py b/src/config/__init__.py deleted file mode 100644 index a30814f..0000000 --- a/src/config/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from src.config.config import config_instance - diff --git a/src/config/config.py b/src/config/config.py deleted file mode 100644 index fb95ba0..0000000 --- a/src/config/config.py +++ /dev/null @@ -1,151 +0,0 @@ -import functools -from pydantic import BaseSettings, Field, AnyHttpUrl, EmailStr - - -class DatabaseSettings(BaseSettings): - SQL_DB_URL: str = Field(..., env='SQL_DB_URL') - LOCAL_DB_URL: str = Field(..., env='LOCAL_DB_URL') - TOTAL_CONNECTIONS: int = Field(default=100) - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class ApiServers(BaseSettings): - MASTER_API_SERVER: str = Field(..., env='MASTER_API_SERVER') - SLAVE_API_SERVER: str = Field(..., env='SLAVE_API_SERVER') - SERVERS_LIST: str = Field(..., env='SERVERS_LIST') - X_API_KEY: str = Field(..., env='X_API_KEY') - X_SECRET_TOKEN: str = Field(..., env='X_SECRET_TOKEN') - X_RAPID_SECRET: str = Field(..., env='X_RAPID_SECRET') - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class Logging(BaseSettings): - filename: str = Field(default="eod_stock_api_gateway.logs") - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class RedisSettings(BaseSettings): - """ - # NOTE: maybe should use Internal pydantic Settings - # TODO: please finalize Redis Cache Settings """ - CACHE_TYPE: str = Field(..., env="CACHE_TYPE") - CACHE_REDIS_HOST: str = Field(..., env="CACHE_REDIS_HOST") - CACHE_REDIS_PORT: int = Field(..., env="CACHE_REDIS_PORT") - REDIS_PASSWORD: str = Field(..., env="REDIS_PASSWORD") - REDIS_USERNAME: str = Field(..., env="REDIS_USERNAME") - CACHE_REDIS_DB: str = Field(..., env="CACHE_REDIS_DB") - CACHE_REDIS_URL: str = Field(..., env="MICROSOFT_REDIS_URL") - CACHE_DEFAULT_TIMEOUT: int = Field(default=60 * 60 * 6) - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class CacheSettings(BaseSettings): - """Google Mem Cache Settings""" - # NOTE: TO USE Flask_cache with redis set Cache type to redis and setup CACHE_REDIS_URL - CACHE_TYPE: str = Field(..., env="CACHE_TYPE") - CACHE_DEFAULT_TIMEOUT: int = Field(default=60 * 60 * 3) - MEM_CACHE_SERVER_URI: str = Field(default="") - CACHE_REDIS_URL: str = Field(..., env="CACHE_REDIS_URL") - MAX_CACHE_SIZE: int = Field(default=1024) - USE_CLOUDFLARE_CACHE: bool = Field(default=True) - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class EmailSettings(BaseSettings): - SMTP_SERVER: str = Field(..., env="SMTP_SERVER") - SMTP_PORT: int = Field(..., env="SMTP_PORT") - SENDGRID_API_KEY: str = Field(..., env="SENDGRID_API_KEY") - ADMIN: str = Field(default="noreply@eod-stock-api.site") - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class PayPalSettings(BaseSettings): - MODE: str = Field(default="live") - CLIENT_ID: str = Field(..., env="PAYPAL_CLIENT_ID") - CLIENT_SECRET: str = Field(..., env="PAYPAL_CLIENT_SECRET") - BEARER_TOKEN: str = Field(..., env="PAYPAL_BEARER_TOKEN") - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class CloudflareSettings(BaseSettings): - EMAIL: str = Field(..., env="CLOUDFLARE_EMAIL") - TOKEN: str = Field(..., env="CLOUDFLARE_TOKEN") - CLOUDFLARE_SECRET_KEY: str = Field(..., env="CLOUDFLARE_SECRET_KEY") - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class AppSettings(BaseSettings): - app_name: str = "EOD-STOCK-API - API GATEWAY" - app_description: str = """ - **Stock Marketing & Financial News API**, - - provides end-of-day stock information for multiple exchanges around the world. - With this API, you can retrieve data for a specific stock at a given date, or for a range of dates. and also get access - to companies fundamental data, financial statements, social media trending stocks by sentiment, and also the ability to create a summary of the Financial - News Related to a certain stock or company and its sentiment. - """ - version: str = "1.0.0" - terms_of_service: AnyHttpUrl = "https://eod-stock-api.site/terms" - contact_name: str = "EOD-STOCK-API" - contact_url: AnyHttpUrl = "https://eod-stock-api.site/contact" - contact_email: EmailStr = "info@eod-stock-api.site" - license_name: str = "Apache 2.0" - license_url: AnyHttpUrl = "https://www.apache.org/licenses/LICENSE-2.0.html" - docs_url: str = None - openapi_url: str = None - redoc_url: str = None - - class Config: - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -class Settings(BaseSettings): - SECRET_KEY: str = Field(..., env="SECRET_TOKEN") - DATABASE_SETTINGS: DatabaseSettings = DatabaseSettings() - API_SERVERS: ApiServers = ApiServers() - DEVELOPMENT_SERVER_NAME: str = Field(default="DESKTOP-T9V7F59") - LOGGING: Logging = Logging() - DEBUG: bool = True - PREFETCH_INTERVAL: int = 5 - REDIS_CACHE: RedisSettings = RedisSettings() - CACHE_SETTINGS: CacheSettings = CacheSettings() - EMAIL_SETTINGS: EmailSettings = EmailSettings() - PAYPAL_SETTINGS: PayPalSettings = PayPalSettings() - CLOUDFLARE_SETTINGS: CloudflareSettings = CloudflareSettings() - FERNET_KEY: bytes = Field(..., env="FERNET_KEY") - APP_SETTINGS: AppSettings = AppSettings() - - class Config: - case_sensitive = True - env_file = '.env.development' - env_file_encoding = 'utf-8' - - -@functools.lru_cache -def config_instance(): - return Settings() diff --git a/src/const.py b/src/const.py deleted file mode 100644 index 7e9634d..0000000 --- a/src/const.py +++ /dev/null @@ -1,80 +0,0 @@ -UUID_LEN: int = 16 -STR_LEN: int = 255 -NAME_LEN: int = 128 -EMAIL_LEN: int = 255 -CELL_LEN: int = 13 -API_KEY_LEN: int = 64 - -user_agents = [ - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)", - "Opera/9.20 (Windows NT 6.0; U; en)", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.1) Gecko/20061205 Iceweasel/2.0.0.1 (Debian-2.0.0.1+dfsg-2)", - "Opera/10.00 (X11; Linux i686; U; en) Presto/2.2.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; he-IL) AppleWebKit/528.16 (KHTML, like Gecko) Version/4.0 Safari/528.16", - "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101209 Firefox/3.6.13", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100804 Gentoo Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100809 Fedora/3.6.7-1.fc14 Firefox/3.6.7", - "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", - "YahooSeeker/1.2 (compatible; Mozilla 4.0; MSIE 5.5; yahooseeker at yahoo-inc dot com ; http://help.yahoo.com/help/us/shop/merchant/)", - "Mozilla/5.0 (Windows NT 5.1) Gecko/20100101 Firefox/14.0 Opera/12.0", - "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00", - "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14", - "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14", - "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; da-dk) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1", - "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", - "Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; chromeframe/12.0.742.112)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)", - "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", - "Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.0; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130331 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130401 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130328 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130401 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130330 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130331 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130401 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.2; rv:21.0) Gecko/20130326 Firefox/21.0", - "Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:22.0) Gecko/20130328 Firefox/22.0", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36", - "Mozilla/5.0 (compatible; MSIE 9.0; AOL 9.7; AOLBuild 4343.19; Windows NT 6.1; WOW64; Trident/5.0; FunWebProducts)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; Acoo Browser 1.98.744; .NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:29.0) Gecko/20100101 Firefox/29.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", - "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", - "Mozilla/5.0 (Android; Mobile; rv:29.0) Gecko/29.0 Firefox/29.0", - "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36", - "Mozilla/5.0 (iPad; CPU OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) CriOS/34.0.1847.18 Mobile/11B554a Safari/9537.53", - "Mozilla/5.0 (iPad; CPU OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53"] diff --git a/src/database/__init__.py b/src/database/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/account/__init__.py b/src/database/account/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/account/account.py b/src/database/account/account.py deleted file mode 100644 index ce28382..0000000 --- a/src/database/account/account.py +++ /dev/null @@ -1,119 +0,0 @@ -import hashlib -import secrets - -from pydantic import BaseModel -from sqlalchemy import Column, String, inspect, Boolean -from sqlalchemy.orm import relationship - -from src.authorize.authorize import NotAuthorized -from src.const import UUID_LEN, NAME_LEN, EMAIL_LEN, STR_LEN, CELL_LEN -from src.database.apikeys.keys import ApiKeyModel -from src.database.database_sessions import Base, sessionType, engine -from src.utils.utils import create_id - - -class TwoFactorLoginData(BaseModel): - email: str - code: str - - -class Account(Base): - """ - User Account ORM - """ - __tablename__ = "accounts" - uuid: str = Column(String(UUID_LEN), primary_key=True, index=True) - first_name: str = Column(String(NAME_LEN), index=True) - second_name: str = Column(String(NAME_LEN), index=True) - surname: str = Column(String(NAME_LEN), index=True) - email: str = Column(String(EMAIL_LEN), index=True, unique=True) - cell: str = Column(String(CELL_LEN), index=True, unique=True) - password_hash: str = Column(String(STR_LEN), index=True) - is_admin: bool = Column(Boolean, default=False) - is_deleted: bool = Column(Boolean, default=False) - apikey = relationship('ApiKeyModel', uselist=False, foreign_keys=[ApiKeyModel.uuid]) - - def __init__(self, uuid: str, first_name: str, second_name: str, surname: str, email: str, cell: str, - password: str): - self.uuid = uuid or create_id() - self.first_name = first_name - self.second_name = second_name - self.surname = surname - self.email = email - self.cell = cell - self.password = password - - def __bool__(self) -> bool: - return bool(self.uuid) - - @classmethod - def create_if_not_exists(cls): - if not inspect(engine).has_table(cls.__tablename__): - Base.metadata.create_all(bind=engine) - - @property - def names(self) -> str: - return f"{self.first_name}, {self.second_name}, {self.surname}" - - @property - def contact_details(self) -> str: - return f"Cell: {self.cell}, Email: {self.email}" - - @property - def password(self) -> str: - """ - Raises an AttributeError if someone tries to get the password directly - """ - return self.password_hash - - @password.setter - def password(self, plaintext_password: str) -> None: - """ - Hashes and sets the user's password - """ - self.password_hash = hashlib.sha256(plaintext_password.encode('utf-8')).hexdigest() - - def to_dict(self) -> dict[str, str]: - init_dict = { - "uuid": self.uuid, - "first_name": self.first_name, - "second_name": self.second_name, - "surname": self.surname, - "email": self.email, - "cell": self.cell, - "is_admin": self.is_admin - } - if self.apikey: - init_dict["apikey"] = self.apikey.to_dict() - return init_dict - - @classmethod - async def get_by_uuid(cls, uuid: str, session: sessionType): - return session.query(cls).filter(cls.uuid == uuid).first() - - @classmethod - async def get_by_email(cls, email: str, session: sessionType): - return session.query(cls).filter(cls.email == email).first() - - @classmethod - async def login(cls, username: str, password: str, session: sessionType): - # Get the user with the specified email address - user: Account = session.query(cls).filter(cls.email == username.casefold()).first() - - if (user is None) or user.is_deleted: - raise NotAuthorized(message="You are not authorized to login to this account") - - # Hash the entered password using a secure hash function (SHA-256 in this example) - password_hash = hashlib.sha256(password.encode('utf-8')).hexdigest() - # Compare the hashed password to the stored hash using secrets.compare_digest, - # and return either the user object or None depending on the result - return user if user and secrets.compare_digest(password_hash, user.password) else None - - @classmethod - async def fetch_all(cls, session: sessionType) -> list[any]: - """ - Returns a complete list of users - :param session: - :return: - """ - return session.query(cls).all() diff --git a/src/database/apikeys/__init__.py b/src/database/apikeys/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/apikeys/keys.py b/src/database/apikeys/keys.py deleted file mode 100644 index 5279c12..0000000 --- a/src/database/apikeys/keys.py +++ /dev/null @@ -1,145 +0,0 @@ -from __future__ import annotations - -import asyncio -import datetime - -import pymysql -from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, inspect -from sqlalchemy.orm import relationship -from typing_extensions import Self - -from src.database.database_sessions import sessions, Base, sessionType, engine -from src.database.plans.plans import Subscriptions, Plans -from src.utils.utils import create_id, create_api_key - -apikeys_lock = asyncio.Lock() -# Define a dict to store API Keys and their rate rate_limit data -# Cache tp store API KEys -# TODO use Async QUEUE -api_keys: dict[str, dict[str, int]] = {} -ONE_MINUTE = 60 * 60 -UUID_LEN: int = 16 -STR_LEN: int = 255 -NAME_LEN: int = 128 -EMAIL_LEN: int = 255 -CELL_LEN: int = 13 -API_KEY_LEN: int = 64 - - -class ApiKeyModel(Base): - """ - api key model - """ - __tablename__ = 'eod_api_keys' - uuid: str = Column(String(UUID_LEN), ForeignKey("accounts.uuid"), index=True) - api_key: str = Column(String(API_KEY_LEN), primary_key=True, index=True) - duration: int = Column(Integer) - rate_limit: int = Column(Integer) - is_active: bool = Column(Boolean, default=True) - subscription = relationship("Subscriptions", uselist=False, foreign_keys=[Subscriptions.uuid]) - - def __init__(self, uuid: str, api_key: str, duration: int, rate_limit: int): - self.uuid = uuid - self.api_key = api_key - self.duration = duration - self.rate_limit = rate_limit - - @classmethod - def create_if_not_exists(cls): - if not inspect(engine).has_table(cls.__tablename__): - Base.metadata.create_all(bind=engine) - - def to_dict(self) -> dict[str, str]: - init_dict = { - "uuid": self.uuid, - "api_key": self.api_key, - "duration": self.duration, - "rate_limit": self.rate_limit, - "is_active": self.is_active, - "subscription": self.subscription.to_dict() - } - if self.subscription: - init_dict['subscription'] = self.subscription.to_dict() - return init_dict - - @classmethod - async def get_by_apikey(cls, api_key: str, session: sessionType) -> ApiKeyModel: - """ - :param api_key: - :param session: - :return: - """ - return session.query(cls).filter(cls.api_key == api_key).first() - - async def update_rate_limits_from_plan(self, plan_data: dict[str, str | bool | int]) -> Self: - """ - - :param plan_data: - :return: - """ - self.rate_limit = plan_data.get("rate_limit") - self.duration = 60 * 60 * 1 # ONE hOUR FOR ALL PLANS - self.subscription.api_requests_balance = plan_data.get("plan_limit") - return self - - @classmethod - async def get_all_active(cls, session: sessionType): - return session.query(cls).filter_by(is_active=True).all() - - -async def cache_api_keys() -> int: - with next(sessions) as session: - db_keys = await ApiKeyModel.get_all_active(session=session) - async with apikeys_lock: - api_keys.update({db_key.api_key: {'requests_count': 0, - 'last_request_timestamp': 0, - 'duration': db_key.duration, - 'rate_limit': db_key.rate_limit} for db_key in db_keys}) - return len(db_keys) - - -async def create_admin_key(): - """ - this is only for the purposes of testing - :return: - """ - from src.database.account.account import Account - with next(sessions) as session: - - _uuid = create_id(size=UUID_LEN) - _api_key = create_api_key() - first_name = "John" - second_name = "Peters" - surname = "Smith" - email = "info@eod-stock-api.site" - cell = "0711863234" - is_admin = True - admin_user = Account(uuid=_uuid, first_name=first_name, - second_name=second_name, surname=surname, email=email, - cell=cell, is_admin=is_admin, password="MobiusCrypt5627084@") - - api_key = ApiKeyModel(uuid=_uuid, - api_key=_api_key, - duration=ONE_MINUTE * 60, - rate_limit=30, - is_active=True) - - sub_id = create_id(UUID_LEN) - # await create_plans() - plans = await Plans.get_all_plans(session=session) - _enterprise_plan: Plans = [plan for plan in plans if plan.plan_name == "ENTERPRISE"][0] - subscription = Subscriptions(uuid=_uuid, subscription_id=sub_id, plan_id=_enterprise_plan.plan_id, - time_subscribed=datetime.datetime.now().timestamp(), payment_day="31", - api_requests_balance=_enterprise_plan.plan_limit) - try: - session.add(admin_user) - session.commit() - session.flush() - session.add(subscription) - session.add(api_key) - session.commit() - session.flush() - - except pymysql.err.OperationalError as e: - # TODO log errors - pass diff --git a/src/database/contact/__init__.py b/src/database/contact/__init__.py deleted file mode 100644 index d7e3324..0000000 --- a/src/database/contact/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from src.database.contact.contact import Contacts \ No newline at end of file diff --git a/src/database/contact/contact.py b/src/database/contact/contact.py deleted file mode 100644 index ff2a755..0000000 --- a/src/database/contact/contact.py +++ /dev/null @@ -1,68 +0,0 @@ -import time -from datetime import datetime -from sqlalchemy import Column, String, inspect, Float, ForeignKey, Integer - -from src.const import UUID_LEN, NAME_LEN, EMAIL_LEN, STR_LEN -from src.database.database_sessions import Base, engine -from src.utils.utils import create_id - - -class Contacts(Base): - """ - ORM Model for Contacts - """ - __tablename__ = 'contact_messages' - uuid: str = Column(String(UUID_LEN), index=True, nullable=True) - contact_id: str = Column(String(UUID_LEN), primary_key=True, index=True) - name: str = Column(String(NAME_LEN), index=True) - email: str = Column(String(EMAIL_LEN), index=True) - message: str = Column(String(STR_LEN)) - timestamp = Column(Float, index=True) - - def init(self, contact_id: str, name: str, email: str, message: str, timestamp: float, uuid: str | None = None): - """ - - :param uuid: - :param contact_id: - :param name: - :param email: - :param message: - :param timestamp: - :return: - """ - self.uuid = uuid - self.contact_id = contact_id or create_id(UUID_LEN) - if (name is None) or (email is None) or (message is None): - raise ValueError("Name, Email, and Message are required") - self.name, self.email, self.message = name, email, message - - self.timestamp = timestamp if timestamp and isinstance(timestamp, float | int) else time.monotonic() - - @property - def datetime(self) -> datetime: - return datetime.fromtimestamp(self.timestamp) - - @classmethod - def create_if_not_exists(cls): - if not inspect(engine).has_table(cls.__tablename__): - Base.metadata.create_all(bind=engine) - - def to_dict(self) -> dict[str, str | int | float]: - """will return a dict for contacts""" - return { - 'uuid': self.uuid, - 'contact_id': self.contact_id, - 'name': self.name, - 'email': self.email, - 'message': self.message, - 'datetime': self.datetime.strftime('%Y-%m-%dT%H:%M:%'), - 'timestamp': self.timestamp} - - -class ContactControl(Base): - """ - once a contact message has been responded to the responses and a way to control that conversation - will be tracked in this class - """ - __tablename__ = 'contact_control' - id: str = Column(Integer, primary_key=True) \ No newline at end of file diff --git a/src/database/database_sessions.py b/src/database/database_sessions.py deleted file mode 100644 index 4b3e09e..0000000 --- a/src/database/database_sessions.py +++ /dev/null @@ -1,24 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker, Session - -from src.config import config_instance - -DATABASE_URL = config_instance().DATABASE_SETTINGS.SQL_DB_URL -engine = create_engine(DATABASE_URL) - -# Define a SQLAlchemy model for API Keys -Base = declarative_base() -sessionType = Session - -SESSIONS_PRE_CACHE_SIZE = 150 - - -def get_session() -> Session: - while True: - for session in [sessionmaker(bind=engine) for _ in range(SESSIONS_PRE_CACHE_SIZE)]: - yield session() - - -sessions = get_session() - diff --git a/src/database/plans/__init__.py b/src/database/plans/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/plans/init_plans.py b/src/database/plans/init_plans.py deleted file mode 100644 index eb3f021..0000000 --- a/src/database/plans/init_plans.py +++ /dev/null @@ -1,306 +0,0 @@ -""" - -""" -from dataclasses import field - -from numba import jit -from pydantic.dataclasses import dataclass - -from src.const import UUID_LEN -from src.database.database_sessions import sessions -from src.database.plans.plans import Plans, PlanType -from src.utils.utils import create_id - - -@dataclass(frozen=True) -class PlanNames: - BASIC: str = "BASIC" - PROFESSIONAL: str = "PROFESSIONAL" - BUSINESS: str = "BUSINESS" - ENTERPRISE: str = "ENTERPRISE" - - -@dataclass(frozen=True) -class ChargeAmounts: - """ - load this from database in future - """ - BASIC: int = 0 - PROFESSIONAL: int = 1999 - BUSINESS: int = 4999 - ENTERPRISE: int = 9999 - - -@dataclass(frozen=True) -class PlanDescriptions: - BASIC: str = "Entry Level plan for development purposes" - PROFESSIONAL: str = "For a Professional project with modest traffic you can use our professional plan" - BUSINESS: str = "Preferred plan for a business solutions" - ENTERPRISE: str = "Enterprise level solution intended for high availability and very low latency" - - -def get_basic_resources() -> set[str]: - """ - should have an options of looking into the database to determine if overriding resource parameters - are not stored - :return: - """ - return { - "stocks.code", - "stocks.options", - "exchange.complete", - "exchange.stocks.code", - "exchange.stocks.id", - "fundamentals.company_details.stock_code", - "eod.all", - "news.articles.stock_code" - } - - -@jit -def get_professional_resources() -> set[str]: - """ - should add an ability to retrieve professional resources from - the database - :return: - """ - return { - "stocks.code", - "stocks.options", - "stocks.currency", - "stocks.country", - - "options.contracts", - - "eod.all", - - "exchange.complete", - "exchange.companies", - "exchange.stocks", - "exchange.stocks.code", - "exchange.stocks.id", - "exchange.code", - "exchange.id", - "exchange.with_tickers.code", - - "financial_statements.balance_sheet.annual", - "financial_statements.balance_sheet.quarterly", - "financial_statements.stock_code.date", - - "fundamentals.general", - "fundamentals.company_details.stock_code", - "fundamentals.company_details.id", - "fundamentals.analyst_ranks.stock_code", - "fundamentals.tech_indicators.stock_code", - "fundamentals.valuations.stock_code", - "fundamentals.highlights.stock_code", - "fundamentals.company_address.stock_code", - "fundamentals.highlights.stock_code", - - "news.article", - "news.articles.stock_code", - - "sentiment_analysis.stock_code" - } - - -@jit -def get_business_resources(): - return { - "stocks.complete", - "stocks.code", - "stocks.options", - "stocks.currency", - "stocks.country", - "options.contracts", - - "eod.all", - - "exchange.complete", - "exchange.companies", - "exchange.stocks", - "exchange.stocks.code", - "exchange.stocks.id", - "exchange.code", - "exchange.id", - "exchange.with_tickers.code", - - "financial_statements.balance_sheet.annual", - "financial_statements.balance_sheet.quarterly", - "financial_statements.company", - "financial_statements.stock_code.date", - "financial_statements.income_statement.id", - "financial_statements.stock_code.date_range", - "financial_statements.term", - "financial_statements.year", - - "fundamentals.complete", - "fundamentals.general", - "fundamentals.analyst_ranks.exchange", - "fundamentals.insider.stock_code", - "fundamentals.tech_indicators.stock_code", - "fundamentals.tech_indicators.exchange_code", - "fundamentals.valuations.stock_code", - "fundamentals.valuations.exchange", - "fundamentals.company_details.id", - "fundamentals.company_details.stock_code", - "fundamentals.highlights.id", - "fundamentals.company_address.id", - "fundamentals.highlights.stock_code", - "fundamentals.company_address.stock_code", - - "news.article", - "news.articles.bound", - "news.articles.date", - "news.articles.publisher", - "news.articles.stock_code", - - "social.trend_setters.stock_code", - - "sentiment_analysis.stock_code", - "sentiment_analysis.tweeter.stock_code" - } - - -@jit -def get_enterprise_resources(): - return { - "stocks.complete", - "stocks.code", - "stocks.options", - "stocks.currency", - "stocks.country", - "options.contracts", - - "eod.all", - - "exchange.complete", - "exchange.companies", - "exchange.stocks", - "exchange.stocks.code", - "exchange.stocks.id", - "exchange.code", - "exchange.id", - "exchange.with_tickers.code", - - "financial_statements.balance_sheet.annual", - "financial_statements.balance_sheet.quarterly", - "financial_statements.company", - "financial_statements.stock_code.date", - "financial_statements.income_statement.id", - "financial_statements.stock_code.date_range", - "financial_statements.term", - "financial_statements.year", - - "fundamentals.complete", - "fundamentals.general", - "fundamentals.analyst_ranks.exchange", - "fundamentals.insider.stock_code", - "fundamentals.tech_indicators.stock_code", - "fundamentals.tech_indicators.exchange_code", - "fundamentals.valuations.stock_code", - "fundamentals.valuations.exchange", - "fundamentals.company_details.id", - "fundamentals.company_details.stock_code", - "fundamentals.highlights.id", - "fundamentals.company_address.id", - "fundamentals.highlights.stock_code", - "fundamentals.company_address.stock_code", - - "news.article", - "news.articles.bound", - "news.articles.date", - "news.articles.publisher", - "news.articles.stock_code", - - "social.trend_setters.stock_code", - - "sentiment_analysis.stock_code", - "sentiment_analysis.tweeter.stock_code" - } - - -@dataclass(frozen=True) -class PlanResources: - BASIC: set[str] = field(default_factory=lambda: get_basic_resources()) - PROFESSIONAL: set[str] = field(default_factory=lambda: get_professional_resources()) - BUSINESS: set[str] = field(default_factory=lambda: get_business_resources()) - ENTERPRISE: set[str] = field(default_factory=lambda: get_enterprise_resources()) - - -@dataclass(frozen=True) -class RateLimits: - # TODO Propagate the plan limits to the APIModel & Subscriptions - BASIC: tuple[int, int, int] = (10, 1_500, 0) - PROFESSIONAL: tuple[int, int, int] = (50, 10_000, 1) - BUSINESS: tuple[int, int, int] = (75, 25_000, 1) - ENTERPRISE: tuple[int, int, int] = (125, 50_000, 1) - - -async def create_plans() -> None: - """ - run once on setup - :return: - """ - - with next(sessions) as session: - session.add(create_basic()) - session.add(create_professional()) - session.add(create_business()) - session.add(create_enterprise()) - session.commit() - session.flush() - - return None - - -def create_enterprise() -> Plans: - rate_limit, plan_limit, rate_per_request = RateLimits().ENTERPRISE - return Plans(plan_id=create_id(size=UUID_LEN), - plan_name=PlanNames().ENTERPRISE, - charge_amount=ChargeAmounts().ENTERPRISE, - description=PlanDescriptions().ENTERPRISE, - resource_set=PlanResources().ENTERPRISE, - rate_limit=rate_limit, - plan_limit=plan_limit, - plan_limit_type=PlanType.soft_limit, - rate_per_request=rate_per_request) - - -def create_business() -> Plans: - rate_limit, plan_limit, rate_per_request = RateLimits().BUSINESS - return Plans(plan_id=create_id(size=UUID_LEN), - plan_name=PlanNames().BUSINESS, - charge_amount=ChargeAmounts().BUSINESS, - description=PlanDescriptions().BUSINESS, - resource_set=PlanResources().BUSINESS, - rate_limit=rate_limit, - plan_limit=plan_limit, - plan_limit_type=PlanType.soft_limit, - rate_per_request=rate_per_request) - - -def create_professional() -> Plans: - rate_limit, plan_limit, rate_per_request = RateLimits().PROFESSIONAL - return Plans(plan_id=create_id(size=UUID_LEN), - plan_name=PlanNames().PROFESSIONAL, - charge_amount=ChargeAmounts().PROFESSIONAL, - description=PlanDescriptions().PROFESSIONAL, - resource_set=PlanResources().PROFESSIONAL, - rate_limit=rate_limit, - plan_limit=plan_limit, - plan_limit_type=PlanType.soft_limit, - rate_per_request=rate_per_request) - - -def create_basic() -> Plans: - rate_limit, plan_limit, rate_per_request = RateLimits().BASIC - return Plans(plan_id=create_id(size=UUID_LEN), - plan_name=PlanNames().BASIC, - charge_amount=ChargeAmounts().BASIC, - description=PlanDescriptions().BASIC, - resource_set=PlanResources().BASIC, - rate_limit=rate_limit, - plan_limit=plan_limit, - rate_per_request=rate_per_request, - plan_limit_type=PlanType.hard_limit) diff --git a/src/database/plans/plans.py b/src/database/plans/plans.py deleted file mode 100644 index 733602e..0000000 --- a/src/database/plans/plans.py +++ /dev/null @@ -1,369 +0,0 @@ -import datetime - -from sqlalchemy import Column, String, Text, Integer, Float, Boolean, ForeignKey, inspect -from sqlalchemy.exc import NoResultFound -from sqlalchemy.orm import relationship -from typing_extensions import Self - -from src.const import UUID_LEN, NAME_LEN -from src.database.database_sessions import sessionType, Base, engine -from src.utils.utils import create_id - - -class Subscriptions(Base): - __tablename__ = "subscriptions" - subscription_id: str = Column(String(UUID_LEN), primary_key=True) - uuid: str = Column(String(UUID_LEN), ForeignKey("eod_api_keys.uuid")) - plan_id: str = Column(String(UUID_LEN), ForeignKey("plans.plan_id")) - time_subscribed: float = Column(Float) - payment_day: str = Column(String(NAME_LEN)) - _is_active: bool = Column(Boolean, default=False) - api_requests_balance: int = Column(Integer) - - approval_url: str = Column(String(255)) - paypal_id: str = Column(String(255)) - - def __init__(self, subscription_id: str, uuid: str, plan_id: str, time_subscribed: float, payment_day: str, - api_requests_balance: int, approval_url: str, paypal_id: str, is_active: bool = False): - """ - - :param subscription_id: - :param uuid: - :param plan_id: - :param time_subscribed: - :param payment_day: - :param api_requests_balance: - :param approval_url: - :param paypal_id: - :param is_active: - """ - self.subscription_id = subscription_id - self.uuid = uuid - self.plan_id = plan_id - self.time_subscribed = time_subscribed - self.payment_day = payment_day - self.api_requests_balance = api_requests_balance - self.approval_url = approval_url - self.paypal_id = paypal_id - self._is_active = is_active - - def set_is_active(self, is_active: bool): - """ - **set_is_active** - used to activate or de-activate subscription - :return: - """ - self._is_active = is_active - - @classmethod - def create_if_not_exists(cls): - if not inspect(engine).has_table(cls.__tablename__): - Base.metadata.create_all(bind=engine) - - def to_dict(self) -> dict[str, str | float | int | bool]: - return { - "subscription_id": self.subscription_id, - "uuid": self.uuid, - "plan_id": self.plan_id, - "time_subscribed": self.time_subscribed, - "payment_day": self.payment_day, - "is_active": self._is_active, - "api_requests_balance": self.api_requests_balance - } - - async def is_active(self, session: sessionType) -> bool: - """ - - Subscription is Active if _is_active is True, and plan fully paid - NOTE: there maybe other reasons to turn off subscriptions other than payments - :return: - """ - invoices = session.query(Invoices).filter(Invoices.subscription_id == self.subscription_id).all() - return False if any([invoice for invoice in invoices - if not invoice.is_paid(session=session)]) else self._is_active - - async def can_access_resource(self, resource_name: str, session: sessionType) -> bool: - """ - with a resource name checks if it can be accessed - by the subscribed plan - :param resource_name: - :param session: - :return: - """ - return session.query(Plans).filter( - Plans.plan_id == self.plan_id).first().resource_exist(resource_name=resource_name) - - @classmethod - async def create_subscription(cls, _data: dict[str, str | bool | int], session: sessionType) -> Self: - """ - Will create a subscription instance and then return - return True - :param session: - :param _data: - :return: - """ - instance = cls(**_data) - return instance - - @classmethod - async def activate(cls, subscription_id: str, session: sessionType) -> bool: - """ - - :param session: - :param subscription_id: - :return: - """ - subscription = session.query(cls).filter(cls.subscription_id == subscription_id).first() - subscription._is_active = True - session.merge(subscription) - session.commit() - - @classmethod - async def get_by_subscription_id(cls, subscription_id: str, session: sessionType) -> Self: - """ - :param subscription_id: - :param session: - :return: Self - """ - return session.query(cls).filter(cls.subscription_id == subscription_id).first() - - @classmethod - async def update_subscription(cls, subscription_data: dict[str, str | bool | int], session: sessionType) -> bool: - api_key = subscription_data.pop('api_key', None) - if not api_key: - return False # no api_key provided, can't update - - try: - subscription = session.query(cls).filter_by(uuid=api_key).one() - except NoResultFound: - return False # subscription not found for this api_key - - # update only the fields that exist on the subscription_data dictionary - for field, value in subscription_data.items(): - if hasattr(subscription, field): - setattr(subscription, field, value) - - session.merge(subscription) - session.commit() - return True # update successful - - -class PlanType: - hard_limit: str = "hard_limit" - soft_limit: str = "soft_limit" - - -class Plans(Base): - """ - Subscription Plans - """ - __tablename__ = "plans" - plan_id: str = Column(String(UUID_LEN), primary_key=True, index=True) - paypal_id: str = Column(String(255), index=True) - plan_name: str = Column(String(NAME_LEN), index=True, unique=True) - charge_amount: int = Column(Integer) # Payment Amount for this plan in Cents - description: str = Column(Text) - _resource_str: str = Column(Text) - rate_limit: int = Column(Integer) # Limit per Hour - plan_limit: int = Column(Integer) # Monthly Limit - plan_limit_type: PlanType = Column(String(10)) # Hard or Soft Limit - rate_per_request: int = Column(Integer, default=0) # in Cents - is_visible: bool = Column(Boolean, default=True) # Only visible plans are shown in the interface - subscriptions = relationship("Subscriptions", uselist=True, foreign_keys=[Subscriptions.plan_id]) - - @classmethod - def create_if_not_exists(cls): - if not inspect(engine).has_table(cls.__tablename__): - Base.metadata.create_all(bind=engine) - - @property - def resource_set(self) -> set[str]: - return set([res.lower() for res in self._resource_str.split(",")]) - - @resource_set.setter - def resource_set(self, rs_set: set[str]): - self._resource_str = ",".join(rs_set) - - def to_dict(self) -> dict[str, str | int | set[str]]: - return { - "plan_id": self.plan_id, - "paypal_id": self.paypal_id, - "plan_name": self.plan_name, - "Amount": self.charge_amount, - "description": self.description, - "resources": list(self.resource_set), - "rate_limit": self.rate_limit, - "plan_limit": self.plan_limit, - "plan_limit_type": self.plan_limit_type, - "rate_per_request": self.rate_per_request, - } - - def resource_exist(self, resource_name: str) -> bool: - """ - **resource_exist** - checks if a specific resource exist on client plan - :param resource_name: - :return: - """ - # TODO Upgrade this on database - _new_resources = ["news.articles.paged", "news.publishers", "news.articles.exchange.paged"] - if resource_name in _new_resources: - return True - return resource_name in self.resource_set - - @classmethod - async def get_plan_by_plan_id(cls, plan_id: str, session: sessionType) -> Self: - """ - given plan_id will return subscribed Plan - :param session: - :param plan_id: - :return: - """ - return session.query(cls).filter(cls.plan_id == plan_id).first() - - @classmethod - async def get_all_plans(cls, session: sessionType) -> list[Self]: - """ - - :param session: - :return: - """ - return session.query(cls).all() - - def is_hard_limit(self) -> bool: - return self.plan_limit_type == PlanType.hard_limit - - @classmethod - def fetch_all(cls, session: sessionType): - return session.query(cls).all() - - -class Payments(Base): - """ - subscription_id , links to the subscription being paid for - payment_method, indicates the method used for payments - is_success, will be true if payment is successful - time_paid, time payment was made - period_paid, the term in number days the plan was paid for, this is pre paid - """ - __tablename__ = "payments" - subscription_id: str = Column(String(UUID_LEN), ForeignKey("subscriptions.subscription_id")) - payment_id: str = Column(String(UUID_LEN), primary_key=True) - invoice_id: str = Column(String(UUID_LEN), ForeignKey("invoices.invoice_id")) - payment_method: str = Column(String(NAME_LEN)) - payment_amount: int = Column(Integer) # amount in cents - is_success: bool = Column(Boolean) - time_paid: float = Column(Float) - - def __init__(self, subscription_id: str, payment_method: str, payment_amount: int, is_success: bool = True, - invoice_id: str = None, payment_id: str = None): - - self.subscription_id = subscription_id - self.payment_method = payment_method - self.payment_amount = payment_amount - self.is_success = is_success - self.time_paid = datetime.datetime.now().timestamp() - self.invoice_id = invoice_id or create_id() - self.payment_id = payment_id or create_id() - - @classmethod - def create_if_not_exists(cls): - if not inspect(engine).has_table(cls.__tablename__): - Base.metadata.create_all(bind=engine) - - def to_dict(self) -> dict[str, str | int | float]: - """ - :return: - """ - return { - "subscription_id": self.subscription_id, - "payment_id": self.payment_id, - "invoice_id": self.invoice_id, - "payment_method": self.payment_method, - "payment_amount": self.payment_amount, - "is_success": self.is_success, - "time_paid": self.time_paid - } - - async def on_payment(self, _data: dict[str, str | int], session: sessionType): - """ - on payment notification - notifications will be sent by the payment gateway - verify payment and then activate subscription - - :param session: - :param _data: - :return: - """ - if await self.verify_payment(_data=_data, session=session): - await Subscriptions.activate(self.subscription_id, session=session) - - async def verify_payment(self, _data: dict[str, str | int | bool], session: sessionType): - """ - **verify payment ** - this method will be dependent on the payment method used to process payment - once payment is verified success will be set to true - payment amount rechecked, and time_paid adjusted to match receipt data - :param session: - :param _data: - :return: - """ - pass - - -class Invoices(Base): - """ - Invoices - param: - invoice_uri: will be used in payments to locate the invoice - """ - __tablename__ = "invoices" - invoice_id: str = Column(String(UUID_LEN), primary_key=True) - subscription_id: str = Column(String(UUID_LEN), ForeignKey("subscriptions.subscription_id")) - invoiced_amount: int = Column(Integer) - invoice_from_date: float = Column(Float) - invoice_to_date: float = Column(Float) - time_issued: float = Column(Float) - invoice_uri: str = Column(String(255)) - - @classmethod - def create_if_not_exists(cls): - if not inspect(engine).has_table(cls.__tablename__): - Base.metadata.create_all(bind=engine) - - def to_dict(self) -> dict[str, str | int | float]: - """ - :return: - """ - return { - "invoice_id": self.invoice_id, - "subscription_id": self.subscription_id, - "invoiced_amount": self.invoiced_amount, - "invoice_from_date": self.invoice_from_date, - "invoice_to_date": self.invoice_to_date, - "time_issued": self.time_issued - } - - def is_paid(self, session: sessionType) -> bool: - """ - call to learn if an invoice is paid - :param session: - :return: - """ - return session.query(Payments).filter(Payments.invoice_id == self.invoice_id).filter( - Payments.payment_amount >= self.invoiced_amount).first() is not None - - @classmethod - async def create_invoice(cls, _data: dict[str, str | int | bool], session: sessionType) -> Self: - """ - **create_invoice** - given subscription data create an invoice and send to - client with email. - - wait for payment notification then activate subscription upon receipt of - payment - :param session: - :param _data: - :return: True if invoice was created and scheduled to be sent - """ - pass diff --git a/src/event_queues/__init__.py b/src/event_queues/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/event_queues/invoice_queue.py b/src/event_queues/invoice_queue.py deleted file mode 100644 index 6530c0d..0000000 --- a/src/event_queues/invoice_queue.py +++ /dev/null @@ -1,60 +0,0 @@ -""" - invoice queues -""" -from typing import Callable -import asyncio -lock = asyncio.Lock() - -_outgoing_invoices_queues: list[dict[str, str | int]] = list() -get_argument: Callable = _outgoing_invoices_queues.pop -add_arguments: Callable = _outgoing_invoices_queues.append - - -async def process_invoice_queues(): - """ - **process_queues** - - :return: - """ - while True: - if _outgoing_invoices_queues: - async with lock: - await send_invoice(args=get_argument()) - # sleep for 1 minute - await asyncio.sleep(60 * 1) - - -async def send_invoice(args: dict[str, dict[str, str | int]]) -> None: - """ - **send_invoice** - :param args: - :return: - """ - invoice: dict[str, str | int] = args.get("invoice", {}) - account: dict[str, str | int] = args.get("account", {}) - - if invoice and account: - pass - # TODO Compile and Email and then send to client here - - -async def add_invoice_to_send(invoice: dict[str, str | int], account: dict[str, str | int]): - """ - **add_invoice_to_send** - - :return: None - """ - # Note: this step may be unnecessary it may be faster to just add the account dict here - _account: dict[str, str] = await get_account_details(account=account) - async with lock: - add_arguments(dict(account=_account, invoice=invoice)) - - -async def get_account_details(account: dict[str, str | int]) -> dict[str, str | int]: - """select relevant fields from account dict""" - email = account.get("email") - cell = account.get("cell") - first_name = account.get("first_name") - second_name = account.get("second_name") - surname = account.get("surname") - return dict(cell=cell, email=email, first_name=first_name, second_name=second_name, surname=surname) diff --git a/src/main/__init__.py b/src/main/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/main.py b/src/main/main.py deleted file mode 100644 index 902337d..0000000 --- a/src/main/main.py +++ /dev/null @@ -1,585 +0,0 @@ -import asyncio -import datetime -import hashlib -import hmac -import itertools -import time -from json.decoder import JSONDecodeError - -import httpx -import requests -from fastapi import FastAPI, Request, HTTPException -from fastapi.middleware.cors import CORSMiddleware -from fastapi.middleware.trustedhost import TrustedHostMiddleware -from fastapi.openapi.docs import get_redoc_html -from fastapi.responses import JSONResponse -from fastapi.staticfiles import StaticFiles - -from starlette.responses import HTMLResponse, RedirectResponse - -from src.authorize.authorize import auth_and_rate_limit, create_take_credit_args, process_credit_queue, NotAuthorized, \ - load_plans_by_api_keys, RateLimitExceeded -from src.cache.cache import redis_cache -from src.cloudflare_middleware import EODAPIFirewall -from src.config import config_instance -from src.database.apikeys.keys import cache_api_keys -from src.database.plans.init_plans import RateLimits -from src.make_request import async_client -from src.management_api.email.email import email_process -from src.management_api.routes import admin_app -from src.prefetch import prefetch_endpoints -from src.ratelimit import ip_rate_limits, RateLimit -from src.requests import requester, ServerMonitor -from src.utils.my_logger import init_logger -from src.utils.utils import is_development - -cf_firewall = EODAPIFirewall() -# API Servers -api_server_urls = config_instance().API_SERVERS.SERVERS_LIST.split(',') -api_server_counter = 0 - -# used to logging debug information for the application -app_logger = init_logger("eod_stock_api_gateway") - -app = FastAPI( - title=config_instance().APP_SETTINGS.app_name, - description=config_instance().APP_SETTINGS.app_description, - version=config_instance().APP_SETTINGS.version, - terms_of_service=config_instance().APP_SETTINGS.terms_of_service, - contact={ - "name": config_instance().APP_SETTINGS.contact_name, - "url": config_instance().APP_SETTINGS.contact_url, - "email": config_instance().APP_SETTINGS.contact_email - }, - license_info={ - "name": config_instance().APP_SETTINGS.license_name, - "url": config_instance().APP_SETTINGS.license_url, - }, - docs_url=None, - openapi_url=None, - redoc_url=None -) - -app.mount("/static", StaticFiles(directory="src/main/static"), name="static") - - -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # ERROR HANDLERS -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - -@app.exception_handler(RateLimitExceeded) -async def rate_limit_error_handler(request: Request, exc: RateLimitExceeded): - """ - **rate_limit_error_handler** - will handle Rate Limits Exceeded Error - :param request: - :param exc: - :return: - """ - app_logger.error(msg=f""" - Rate Limit Error - - Debug Information - request_url: {request.url} - request_method: {request.method} - - error_detail: {exc.detail} - rate_limit: {exc.rate_limit} - status_code: {exc.status_code} - """) - return JSONResponse( - status_code=exc.status_code, - content={ - 'message': exc.detail, - 'rate_limit': exc.rate_limit - } - ) - - -@app.exception_handler(HTTPException) -async def http_exception_handler(request, exc): - """HTTP Error Handler Will display HTTP Errors in JSON Format to the client""" - app_logger.info(msg=f""" - HTTP Exception Occurred - - Debug Information - request_url: {request.url} - request_method: {request.method} - - error_detail: {exc.detail} - status_code: {exc.status_code} - """) - - return JSONResponse( - status_code=exc.status_code, - content={"message": exc.detail}) - - -@app.exception_handler(NotAuthorized) -async def handle_not_authorized(request, exc): - app_logger.info(f""" - Not Authorized Error - - Debug Information - request_url: {request.url} - request_method: {request.method} - error_detail: {exc.message} - status_code: {exc.status_code} - - """) - return JSONResponse(status_code=exc.status_code, content={"message": exc.message}) - - -@app.exception_handler(JSONDecodeError) -async def handle_json_decode_error(request, exc): - app_logger.info(f""" - Error Decoding JSON - Debug Information - request_url: {request.url} - request_method: {request.method} - error_detail: "error decoding JSON" - status_code: {exc.status_code} - """) - await delete_resource_from_cache(request) - - message: str = "Oopsie- Server Error, Hopefully our engineers will resolve it soon" - return JSONResponse(status_code=exc.status_code, content=message) - - -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # MIDDLE WARES -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_headers=["*"] -) - - -@app.middleware("http") -async def add_security_headers(request: Request, call_next): - """adding security headers""" - - response = await call_next(request) - response.headers["X-Content-Type-Options"] = "nosniff" - response.headers["X-Frame-Options"] = "DENY" - # # TODO find a normal way to resolve this - if not request.url.path.startswith("/redoc") and not request.url.path.startswith("/_admin/redoc"): - response.headers[ - "Content-Security-Policy"] = "default-src 'none'; script-src 'self' https://cdn.redoc.ly; connect-src 'self'; img-src 'self'; style-src 'self'" - response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" - return response - - -if is_development(config_instance=config_instance): - app.add_middleware(TrustedHostMiddleware, allowed_hosts=["eod-stock-api.local", "localhost", "127.0.0.1"]) -else: - app.add_middleware(TrustedHostMiddleware, allowed_hosts=["master-gateway.eod-stock-api.site", "gateway.eod-stock-api.site"]) - -# Rate Limit per IP Must Always Match The Rate Limit of the Highest Plan Allowed -rate_limit, _, duration = RateLimits().ENTERPRISE - - -@app.middleware(middleware_type="http") -async def edge_request_throttle(request: Request, call_next): - """ - Middleware will throttle requests if they are more than 100 requests per second - per edge, other edge servers may be serviced just as before but the one server - where higher traffic is coming from will be limited - """ - # rate limit by Edge Server IP Address, this will have the effect of throttling entire regions if flooding requests - # are mainly coming from such regions - ip_address = await cf_firewall.get_edge_server_ip(headers=request.headers) - if ip_address not in ip_rate_limits: - # This will throttle the connection if there is too many requests coming from only one edge server - ip_rate_limits[ip_address] = RateLimit() - - is_throttled = False - if await ip_rate_limits[ip_address].is_limit_exceeded(): - await ip_rate_limits[ip_address].ip_throttle(edge_ip=ip_address, request=request) - is_throttled = True - # continue with the request - # either the request was throttled and now proceeding or all is well - app_logger.info(f"On Entry to : {request.url.path}") - response = await call_next(request) - - # attaching a header showing throttling was in effect and proceeding - if is_throttled: - response.headers["X-Request-Throttled-Time"] = f"{ip_rate_limits[ip_address].throttle_duration} Seconds" - return response - - -@app.middleware(middleware_type="http") -async def check_ip(request: Request, call_next): - """ - determines if call originate from cloudflare - :param request: - :param call_next: - :return: - """ - # THE Cloudflare IP Address is a client in this case as its the one sending the requests - edge_ip: str = await cf_firewall.get_edge_server_ip(headers=request.headers) - app_logger.info(f"Client IP Address : {edge_ip}") - - if edge_ip and await cf_firewall.check_ip_range(ip=edge_ip): - response = await call_next(request) - elif is_development(config_instance=config_instance): - response = await call_next(request) - else: - return JSONResponse( - content={ - "message": "Access denied, Bad Gateway Address we can only process request from our gateway server", - "gateway": "https://gateway.eod-stock-api.site"}, - status_code=403) - - return response - - -# Create a middleware function that checks the IP address of incoming requests and only allows requests from the -# Cloudflare IP ranges. Here's an example of how you could do this: -@app.middleware(middleware_type="http") -async def validate_request_middleware(request, call_next): - """ - checks if the request comes from cloudflare through a token - also check if the path is going to a good path / route if not it blocks the request - :param request: - :param call_next: - :return: - """ - - # This code will be executed for each incoming request - # before it is processed by the route handlers. - # You can modify the request here, or perform any other - # pre-processing that you need. - # allowedPaths = ["/", "/api/", "/redoc", "/docs", "/_admin/"] - async def compare_tokens(): - """will check headers to see if the request comes from cloudflare""" - _cf_secret_token = request.headers.get('X-SECRET-TOKEN') - _cloudflare_token = config_instance().CLOUDFLARE_SETTINGS.CLOUDFLARE_SECRET_KEY - app_logger.info(f"Request Headers : {request.headers}") - if _cf_secret_token is None: - return False - - hash_func = hashlib.sha256 # choose a hash function - secret_key = config_instance().SECRET_KEY - digest1 = hmac.new(secret_key.encode(), _cf_secret_token.encode(), hash_func).digest() - digest2 = hmac.new(secret_key.encode(), _cloudflare_token.encode(), hash_func).digest() - return hmac.compare_digest(digest1, digest2) - - path = str(request.url.path) - _url = str(request.url) - start_time = time.monotonic() - - if await cf_firewall.is_request_malicious(headers=request.headers, url=request.url, body=str(request.body)): - """ - If we are here then there is something wrong with the request - """ - mess: dict[str, str] = { - "message": "Request Contains Suspicious patterns cannot continue"} - response = JSONResponse(content=mess, status_code=404) - return response - - if path.startswith("/_admin") or path.startswith("/redoc") or path.startswith("/docs") or path.startswith( - "/static"): - app_logger.info("starts with admin going in ") - response = await call_next(request) - - elif path in ["/open-api", "/"]: - """letting through specific URLS for Documentation""" - app_logger.info(f"Routing to Documentations : {path}") - response = await call_next(request) - - elif is_development(config_instance=config_instance): - app_logger.info(f"Development") - response = await call_next(request) - - elif not await compare_tokens(): - mess: dict[str, str] = { - "message": "Request Is not valid Bad Token please ensure you are routing this request through our gateway"} - response = JSONResponse(content=mess, status_code=404) - - # --- - elif await cf_firewall.path_matches_known_route(path=path): - response = await call_next(request) - else: - app_logger.warning(msg=f""" - Potentially Bad Route Being Accessed - request.url = {request.url} - - request.method = {request.method} - - request.headers = {request.headers} - - request_time = {datetime.datetime.now().isoformat(sep="-")} - """) - # raise NotAuthorized(message="Route Not Allowed, if you think this maybe an error please contact admin") - response = JSONResponse(content="Request Does not Match Any Known Route", status_code=404) - - end_time = time.monotonic() - app_logger.info(f"Elapsed Time Validate Request : {end_time - start_time}") - app_logger.info("Cleared Request Validation") - return response - - -####################################################################################################################### - - -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # STARTUP EVENTS -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -remote_servers = ServerMonitor() - - -# On Start Up Run the following Tasks -@app.on_event('startup') -async def startup_event(): - async def setup_cf_firewall(): - app_logger.info("Application Setup") - ipv4_cdrs, ipv6_cdrs = await cf_firewall.get_ip_ranges() - cf_firewall.ip_ranges = list(itertools.chain(*[ipv4_cdrs, ipv6_cdrs])) - - app_logger.info(f"CF Firewall Added {len(ipv4_cdrs)} IP-V4 & {len(ipv6_cdrs)} IP-V6 Addresses") - # This will restore a list of known Bad IP Addresses - await cf_firewall.restore_bad_addresses_from_redis() - - async def update_api_keys_background_task(): - while True: - # Caching API Keys , plans and Subscriptions - total_apikeys: int = await cache_api_keys() - app_logger.info(f"Cache Prefetched {total_apikeys} apikeys") - await load_plans_by_api_keys() - # wait for 5 minutes then update API Keys records - await asyncio.sleep(60 * 15) - - # noinspection PyUnusedLocal - async def prefetch(): - """ - Method to Prefetch Common Routes for faster Execution - :return: - """ - while True: - if not is_development(config_instance=config_instance): - app_logger.info(f"Started Pre Fetching Requests") - total_prefetched = await prefetch_endpoints() - app_logger.info(f"Cache Pre Fetched {total_prefetched} endpoints") - - # wait for 3 hours minutes then prefetch urls again - await asyncio.sleep(60 * 60 * 3.5) - - async def backup_cf_firewall_data(): - while True: - total_bad_addresses = await cf_firewall.save_bad_addresses_to_redis() - app_logger.info(f"CF Firewall Backed Up {total_bad_addresses} Bad Addresses") - # Everyday - # Runs every 24 hours in order to backup bad addresses list - await asyncio.sleep(60 * 60 * 24) - - async def clean_up_memcache(): - while True: - # This cleans up the cache every 30 minutes - total_cleaned = await redis_cache.memcache_ttl_cleaner() - app_logger.info(f"Cleaned Up {total_cleaned} Expired Mem Cache Values") - await asyncio.sleep(delay=60 * 30) - - async def check_redis_errors(): - """will check if redis is making too many errors then switch redis off if it's the case""" - while True: - await redis_cache.redis_errors.check_error_threshold() - await asyncio.sleep(delay=60*60*10) - - async def monitor_servers(): - """will prioritize servers which are responsive and also available""" - while True: - await remote_servers.sort_api_servers_by_health() - await asyncio.sleep(delay=60 * 60 * 30) - - asyncio.create_task(setup_cf_firewall()) - asyncio.create_task(backup_cf_firewall_data()) - asyncio.create_task(update_api_keys_background_task()) - asyncio.create_task(process_credit_queue()) - asyncio.create_task(email_process.process_message_queues()) - asyncio.create_task(clean_up_memcache()) - asyncio.create_task(monitor_servers()) - asyncio.create_task(check_redis_errors()) - asyncio.create_task(prefetch()) - - -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # ADMIN APP -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - - -# TODO ensure that the admin APP is running on the Admin Sub Domain Meaning this should Change -# TODO Also the Admin APP must be removed from the gateway it will just slow down the gateway - - -# TODO Admin Application Mounting Point should eventually Move this -# To its own separate Application -app.mount(path="/_admin", app=admin_app, name="admin_app") - - -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # API GATEWAY -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - -@app.get("/api/v1/{path:path}", include_in_schema=False) -@auth_and_rate_limit -async def v1_gateway(request: Request, path: str): - """ - NOTE: In order for the gateway server to work properly it needs at least 2 GIG or RAM - master router - :param request: - :param path: - :return: - """ - - api_key: dict = request.query_params.get('api_key') - _path = f"/api/v1/{path}" - app_logger.info(f"Gateway") - await create_take_credit_args(api_key=api_key, path=_path) - - api_urls = [f'{api_server_url}/api/v1/{path}' for api_server_url in remote_servers.healthy_server_urls] - - # Will Take at least six second on the cache if it finds nothing will return None - # need an improved get timeout for the articles - tasks = [redis_cache.get(key=api_url, timeout=60 * 5) for api_url in api_urls] - app_logger.info(f"fetching responses for {api_urls}") - cached_responses = await asyncio.gather(*tasks) - app_logger.info("fetched records") - for i, response in enumerate(cached_responses): - if response and response.get('payload'): - app_logger.info(msg=f"Found cached response from {api_urls[i]}") - return JSONResponse(content=response, status_code=200, headers={"Content-Type": "application/json"}) - - app_logger.info(msg="All cached responses not found- Must Be a Slow Day") - for api_url in api_urls: - try: - # 5 minutes timeout on resource fetching from backend - some resources may take very long - response = await requester(api_url=api_url, timeout=9600) - if response and response.get("status", False) and response.get('payload'): - # NOTE, Cache is being set to a ttl of one hour here - await redis_cache.set(key=api_url, value=response, ttl=60 * 60) - return JSONResponse(content=response, status_code=200, headers={"Content-Type": "application/json"}) - except httpx.HTTPError as http_err: - app_logger.info(msg=f"Errors when making requests : {str(http_err)}") - - mess = "All API Servers failed to respond - Or there is no Data for the requested resource and parameters" - app_logger.warning(msg=mess) - _time = datetime.datetime.now().isoformat(sep="-") - - # Note: Sending Message to developers containing the details of the request of which there was no data at all - _args = dict(message_type="resource_not_found", request=request, api_key=api_key) - await email_process.send_message_to_devs(**_args) - - return JSONResponse(content={"status": False, "message": mess}, status_code=404, - headers={"Content-Type": "application/json"}) - - -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # Documentations Routes -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# noinspection PyUnusedLocal -@app.get("/open-api", include_in_schema=False) -async def open_api(request: Request): - """ - **open_api** - will return a json open api specification for the servers loaded from GitHub repo - :param request: - :return: - """ - # NOTE this allows me to load openapi specifications from another repo in GitHub therefore do not have to update - # API Specifications on the gateway - spec_url = "https://raw.githubusercontent.com/MJ-API-Development/open-api-spec/main/open-api.json" - response = await redis_cache.get(key=spec_url, timeout=1) - if response is None: - data = await async_client.get(url=spec_url, timeout=60 * 5) - if data: - response = data.json() - await redis_cache.set(key=spec_url, value=response, ttl=60 * 60) - - return JSONResponse(content=response, status_code=200, headers={"Content-Type": "application/json"}) - - -# noinspection PyUnusedLocal -@app.get("/", include_in_schema=True) -async def home_route(request: Request): - """ - **home_route** - redirects to documentations - :return: - """ - return RedirectResponse(url="/redoc", status_code=301) - - -# noinspection PyUnusedLocal -@app.get("/redoc", include_in_schema=False, response_class=HTMLResponse) -async def redoc_html(request: Request): - return get_redoc_html( - openapi_url='https://gateway.eod-stock-api.site/open-api', - title=app.title + " - ReDoc", - redoc_js_url="https://gateway.eod-stock-api.site/static/redoc.standalone.js", - with_google_fonts=True - ) - - -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # CACHE MANAGEMENT UTILS -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - -async def create_resource_keys(request: Request) -> list[str]: - """ - **create_resource_keys** - create resource keys for urls - :param request: - :return: - """ - return [f"{b_server}{request.url.path}" for b_server in api_server_urls] - - -async def delete_resource_from_cache(request: Request): - """ - **delete_resource_from_cache** - This will delete any resource associated with a request from cache if such a - resource is causing errors such as JSON Decode Errors - :return: - """ - try: - resource_keys: list[str] = await create_resource_keys(request) - for resource_key in resource_keys: - await redis_cache.delete_key(key=resource_key) - except Exception as e: - app_logger.error(msg=str(e)) - - -async def check_all_services(): - """ - **heck_all_services** - compile a full list of services and show if they are available - :return: - """ - # TODO update this to use the gateway server monitor method specifically this one = remote_servers.sort_api_servers_by_health() - ping_master = requests.get('https://stock-eod-api.site/_ah/warmup') - master = "Offline" - - if ping_master.status_code == 200: - master = "online" - - return { - 'Gateway': 'Online', - 'API_Master': master, - 'API_Slave': 'Online' - } - - -# noinspection PyUnusedLocal -@app.get("/_ah/warmup", include_in_schema=True) -async def status_check(request: Request): - """ - **status_check** - used as a way to monitor if the gateway is up - will have a side effect of also checking all API Servers - """ - _payload = await check_all_services() - response = dict(payload=_payload) - return JSONResponse(content=response, status_code=200, headers={"Content-Type": "application/json"}) diff --git a/src/main/static/redoc.standalone.js b/src/main/static/redoc.standalone.js deleted file mode 100644 index 9f51a9d..0000000 --- a/src/main/static/redoc.standalone.js +++ /dev/null @@ -1,1806 +0,0 @@ -/*! For license information please see redoc.standalone.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):"function"==typeof define&&define.amd?define(["null"],t):"object"==typeof exports?exports.Redoc=t(require("null")):e.Redoc=t(e.null)}(this,(function(e){return function(){var t={5499:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CodeGen=t.Name=t.nil=t.stringify=t.str=t._=t.KeywordCxt=void 0;const r=n(3325),o=n(6479),i=n(5522),a=n(1603),s=["/properties"],l="http://json-schema.org/draft-07/schema";class c extends r.default{_addVocabularies(){super._addVocabularies(),o.default.forEach((e=>this.addVocabulary(e))),this.opts.discriminator&&this.addKeyword(i.default)}_addDefaultMetaSchema(){if(super._addDefaultMetaSchema(),!this.opts.meta)return;const e=this.opts.$data?this.$dataMetaSchema(a,s):a;this.addMetaSchema(e,l,!1),this.refs["http://json-schema.org/schema"]=l}defaultMeta(){return this.opts.defaultMeta=super.defaultMeta()||(this.getSchema(l)?l:void 0)}}e.exports=t=c,Object.defineProperty(t,"__esModule",{value:!0}),t.default=c;var u=n(1321);Object.defineProperty(t,"KeywordCxt",{enumerable:!0,get:function(){return u.KeywordCxt}});var p=n(4475);Object.defineProperty(t,"_",{enumerable:!0,get:function(){return p._}}),Object.defineProperty(t,"str",{enumerable:!0,get:function(){return p.str}}),Object.defineProperty(t,"stringify",{enumerable:!0,get:function(){return p.stringify}}),Object.defineProperty(t,"nil",{enumerable:!0,get:function(){return p.nil}}),Object.defineProperty(t,"Name",{enumerable:!0,get:function(){return p.Name}}),Object.defineProperty(t,"CodeGen",{enumerable:!0,get:function(){return p.CodeGen}})},4667:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.regexpCode=t.getProperty=t.safeStringify=t.stringify=t.strConcat=t.addCodeArg=t.str=t._=t.nil=t._Code=t.Name=t.IDENTIFIER=t._CodeOrName=void 0;class n{}t._CodeOrName=n,t.IDENTIFIER=/^[a-z$_][a-z$_0-9]*$/i;class r extends n{constructor(e){if(super(),!t.IDENTIFIER.test(e))throw new Error("CodeGen: name must be a valid identifier");this.str=e}toString(){return this.str}emptyStr(){return!1}get names(){return{[this.str]:1}}}t.Name=r;class o extends n{constructor(e){super(),this._items="string"==typeof e?[e]:e}toString(){return this.str}emptyStr(){if(this._items.length>1)return!1;const e=this._items[0];return""===e||'""'===e}get str(){var e;return null!==(e=this._str)&&void 0!==e?e:this._str=this._items.reduce(((e,t)=>`${e}${t}`),"")}get names(){var e;return null!==(e=this._names)&&void 0!==e?e:this._names=this._items.reduce(((e,t)=>(t instanceof r&&(e[t.str]=(e[t.str]||0)+1),e)),{})}}function i(e,...t){const n=[e[0]];let r=0;for(;r"),GTE:new r._Code(">="),LT:new r._Code("<"),LTE:new r._Code("<="),EQ:new r._Code("==="),NEQ:new r._Code("!=="),NOT:new r._Code("!"),OR:new r._Code("||"),AND:new r._Code("&&"),ADD:new r._Code("+")};class s{optimizeNodes(){return this}optimizeNames(e,t){return this}}class l extends s{constructor(e,t,n){super(),this.varKind=e,this.name=t,this.rhs=n}render({es5:e,_n:t}){const n=e?o.varKinds.var:this.varKind,r=void 0===this.rhs?"":` = ${this.rhs}`;return`${n} ${this.name}${r};`+t}optimizeNames(e,t){if(e[this.name.str])return this.rhs&&(this.rhs=R(this.rhs,e,t)),this}get names(){return this.rhs instanceof r._CodeOrName?this.rhs.names:{}}}class c extends s{constructor(e,t,n){super(),this.lhs=e,this.rhs=t,this.sideEffects=n}render({_n:e}){return`${this.lhs} = ${this.rhs};`+e}optimizeNames(e,t){if(!(this.lhs instanceof r.Name)||e[this.lhs.str]||this.sideEffects)return this.rhs=R(this.rhs,e,t),this}get names(){return C(this.lhs instanceof r.Name?{}:{...this.lhs.names},this.rhs)}}class u extends c{constructor(e,t,n,r){super(e,n,r),this.op=t}render({_n:e}){return`${this.lhs} ${this.op}= ${this.rhs};`+e}}class p extends s{constructor(e){super(),this.label=e,this.names={}}render({_n:e}){return`${this.label}:`+e}}class d extends s{constructor(e){super(),this.label=e,this.names={}}render({_n:e}){return`break${this.label?` ${this.label}`:""};`+e}}class f extends s{constructor(e){super(),this.error=e}render({_n:e}){return`throw ${this.error};`+e}get names(){return this.error.names}}class h extends s{constructor(e){super(),this.code=e}render({_n:e}){return`${this.code};`+e}optimizeNodes(){return`${this.code}`?this:void 0}optimizeNames(e,t){return this.code=R(this.code,e,t),this}get names(){return this.code instanceof r._CodeOrName?this.code.names:{}}}class m extends s{constructor(e=[]){super(),this.nodes=e}render(e){return this.nodes.reduce(((t,n)=>t+n.render(e)),"")}optimizeNodes(){const{nodes:e}=this;let t=e.length;for(;t--;){const n=e[t].optimizeNodes();Array.isArray(n)?e.splice(t,1,...n):n?e[t]=n:e.splice(t,1)}return e.length>0?this:void 0}optimizeNames(e,t){const{nodes:n}=this;let r=n.length;for(;r--;){const o=n[r];o.optimizeNames(e,t)||(j(e,o.names),n.splice(r,1))}return n.length>0?this:void 0}get names(){return this.nodes.reduce(((e,t)=>$(e,t.names)),{})}}class g extends m{render(e){return"{"+e._n+super.render(e)+"}"+e._n}}class y extends m{}class v extends g{}v.kind="else";class b extends g{constructor(e,t){super(t),this.condition=e}render(e){let t=`if(${this.condition})`+super.render(e);return this.else&&(t+="else "+this.else.render(e)),t}optimizeNodes(){super.optimizeNodes();const e=this.condition;if(!0===e)return this.nodes;let t=this.else;if(t){const e=t.optimizeNodes();t=this.else=Array.isArray(e)?new v(e):e}return t?!1===e?t instanceof b?t:t.nodes:this.nodes.length?this:new b(T(e),t instanceof b?[t]:t.nodes):!1!==e&&this.nodes.length?this:void 0}optimizeNames(e,t){var n;if(this.else=null===(n=this.else)||void 0===n?void 0:n.optimizeNames(e,t),super.optimizeNames(e,t)||this.else)return this.condition=R(this.condition,e,t),this}get names(){const e=super.names;return C(e,this.condition),this.else&&$(e,this.else.names),e}}b.kind="if";class w extends g{}w.kind="for";class x extends w{constructor(e){super(),this.iteration=e}render(e){return`for(${this.iteration})`+super.render(e)}optimizeNames(e,t){if(super.optimizeNames(e,t))return this.iteration=R(this.iteration,e,t),this}get names(){return $(super.names,this.iteration.names)}}class k extends w{constructor(e,t,n,r){super(),this.varKind=e,this.name=t,this.from=n,this.to=r}render(e){const t=e.es5?o.varKinds.var:this.varKind,{name:n,from:r,to:i}=this;return`for(${t} ${n}=${r}; ${n}<${i}; ${n}++)`+super.render(e)}get names(){const e=C(super.names,this.from);return C(e,this.to)}}class _ extends w{constructor(e,t,n,r){super(),this.loop=e,this.varKind=t,this.name=n,this.iterable=r}render(e){return`for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})`+super.render(e)}optimizeNames(e,t){if(super.optimizeNames(e,t))return this.iterable=R(this.iterable,e,t),this}get names(){return $(super.names,this.iterable.names)}}class O extends g{constructor(e,t,n){super(),this.name=e,this.args=t,this.async=n}render(e){return`${this.async?"async ":""}function ${this.name}(${this.args})`+super.render(e)}}O.kind="func";class S extends m{render(e){return"return "+super.render(e)}}S.kind="return";class E extends g{render(e){let t="try"+super.render(e);return this.catch&&(t+=this.catch.render(e)),this.finally&&(t+=this.finally.render(e)),t}optimizeNodes(){var e,t;return super.optimizeNodes(),null===(e=this.catch)||void 0===e||e.optimizeNodes(),null===(t=this.finally)||void 0===t||t.optimizeNodes(),this}optimizeNames(e,t){var n,r;return super.optimizeNames(e,t),null===(n=this.catch)||void 0===n||n.optimizeNames(e,t),null===(r=this.finally)||void 0===r||r.optimizeNames(e,t),this}get names(){const e=super.names;return this.catch&&$(e,this.catch.names),this.finally&&$(e,this.finally.names),e}}class P extends g{constructor(e){super(),this.error=e}render(e){return`catch(${this.error})`+super.render(e)}}P.kind="catch";class A extends g{render(e){return"finally"+super.render(e)}}function $(e,t){for(const n in t)e[n]=(e[n]||0)+(t[n]||0);return e}function C(e,t){return t instanceof r._CodeOrName?$(e,t.names):e}function R(e,t,n){return e instanceof r.Name?i(e):(o=e)instanceof r._Code&&o._items.some((e=>e instanceof r.Name&&1===t[e.str]&&void 0!==n[e.str]))?new r._Code(e._items.reduce(((e,t)=>(t instanceof r.Name&&(t=i(t)),t instanceof r._Code?e.push(...t._items):e.push(t),e)),[])):e;var o;function i(e){const r=n[e.str];return void 0===r||1!==t[e.str]?e:(delete t[e.str],r)}}function j(e,t){for(const n in t)e[n]=(e[n]||0)-(t[n]||0)}function T(e){return"boolean"==typeof e||"number"==typeof e||null===e?!e:r._`!${L(e)}`}A.kind="finally",t.CodeGen=class{constructor(e,t={}){this._values={},this._blockStarts=[],this._constants={},this.opts={...t,_n:t.lines?"\n":""},this._extScope=e,this._scope=new o.Scope({parent:e}),this._nodes=[new y]}toString(){return this._root.render(this.opts)}name(e){return this._scope.name(e)}scopeName(e){return this._extScope.name(e)}scopeValue(e,t){const n=this._extScope.value(e,t);return(this._values[n.prefix]||(this._values[n.prefix]=new Set)).add(n),n}getScopeValue(e,t){return this._extScope.getValue(e,t)}scopeRefs(e){return this._extScope.scopeRefs(e,this._values)}scopeCode(){return this._extScope.scopeCode(this._values)}_def(e,t,n,r){const o=this._scope.toName(t);return void 0!==n&&r&&(this._constants[o.str]=n),this._leafNode(new l(e,o,n)),o}const(e,t,n){return this._def(o.varKinds.const,e,t,n)}let(e,t,n){return this._def(o.varKinds.let,e,t,n)}var(e,t,n){return this._def(o.varKinds.var,e,t,n)}assign(e,t,n){return this._leafNode(new c(e,t,n))}add(e,n){return this._leafNode(new u(e,t.operators.ADD,n))}code(e){return"function"==typeof e?e():e!==r.nil&&this._leafNode(new h(e)),this}object(...e){const t=["{"];for(const[n,o]of e)t.length>1&&t.push(","),t.push(n),(n!==o||this.opts.es5)&&(t.push(":"),r.addCodeArg(t,o));return t.push("}"),new r._Code(t)}if(e,t,n){if(this._blockNode(new b(e)),t&&n)this.code(t).else().code(n).endIf();else if(t)this.code(t).endIf();else if(n)throw new Error('CodeGen: "else" body without "then" body');return this}elseIf(e){return this._elseNode(new b(e))}else(){return this._elseNode(new v)}endIf(){return this._endBlockNode(b,v)}_for(e,t){return this._blockNode(e),t&&this.code(t).endFor(),this}for(e,t){return this._for(new x(e),t)}forRange(e,t,n,r,i=(this.opts.es5?o.varKinds.var:o.varKinds.let)){const a=this._scope.toName(e);return this._for(new k(i,a,t,n),(()=>r(a)))}forOf(e,t,n,i=o.varKinds.const){const a=this._scope.toName(e);if(this.opts.es5){const e=t instanceof r.Name?t:this.var("_arr",t);return this.forRange("_i",0,r._`${e}.length`,(t=>{this.var(a,r._`${e}[${t}]`),n(a)}))}return this._for(new _("of",i,a,t),(()=>n(a)))}forIn(e,t,n,i=(this.opts.es5?o.varKinds.var:o.varKinds.const)){if(this.opts.ownProperties)return this.forOf(e,r._`Object.keys(${t})`,n);const a=this._scope.toName(e);return this._for(new _("in",i,a,t),(()=>n(a)))}endFor(){return this._endBlockNode(w)}label(e){return this._leafNode(new p(e))}break(e){return this._leafNode(new d(e))}return(e){const t=new S;if(this._blockNode(t),this.code(e),1!==t.nodes.length)throw new Error('CodeGen: "return" should have one node');return this._endBlockNode(S)}try(e,t,n){if(!t&&!n)throw new Error('CodeGen: "try" without "catch" and "finally"');const r=new E;if(this._blockNode(r),this.code(e),t){const e=this.name("e");this._currNode=r.catch=new P(e),t(e)}return n&&(this._currNode=r.finally=new A,this.code(n)),this._endBlockNode(P,A)}throw(e){return this._leafNode(new f(e))}block(e,t){return this._blockStarts.push(this._nodes.length),e&&this.code(e).endBlock(t),this}endBlock(e){const t=this._blockStarts.pop();if(void 0===t)throw new Error("CodeGen: not in self-balancing block");const n=this._nodes.length-t;if(n<0||void 0!==e&&n!==e)throw new Error(`CodeGen: wrong number of nodes: ${n} vs ${e} expected`);return this._nodes.length=t,this}func(e,t=r.nil,n,o){return this._blockNode(new O(e,t,n)),o&&this.code(o).endFunc(),this}endFunc(){return this._endBlockNode(O)}optimize(e=1){for(;e-- >0;)this._root.optimizeNodes(),this._root.optimizeNames(this._root.names,this._constants)}_leafNode(e){return this._currNode.nodes.push(e),this}_blockNode(e){this._currNode.nodes.push(e),this._nodes.push(e)}_endBlockNode(e,t){const n=this._currNode;if(n instanceof e||t&&n instanceof t)return this._nodes.pop(),this;throw new Error(`CodeGen: not in block "${t?`${e.kind}/${t.kind}`:e.kind}"`)}_elseNode(e){const t=this._currNode;if(!(t instanceof b))throw new Error('CodeGen: "else" without "if"');return this._currNode=t.else=e,this}get _root(){return this._nodes[0]}get _currNode(){const e=this._nodes;return e[e.length-1]}set _currNode(e){const t=this._nodes;t[t.length-1]=e}},t.not=T;const I=D(t.operators.AND);t.and=function(...e){return e.reduce(I)};const N=D(t.operators.OR);function D(e){return(t,n)=>t===r.nil?n:n===r.nil?t:r._`${L(t)} ${e} ${L(n)}`}function L(e){return e instanceof r.Name?e:r._`(${e})`}t.or=function(...e){return e.reduce(N)}},7791:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ValueScope=t.ValueScopeName=t.Scope=t.varKinds=t.UsedValueState=void 0;const r=n(4667);class o extends Error{constructor(e){super(`CodeGen: "code" for ${e} not defined`),this.value=e.value}}var i;!function(e){e[e.Started=0]="Started",e[e.Completed=1]="Completed"}(i=t.UsedValueState||(t.UsedValueState={})),t.varKinds={const:new r.Name("const"),let:new r.Name("let"),var:new r.Name("var")};class a{constructor({prefixes:e,parent:t}={}){this._names={},this._prefixes=e,this._parent=t}toName(e){return e instanceof r.Name?e:this.name(e)}name(e){return new r.Name(this._newName(e))}_newName(e){return`${e}${(this._names[e]||this._nameGroup(e)).index++}`}_nameGroup(e){var t,n;if((null===(n=null===(t=this._parent)||void 0===t?void 0:t._prefixes)||void 0===n?void 0:n.has(e))||this._prefixes&&!this._prefixes.has(e))throw new Error(`CodeGen: prefix "${e}" is not allowed in this scope`);return this._names[e]={prefix:e,index:0}}}t.Scope=a;class s extends r.Name{constructor(e,t){super(t),this.prefix=e}setValue(e,{property:t,itemIndex:n}){this.value=e,this.scopePath=r._`.${new r.Name(t)}[${n}]`}}t.ValueScopeName=s;const l=r._`\n`;t.ValueScope=class extends a{constructor(e){super(e),this._values={},this._scope=e.scope,this.opts={...e,_n:e.lines?l:r.nil}}get(){return this._scope}name(e){return new s(e,this._newName(e))}value(e,t){var n;if(void 0===t.ref)throw new Error("CodeGen: ref must be passed in value");const r=this.toName(e),{prefix:o}=r,i=null!==(n=t.key)&&void 0!==n?n:t.ref;let a=this._values[o];if(a){const e=a.get(i);if(e)return e}else a=this._values[o]=new Map;a.set(i,r);const s=this._scope[o]||(this._scope[o]=[]),l=s.length;return s[l]=t.ref,r.setValue(t,{property:o,itemIndex:l}),r}getValue(e,t){const n=this._values[e];if(n)return n.get(t)}scopeRefs(e,t=this._values){return this._reduceValues(t,(t=>{if(void 0===t.scopePath)throw new Error(`CodeGen: name "${t}" has no value`);return r._`${e}${t.scopePath}`}))}scopeCode(e=this._values,t,n){return this._reduceValues(e,(e=>{if(void 0===e.value)throw new Error(`CodeGen: name "${e}" has no value`);return e.value.code}),t,n)}_reduceValues(e,n,a={},s){let l=r.nil;for(const c in e){const u=e[c];if(!u)continue;const p=a[c]=a[c]||new Map;u.forEach((e=>{if(p.has(e))return;p.set(e,i.Started);let a=n(e);if(a){const n=this.opts.es5?t.varKinds.var:t.varKinds.const;l=r._`${l}${n} ${e} = ${a};${this.opts._n}`}else{if(!(a=null==s?void 0:s(e)))throw new o(e);l=r._`${l}${a}${this.opts._n}`}p.set(e,i.Completed)}))}return l}}},1885:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.extendErrors=t.resetErrorsCount=t.reportExtraError=t.reportError=t.keyword$DataError=t.keywordError=void 0;const r=n(4475),o=n(6124),i=n(5018);function a(e,t){const n=e.const("err",t);e.if(r._`${i.default.vErrors} === null`,(()=>e.assign(i.default.vErrors,r._`[${n}]`)),r._`${i.default.vErrors}.push(${n})`),e.code(r._`${i.default.errors}++`)}function s(e,t){const{gen:n,validateName:o,schemaEnv:i}=e;i.$async?n.throw(r._`new ${e.ValidationError}(${t})`):(n.assign(r._`${o}.errors`,t),n.return(!1))}t.keywordError={message:({keyword:e})=>r.str`should pass "${e}" keyword validation`},t.keyword$DataError={message:({keyword:e,schemaType:t})=>t?r.str`"${e}" keyword must be ${t} ($data)`:r.str`"${e}" keyword is invalid ($data)`},t.reportError=function(e,n=t.keywordError,o,i){const{it:l}=e,{gen:u,compositeRule:p,allErrors:d}=l,f=c(e,n,o);(null!=i?i:p||d)?a(u,f):s(l,r._`[${f}]`)},t.reportExtraError=function(e,n=t.keywordError,r){const{it:o}=e,{gen:l,compositeRule:u,allErrors:p}=o;a(l,c(e,n,r)),u||p||s(o,i.default.vErrors)},t.resetErrorsCount=function(e,t){e.assign(i.default.errors,t),e.if(r._`${i.default.vErrors} !== null`,(()=>e.if(t,(()=>e.assign(r._`${i.default.vErrors}.length`,t)),(()=>e.assign(i.default.vErrors,null)))))},t.extendErrors=function({gen:e,keyword:t,schemaValue:n,data:o,errsCount:a,it:s}){if(void 0===a)throw new Error("ajv implementation error");const l=e.name("err");e.forRange("i",a,i.default.errors,(a=>{e.const(l,r._`${i.default.vErrors}[${a}]`),e.if(r._`${l}.instancePath === undefined`,(()=>e.assign(r._`${l}.instancePath`,r.strConcat(i.default.instancePath,s.errorPath)))),e.assign(r._`${l}.schemaPath`,r.str`${s.errSchemaPath}/${t}`),s.opts.verbose&&(e.assign(r._`${l}.schema`,n),e.assign(r._`${l}.data`,o))}))};const l={keyword:new r.Name("keyword"),schemaPath:new r.Name("schemaPath"),params:new r.Name("params"),propertyName:new r.Name("propertyName"),message:new r.Name("message"),schema:new r.Name("schema"),parentSchema:new r.Name("parentSchema")};function c(e,t,n){const{createErrors:o}=e.it;return!1===o?r._`{}`:function(e,t,n={}){const{gen:o,it:a}=e,s=[u(a,n),p(e,n)];return function(e,{params:t,message:n},o){const{keyword:a,data:s,schemaValue:c,it:u}=e,{opts:p,propertyName:d,topSchemaRef:f,schemaPath:h}=u;o.push([l.keyword,a],[l.params,"function"==typeof t?t(e):t||r._`{}`]),p.messages&&o.push([l.message,"function"==typeof n?n(e):n]),p.verbose&&o.push([l.schema,c],[l.parentSchema,r._`${f}${h}`],[i.default.data,s]),d&&o.push([l.propertyName,d])}(e,t,s),o.object(...s)}(e,t,n)}function u({errorPath:e},{instancePath:t}){const n=t?r.str`${e}${o.getErrorPath(t,o.Type.Str)}`:e;return[i.default.instancePath,r.strConcat(i.default.instancePath,n)]}function p({keyword:e,it:{errSchemaPath:t}},{schemaPath:n,parentSchema:i}){let a=i?t:r.str`${t}/${e}`;return n&&(a=r.str`${a}${o.getErrorPath(n,o.Type.Str)}`),[l.schemaPath,a]}},7805:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.resolveSchema=t.getCompilingSchema=t.resolveRef=t.compileSchema=t.SchemaEnv=void 0;const r=n(4475),o=n(8451),i=n(5018),a=n(9826),s=n(6124),l=n(1321),c=n(540);class u{constructor(e){var t;let n;this.refs={},this.dynamicAnchors={},"object"==typeof e.schema&&(n=e.schema),this.schema=e.schema,this.schemaId=e.schemaId,this.root=e.root||this,this.baseId=null!==(t=e.baseId)&&void 0!==t?t:a.normalizeId(null==n?void 0:n[e.schemaId||"$id"]),this.schemaPath=e.schemaPath,this.localRefs=e.localRefs,this.meta=e.meta,this.$async=null==n?void 0:n.$async,this.refs={}}}function p(e){const t=f.call(this,e);if(t)return t;const n=a.getFullPath(e.root.baseId),{es5:s,lines:c}=this.opts.code,{ownProperties:u}=this.opts,p=new r.CodeGen(this.scope,{es5:s,lines:c,ownProperties:u});let d;e.$async&&(d=p.scopeValue("Error",{ref:o.default,code:r._`require("ajv/dist/runtime/validation_error").default`}));const h=p.scopeName("validate");e.validateName=h;const m={gen:p,allErrors:this.opts.allErrors,data:i.default.data,parentData:i.default.parentData,parentDataProperty:i.default.parentDataProperty,dataNames:[i.default.data],dataPathArr:[r.nil],dataLevel:0,dataTypes:[],definedProperties:new Set,topSchemaRef:p.scopeValue("schema",!0===this.opts.code.source?{ref:e.schema,code:r.stringify(e.schema)}:{ref:e.schema}),validateName:h,ValidationError:d,schema:e.schema,schemaEnv:e,rootId:n,baseId:e.baseId||n,schemaPath:r.nil,errSchemaPath:e.schemaPath||(this.opts.jtd?"":"#"),errorPath:r._`""`,opts:this.opts,self:this};let g;try{this._compilations.add(e),l.validateFunctionCode(m),p.optimize(this.opts.code.optimize);const t=p.toString();g=`const visitedNodesForRef = new WeakMap(); ${p.scopeRefs(i.default.scope)}return ${t}`,this.opts.code.process&&(g=this.opts.code.process(g,e));const n=new Function(`${i.default.self}`,`${i.default.scope}`,g)(this,this.scope.get());if(this.scope.value(h,{ref:n}),n.errors=null,n.schema=e.schema,n.schemaEnv=e,e.$async&&(n.$async=!0),!0===this.opts.code.source&&(n.source={validateName:h,validateCode:t,scopeValues:p._values}),this.opts.unevaluated){const{props:e,items:t}=m;n.evaluated={props:e instanceof r.Name?void 0:e,items:t instanceof r.Name?void 0:t,dynamicProps:e instanceof r.Name,dynamicItems:t instanceof r.Name},n.source&&(n.source.evaluated=r.stringify(n.evaluated))}return e.validate=n,e}catch(t){throw delete e.validate,delete e.validateName,g&&this.logger.error("Error compiling schema, function code:",g),t}finally{this._compilations.delete(e)}}function d(e){return a.inlineRef(e.schema,this.opts.inlineRefs)?e.schema:e.validate?e:p.call(this,e)}function f(e){for(const r of this._compilations)if(n=e,(t=r).schema===n.schema&&t.root===n.root&&t.baseId===n.baseId)return r;var t,n}function h(e,t){let n;for(;"string"==typeof(n=this.refs[t]);)t=n;return n||this.schemas[t]||m.call(this,e,t)}function m(e,t){const n=c.parse(t),r=a._getFullPath(n);let o=a.getFullPath(e.baseId);if(Object.keys(e.schema).length>0&&r===o)return y.call(this,n,e);const i=a.normalizeId(r),s=this.refs[i]||this.schemas[i];if("string"==typeof s){const t=m.call(this,e,s);if("object"!=typeof(null==t?void 0:t.schema))return;return y.call(this,n,t)}if("object"==typeof(null==s?void 0:s.schema)){if(s.validate||p.call(this,s),i===a.normalizeId(t)){const{schema:t}=s,{schemaId:n}=this.opts,r=t[n];return r&&(o=a.resolveUrl(o,r)),new u({schema:t,schemaId:n,root:e,baseId:o})}return y.call(this,n,s)}}t.SchemaEnv=u,t.compileSchema=p,t.resolveRef=function(e,t,n){var r;const o=a.resolveUrl(t,n),i=e.refs[o];if(i)return i;let s=h.call(this,e,o);if(void 0===s){const n=null===(r=e.localRefs)||void 0===r?void 0:r[o],{schemaId:i}=this.opts;n&&(s=new u({schema:n,schemaId:i,root:e,baseId:t}))}if(void 0===s&&this.opts.loadSchemaSync){const r=this.opts.loadSchemaSync(t,n,o);!r||this.refs[o]||this.schemas[o]||(this.addSchema(r,o,void 0),s=h.call(this,e,o))}return void 0!==s?e.refs[o]=d.call(this,s):void 0},t.getCompilingSchema=f,t.resolveSchema=m;const g=new Set(["properties","patternProperties","enum","dependencies","definitions"]);function y(e,{baseId:t,schema:n,root:r}){var o;if("/"!==(null===(o=e.fragment)||void 0===o?void 0:o[0]))return;for(const r of e.fragment.slice(1).split("/")){if("boolean"==typeof n)return;if(void 0===(n=n[s.unescapeFragment(r)]))return;const e="object"==typeof n&&n[this.opts.schemaId];!g.has(r)&&e&&(t=a.resolveUrl(t,e))}let i;if("boolean"!=typeof n&&n.$ref&&!s.schemaHasRulesButRef(n,this.RULES)){const e=a.resolveUrl(t,n.$ref);i=m.call(this,r,e)}const{schemaId:l}=this.opts;return i=i||new u({schema:n,schemaId:l,root:r,baseId:t}),i.schema!==i.root.schema?i:void 0}},5018:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={data:new r.Name("data"),valCxt:new r.Name("valCxt"),instancePath:new r.Name("instancePath"),parentData:new r.Name("parentData"),parentDataProperty:new r.Name("parentDataProperty"),rootData:new r.Name("rootData"),dynamicAnchors:new r.Name("dynamicAnchors"),vErrors:new r.Name("vErrors"),errors:new r.Name("errors"),this:new r.Name("this"),self:new r.Name("self"),scope:new r.Name("scope"),json:new r.Name("json"),jsonPos:new r.Name("jsonPos"),jsonLen:new r.Name("jsonLen"),jsonPart:new r.Name("jsonPart")};t.default=o},4143:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(9826);class o extends Error{constructor(e,t,n){super(n||`can't resolve reference ${t} from id ${e}`),this.missingRef=r.resolveUrl(e,t),this.missingSchema=r.normalizeId(r.getFullPath(this.missingRef))}}t.default=o},9826:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getSchemaRefs=t.resolveUrl=t.normalizeId=t._getFullPath=t.getFullPath=t.inlineRef=void 0;const r=n(6124),o=n(4063),i=n(4029),a=n(540),s=new Set(["type","format","pattern","maxLength","minLength","maxProperties","minProperties","maxItems","minItems","maximum","minimum","uniqueItems","multipleOf","required","enum","const"]);t.inlineRef=function(e,t=!0){return"boolean"==typeof e||(!0===t?!c(e):!!t&&u(e)<=t)};const l=new Set(["$ref","$recursiveRef","$recursiveAnchor","$dynamicRef","$dynamicAnchor"]);function c(e){for(const t in e){if(l.has(t))return!0;const n=e[t];if(Array.isArray(n)&&n.some(c))return!0;if("object"==typeof n&&c(n))return!0}return!1}function u(e){let t=0;for(const n in e){if("$ref"===n)return 1/0;if(t++,!s.has(n)&&("object"==typeof e[n]&&r.eachItem(e[n],(e=>t+=u(e))),t===1/0))return 1/0}return t}function p(e="",t){return!1!==t&&(e=h(e)),d(a.parse(e))}function d(e){return a.serialize(e).split("#")[0]+"#"}t.getFullPath=p,t._getFullPath=d;const f=/#\/?$/;function h(e){return e?e.replace(f,""):""}t.normalizeId=h,t.resolveUrl=function(e,t){return t=h(t),a.resolve(e,t)};const m=/^[a-z_][-a-z0-9._]*$/i;t.getSchemaRefs=function(e){if("boolean"==typeof e)return{};const{schemaId:t}=this.opts,n=h(e[t]),r={"":n},s=p(n,!1),l={},c=new Set;return i(e,{allKeys:!0},((e,n,o,i)=>{if(void 0===i)return;const p=s+n;let f=r[i];function g(t){if(t=h(f?a.resolve(f,t):t),c.has(t))throw d(t);c.add(t);let n=this.refs[t];return"string"==typeof n&&(n=this.refs[n]),"object"==typeof n?u(e,n.schema,t):t!==h(p)&&("#"===t[0]?(u(e,l[t],t),l[t]=e):this.refs[t]=p),t}function y(e){if("string"==typeof e){if(!m.test(e))throw new Error(`invalid anchor "${e}"`);g.call(this,`#${e}`)}}"string"==typeof e[t]&&(f=g.call(this,e[t])),y.call(this,e.$anchor),y.call(this,e.$dynamicAnchor),r[n]=f})),l;function u(e,t,n){if(void 0!==t&&!o(e,t))throw d(n)}function d(e){return new Error(`reference "${e}" resolves to more than one schema`)}}},3664:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getRules=t.isJSONType=void 0;const n=new Set(["string","number","integer","boolean","null","object","array"]);t.isJSONType=function(e){return"string"==typeof e&&n.has(e)},t.getRules=function(){const e={number:{type:"number",rules:[]},string:{type:"string",rules:[]},array:{type:"array",rules:[]},object:{type:"object",rules:[]}};return{types:{...e,integer:!0,boolean:!0,null:!0},rules:[{rules:[]},e.number,e.string,e.array,e.object],post:{rules:[]},all:{},keywords:{}}}},6124:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.checkStrictMode=t.getErrorPath=t.Type=t.useFunc=t.setEvaluated=t.evaluatedPropsToName=t.mergeEvaluated=t.eachItem=t.unescapeJsonPointer=t.escapeJsonPointer=t.escapeFragment=t.unescapeFragment=t.schemaRefOrVal=t.schemaHasRulesButRef=t.schemaHasRules=t.checkUnknownRules=t.alwaysValidSchema=t.toHash=void 0;const r=n(4475),o=n(4667);function i(e,t=e.schema){const{opts:n,self:r}=e;if(!n.strictSchema)return;if("boolean"==typeof t)return;const o=r.RULES.keywords;for(const n in t)o[n]||h(e,`unknown keyword: "${n}"`)}function a(e,t){if("boolean"==typeof e)return!e;for(const n in e)if(t[n])return!0;return!1}function s(e){return"number"==typeof e?`${e}`:e.replace(/~/g,"~0").replace(/\//g,"~1")}function l(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function c({mergeNames:e,mergeToName:t,mergeValues:n,resultToName:o}){return(i,a,s,l)=>{const c=void 0===s?a:s instanceof r.Name?(a instanceof r.Name?e(i,a,s):t(i,a,s),s):a instanceof r.Name?(t(i,s,a),a):n(a,s);return l!==r.Name||c instanceof r.Name?c:o(i,c)}}function u(e,t){if(!0===t)return e.var("props",!0);const n=e.var("props",r._`{}`);return void 0!==t&&p(e,n,t),n}function p(e,t,n){Object.keys(n).forEach((n=>e.assign(r._`${t}${r.getProperty(n)}`,!0)))}t.toHash=function(e){const t={};for(const n of e)t[n]=!0;return t},t.alwaysValidSchema=function(e,t){return"boolean"==typeof t?t:0===Object.keys(t).length||(i(e,t),!a(t,e.self.RULES.all))},t.checkUnknownRules=i,t.schemaHasRules=a,t.schemaHasRulesButRef=function(e,t){if("boolean"==typeof e)return!e;for(const n in e)if("$ref"!==n&&t.all[n])return!0;return!1},t.schemaRefOrVal=function({topSchemaRef:e,schemaPath:t},n,o,i){if(!i){if("number"==typeof n||"boolean"==typeof n)return n;if("string"==typeof n)return r._`${n}`}return r._`${e}${t}${r.getProperty(o)}`},t.unescapeFragment=function(e){return l(decodeURIComponent(e))},t.escapeFragment=function(e){return encodeURIComponent(s(e))},t.escapeJsonPointer=s,t.unescapeJsonPointer=l,t.eachItem=function(e,t){if(Array.isArray(e))for(const n of e)t(n);else t(e)},t.mergeEvaluated={props:c({mergeNames:(e,t,n)=>e.if(r._`${n} !== true && ${t} !== undefined`,(()=>{e.if(r._`${t} === true`,(()=>e.assign(n,!0)),(()=>e.assign(n,r._`${n} || {}`).code(r._`Object.assign(${n}, ${t})`)))})),mergeToName:(e,t,n)=>e.if(r._`${n} !== true`,(()=>{!0===t?e.assign(n,!0):(e.assign(n,r._`${n} || {}`),p(e,n,t))})),mergeValues:(e,t)=>!0===e||{...e,...t},resultToName:u}),items:c({mergeNames:(e,t,n)=>e.if(r._`${n} !== true && ${t} !== undefined`,(()=>e.assign(n,r._`${t} === true ? true : ${n} > ${t} ? ${n} : ${t}`))),mergeToName:(e,t,n)=>e.if(r._`${n} !== true`,(()=>e.assign(n,!0===t||r._`${n} > ${t} ? ${n} : ${t}`))),mergeValues:(e,t)=>!0===e||Math.max(e,t),resultToName:(e,t)=>e.var("items",t)})},t.evaluatedPropsToName=u,t.setEvaluated=p;const d={};var f;function h(e,t,n=e.opts.strictSchema){if(n){if(t=`strict mode: ${t}`,!0===n)throw new Error(t);e.self.logger.warn(t)}}t.useFunc=function(e,t){return e.scopeValue("func",{ref:t,code:d[t.code]||(d[t.code]=new o._Code(t.code))})},function(e){e[e.Num=0]="Num",e[e.Str=1]="Str"}(f=t.Type||(t.Type={})),t.getErrorPath=function(e,t,n){if(e instanceof r.Name){const o=t===f.Num;return n?o?r._`"[" + ${e} + "]"`:r._`"['" + ${e} + "']"`:o?r._`"/" + ${e}`:r._`"/" + ${e}.replace(/~/g, "~0").replace(/\\//g, "~1")`}return n?r.getProperty(e).toString():"/"+s(e)},t.checkStrictMode=h},4566:function(e,t){"use strict";function n(e,t){return t.rules.some((t=>r(e,t)))}function r(e,t){var n;return void 0!==e[t.keyword]||(null===(n=t.definition.implements)||void 0===n?void 0:n.some((t=>void 0!==e[t])))}Object.defineProperty(t,"__esModule",{value:!0}),t.shouldUseRule=t.shouldUseGroup=t.schemaHasRulesForType=void 0,t.schemaHasRulesForType=function({schema:e,self:t},r){const o=t.RULES.types[r];return o&&!0!==o&&n(e,o)},t.shouldUseGroup=n,t.shouldUseRule=r},7627:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.boolOrEmptySchema=t.topBoolOrEmptySchema=void 0;const r=n(1885),o=n(4475),i=n(5018),a={message:"boolean schema is false"};function s(e,t){const{gen:n,data:o}=e,i={gen:n,keyword:"false schema",data:o,schema:!1,schemaCode:!1,schemaValue:!1,params:{},it:e};r.reportError(i,a,void 0,t)}t.topBoolOrEmptySchema=function(e){const{gen:t,schema:n,validateName:r}=e;!1===n?s(e,!1):"object"==typeof n&&!0===n.$async?t.return(i.default.data):(t.assign(o._`${r}.errors`,null),t.return(!0))},t.boolOrEmptySchema=function(e,t){const{gen:n,schema:r}=e;!1===r?(n.var(t,!1),s(e)):n.var(t,!0)}},7927:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reportTypeError=t.checkDataTypes=t.checkDataType=t.coerceAndCheckDataType=t.getJSONTypes=t.getSchemaTypes=t.DataType=void 0;const r=n(3664),o=n(4566),i=n(1885),a=n(4475),s=n(6124);var l;function c(e){const t=Array.isArray(e)?e:e?[e]:[];if(t.every(r.isJSONType))return t;throw new Error("type must be JSONType or JSONType[]: "+t.join(","))}!function(e){e[e.Correct=0]="Correct",e[e.Wrong=1]="Wrong"}(l=t.DataType||(t.DataType={})),t.getSchemaTypes=function(e){const t=c(e.type);if(t.includes("null")){if(!1===e.nullable)throw new Error("type: null contradicts nullable: false")}else{if(!t.length&&void 0!==e.nullable)throw new Error('"nullable" cannot be used without "type"');!0===e.nullable&&t.push("null")}return t},t.getJSONTypes=c,t.coerceAndCheckDataType=function(e,t){const{gen:n,data:r,opts:i}=e,s=function(e,t){return t?e.filter((e=>u.has(e)||"array"===t&&"array"===e)):[]}(t,i.coerceTypes),c=t.length>0&&!(0===s.length&&1===t.length&&o.schemaHasRulesForType(e,t[0]));if(c){const o=d(t,r,i.strictNumbers,l.Wrong);n.if(o,(()=>{s.length?function(e,t,n){const{gen:r,data:o,opts:i}=e,s=r.let("dataType",a._`typeof ${o}`),l=r.let("coerced",a._`undefined`);"array"===i.coerceTypes&&r.if(a._`${s} == 'object' && Array.isArray(${o}) && ${o}.length == 1`,(()=>r.assign(o,a._`${o}[0]`).assign(s,a._`typeof ${o}`).if(d(t,o,i.strictNumbers),(()=>r.assign(l,o))))),r.if(a._`${l} !== undefined`);for(const e of n)(u.has(e)||"array"===e&&"array"===i.coerceTypes)&&c(e);function c(e){switch(e){case"string":return void r.elseIf(a._`${s} == "number" || ${s} == "boolean"`).assign(l,a._`"" + ${o}`).elseIf(a._`${o} === null`).assign(l,a._`""`);case"number":return void r.elseIf(a._`${s} == "boolean" || ${o} === null - || (${s} == "string" && ${o} && ${o} == +${o})`).assign(l,a._`+${o}`);case"integer":return void r.elseIf(a._`${s} === "boolean" || ${o} === null - || (${s} === "string" && ${o} && ${o} == +${o} && !(${o} % 1))`).assign(l,a._`+${o}`);case"boolean":return void r.elseIf(a._`${o} === "false" || ${o} === 0 || ${o} === null`).assign(l,!1).elseIf(a._`${o} === "true" || ${o} === 1`).assign(l,!0);case"null":return r.elseIf(a._`${o} === "" || ${o} === 0 || ${o} === false`),void r.assign(l,null);case"array":r.elseIf(a._`${s} === "string" || ${s} === "number" - || ${s} === "boolean" || ${o} === null`).assign(l,a._`[${o}]`)}}r.else(),h(e),r.endIf(),r.if(a._`${l} !== undefined`,(()=>{r.assign(o,l),function({gen:e,parentData:t,parentDataProperty:n},r){e.if(a._`${t} !== undefined`,(()=>e.assign(a._`${t}[${n}]`,r)))}(e,l)}))}(e,t,s):h(e)}))}return c};const u=new Set(["string","number","integer","boolean","null"]);function p(e,t,n,r=l.Correct){const o=r===l.Correct?a.operators.EQ:a.operators.NEQ;let i;switch(e){case"null":return a._`${t} ${o} null`;case"array":i=a._`Array.isArray(${t})`;break;case"object":i=a._`${t} && typeof ${t} == "object" && !Array.isArray(${t})`;break;case"integer":i=s(a._`!(${t} % 1) && !isNaN(${t})`);break;case"number":i=s();break;default:return a._`typeof ${t} ${o} ${e}`}return r===l.Correct?i:a.not(i);function s(e=a.nil){return a.and(a._`typeof ${t} == "number"`,e,n?a._`isFinite(${t})`:a.nil)}}function d(e,t,n,r){if(1===e.length)return p(e[0],t,n,r);let o;const i=s.toHash(e);if(i.array&&i.object){const e=a._`typeof ${t} != "object"`;o=i.null?e:a._`!${t} || ${e}`,delete i.null,delete i.array,delete i.object}else o=a.nil;i.number&&delete i.integer;for(const e in i)o=a.and(o,p(e,t,n,r));return o}t.checkDataType=p,t.checkDataTypes=d;const f={message:({schema:e})=>`must be ${e}`,params:({schema:e,schemaValue:t})=>"string"==typeof e?a._`{type: ${e}}`:a._`{type: ${t}}`};function h(e){const t=function(e){const{gen:t,data:n,schema:r}=e,o=s.schemaRefOrVal(e,r,"type");return{gen:t,keyword:"type",data:n,schema:r.type,schemaCode:o,schemaValue:o,parentSchema:r,params:{},it:e}}(e);i.reportError(t,f)}t.reportTypeError=h},2537:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.assignDefaults=void 0;const r=n(4475),o=n(6124);function i(e,t,n){const{gen:i,compositeRule:a,data:s,opts:l}=e;if(void 0===n)return;const c=r._`${s}${r.getProperty(t)}`;if(a)return void o.checkStrictMode(e,`default is ignored for: ${c}`);let u=r._`${c} === undefined`;"empty"===l.useDefaults&&(u=r._`${u} || ${c} === null || ${c} === ""`),i.if(u,r._`${c} = ${r.stringify(n)}`)}t.assignDefaults=function(e,t){const{properties:n,items:r}=e.schema;if("object"===t&&n)for(const t in n)i(e,t,n[t].default);else"array"===t&&Array.isArray(r)&&r.forEach(((t,n)=>i(e,n,t.default)))}},1321:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getData=t.KeywordCxt=t.validateFunctionCode=void 0;const r=n(7627),o=n(7927),i=n(4566),a=n(7927),s=n(2537),l=n(6488),c=n(4688),u=n(4475),p=n(5018),d=n(9826),f=n(6124),h=n(1885);function m({gen:e,validateName:t,schema:n,schemaEnv:r,opts:o},i){o.code.es5?e.func(t,u._`${p.default.data}, ${p.default.valCxt}`,r.$async,(()=>{e.code(u._`"use strict"; ${g(n,o)}`),function(e,t){e.if(p.default.valCxt,(()=>{e.var(p.default.instancePath,u._`${p.default.valCxt}.${p.default.instancePath}`),e.var(p.default.parentData,u._`${p.default.valCxt}.${p.default.parentData}`),e.var(p.default.parentDataProperty,u._`${p.default.valCxt}.${p.default.parentDataProperty}`),e.var(p.default.rootData,u._`${p.default.valCxt}.${p.default.rootData}`),t.dynamicRef&&e.var(p.default.dynamicAnchors,u._`${p.default.valCxt}.${p.default.dynamicAnchors}`)}),(()=>{e.var(p.default.instancePath,u._`""`),e.var(p.default.parentData,u._`undefined`),e.var(p.default.parentDataProperty,u._`undefined`),e.var(p.default.rootData,p.default.data),t.dynamicRef&&e.var(p.default.dynamicAnchors,u._`{}`)}))}(e,o),e.code(i)})):e.func(t,u._`${p.default.data}, ${function(e){return u._`{${p.default.instancePath}="", ${p.default.parentData}, ${p.default.parentDataProperty}, ${p.default.rootData}=${p.default.data}${e.dynamicRef?u._`, ${p.default.dynamicAnchors}={}`:u.nil}}={}`}(o)}`,r.$async,(()=>e.code(g(n,o)).code(i)))}function g(e,t){const n="object"==typeof e&&e[t.schemaId];return n&&(t.code.source||t.code.process)?u._`/*# sourceURL=${n} */`:u.nil}function y({schema:e,self:t}){if("boolean"==typeof e)return!e;for(const n in e)if(t.RULES.all[n])return!0;return!1}function v(e){return"boolean"!=typeof e.schema}function b(e){f.checkUnknownRules(e),function(e){const{schema:t,errSchemaPath:n,opts:r,self:o}=e;t.$ref&&r.ignoreKeywordsWithRef&&f.schemaHasRulesButRef(t,o.RULES)&&o.logger.warn(`$ref: keywords ignored in schema at path "${n}"`)}(e)}function w(e,t){if(e.opts.jtd)return k(e,[],!1,t);const n=o.getSchemaTypes(e.schema);k(e,n,!o.coerceAndCheckDataType(e,n),t)}function x({gen:e,schemaEnv:t,schema:n,errSchemaPath:r,opts:o}){const i=n.$comment;if(!0===o.$comment)e.code(u._`${p.default.self}.logger.log(${i})`);else if("function"==typeof o.$comment){const n=u.str`${r}/$comment`,o=e.scopeValue("root",{ref:t.root});e.code(u._`${p.default.self}.opts.$comment(${i}, ${n}, ${o}.schema)`)}}function k(e,t,n,r){const{gen:o,schema:s,data:l,allErrors:c,opts:d,self:h}=e,{RULES:m}=h;function g(f){i.shouldUseGroup(s,f)&&(f.type?(o.if(a.checkDataType(f.type,l,d.strictNumbers)),_(e,f),1===t.length&&t[0]===f.type&&n&&(o.else(),a.reportTypeError(e)),o.endIf()):_(e,f),c||o.if(u._`${p.default.errors} === ${r||0}`))}!s.$ref||!d.ignoreKeywordsWithRef&&f.schemaHasRulesButRef(s,m)?(d.jtd||function(e,t){!e.schemaEnv.meta&&e.opts.strictTypes&&(function(e,t){t.length&&(e.dataTypes.length?(t.forEach((t=>{O(e.dataTypes,t)||S(e,`type "${t}" not allowed by context "${e.dataTypes.join(",")}"`)})),e.dataTypes=e.dataTypes.filter((e=>O(t,e)))):e.dataTypes=t)}(e,t),e.opts.allowUnionTypes||function(e,t){t.length>1&&(2!==t.length||!t.includes("null"))&&S(e,"use allowUnionTypes to allow union type keyword")}(e,t),function(e,t){const n=e.self.RULES.all;for(const r in n){const o=n[r];if("object"==typeof o&&i.shouldUseRule(e.schema,o)){const{type:n}=o.definition;n.length&&!n.some((e=>{return r=e,(n=t).includes(r)||"number"===r&&n.includes("integer");var n,r}))&&S(e,`missing type "${n.join(",")}" for keyword "${r}"`)}}}(e,e.dataTypes))}(e,t),o.block((()=>{for(const e of m.rules)g(e);g(m.post)}))):o.block((()=>P(e,"$ref",m.all.$ref.definition)))}function _(e,t){const{gen:n,schema:r,opts:{useDefaults:o}}=e;o&&s.assignDefaults(e,t.type),n.block((()=>{for(const n of t.rules)i.shouldUseRule(r,n)&&P(e,n.keyword,n.definition,t.type)}))}function O(e,t){return e.includes(t)||"integer"===t&&e.includes("number")}function S(e,t){t+=` at "${e.schemaEnv.baseId+e.errSchemaPath}" (strictTypes)`,f.checkStrictMode(e,t,e.opts.strictTypes)}t.validateFunctionCode=function(e){v(e)&&(b(e),y(e))?function(e){const{schema:t,opts:n,gen:r}=e;m(e,(()=>{n.$comment&&t.$comment&&x(e),function(e){const{schema:t,opts:n}=e;void 0!==t.default&&n.useDefaults&&n.strictSchema&&f.checkStrictMode(e,"default is ignored in the schema root")}(e),r.let(p.default.vErrors,null),r.let(p.default.errors,0),n.unevaluated&&function(e){const{gen:t,validateName:n}=e;e.evaluated=t.const("evaluated",u._`${n}.evaluated`),t.if(u._`${e.evaluated}.dynamicProps`,(()=>t.assign(u._`${e.evaluated}.props`,u._`undefined`))),t.if(u._`${e.evaluated}.dynamicItems`,(()=>t.assign(u._`${e.evaluated}.items`,u._`undefined`)))}(e),w(e),function(e){const{gen:t,schemaEnv:n,validateName:r,ValidationError:o,opts:i}=e;n.$async?t.if(u._`${p.default.errors} === 0`,(()=>t.return(p.default.data)),(()=>t.throw(u._`new ${o}(${p.default.vErrors})`))):(t.assign(u._`${r}.errors`,p.default.vErrors),i.unevaluated&&function({gen:e,evaluated:t,props:n,items:r}){n instanceof u.Name&&e.assign(u._`${t}.props`,n),r instanceof u.Name&&e.assign(u._`${t}.items`,r)}(e),t.return(u._`${p.default.errors} === 0`))}(e)}))}(e):m(e,(()=>r.topBoolOrEmptySchema(e)))};class E{constructor(e,t,n){if(l.validateKeywordUsage(e,t,n),this.gen=e.gen,this.allErrors=e.allErrors,this.keyword=n,this.data=e.data,this.schema=e.schema[n],this.$data=t.$data&&e.opts.$data&&this.schema&&this.schema.$data,this.schemaValue=f.schemaRefOrVal(e,this.schema,n,this.$data),this.schemaType=t.schemaType,this.parentSchema=e.schema,this.params={},this.it=e,this.def=t,this.$data)this.schemaCode=e.gen.const("vSchema",C(this.$data,e));else if(this.schemaCode=this.schemaValue,!l.validSchemaType(this.schema,t.schemaType,t.allowUndefined))throw new Error(`${n} value must be ${JSON.stringify(t.schemaType)}`);("code"in t?t.trackErrors:!1!==t.errors)&&(this.errsCount=e.gen.const("_errs",p.default.errors))}result(e,t,n){this.gen.if(u.not(e)),n?n():this.error(),t?(this.gen.else(),t(),this.allErrors&&this.gen.endIf()):this.allErrors?this.gen.endIf():this.gen.else()}pass(e,t){this.result(e,void 0,t)}fail(e){if(void 0===e)return this.error(),void(this.allErrors||this.gen.if(!1));this.gen.if(e),this.error(),this.allErrors?this.gen.endIf():this.gen.else()}fail$data(e){if(!this.$data)return this.fail(e);const{schemaCode:t}=this;this.fail(u._`${t} !== undefined && (${u.or(this.invalid$data(),e)})`)}error(e,t,n){if(t)return this.setParams(t),this._error(e,n),void this.setParams({});this._error(e,n)}_error(e,t){(e?h.reportExtraError:h.reportError)(this,this.def.error,t)}$dataError(){h.reportError(this,this.def.$dataError||h.keyword$DataError)}reset(){if(void 0===this.errsCount)throw new Error('add "trackErrors" to keyword definition');h.resetErrorsCount(this.gen,this.errsCount)}ok(e){this.allErrors||this.gen.if(e)}setParams(e,t){t?Object.assign(this.params,e):this.params=e}block$data(e,t,n=u.nil){this.gen.block((()=>{this.check$data(e,n),t()}))}check$data(e=u.nil,t=u.nil){if(!this.$data)return;const{gen:n,schemaCode:r,schemaType:o,def:i}=this;n.if(u.or(u._`${r} === undefined`,t)),e!==u.nil&&n.assign(e,!0),(o.length||i.validateSchema)&&(n.elseIf(this.invalid$data()),this.$dataError(),e!==u.nil&&n.assign(e,!1)),n.else()}invalid$data(){const{gen:e,schemaCode:t,schemaType:n,def:r,it:o}=this;return u.or(function(){if(n.length){if(!(t instanceof u.Name))throw new Error("ajv implementation error");const e=Array.isArray(n)?n:[n];return u._`${a.checkDataTypes(e,t,o.opts.strictNumbers,a.DataType.Wrong)}`}return u.nil}(),function(){if(r.validateSchema){const n=e.scopeValue("validate$data",{ref:r.validateSchema});return u._`!${n}(${t})`}return u.nil}())}subschema(e,t){const n=c.getSubschema(this.it,e);c.extendSubschemaData(n,this.it,e),c.extendSubschemaMode(n,e);const o={...this.it,...n,items:void 0,props:void 0};return function(e,t){v(e)&&(b(e),y(e))?function(e,t){const{schema:n,gen:r,opts:o}=e;o.$comment&&n.$comment&&x(e),function(e){const t=e.schema[e.opts.schemaId];t&&(e.baseId=d.resolveUrl(e.baseId,t))}(e),function(e){if(e.schema.$async&&!e.schemaEnv.$async)throw new Error("async schema in sync schema")}(e);const i=r.const("_errs",p.default.errors);w(e,i),r.var(t,u._`${i} === ${p.default.errors}`)}(e,t):r.boolOrEmptySchema(e,t)}(o,t),o}mergeEvaluated(e,t){const{it:n,gen:r}=this;n.opts.unevaluated&&(!0!==n.props&&void 0!==e.props&&(n.props=f.mergeEvaluated.props(r,e.props,n.props,t)),!0!==n.items&&void 0!==e.items&&(n.items=f.mergeEvaluated.items(r,e.items,n.items,t)))}mergeValidEvaluated(e,t){const{it:n,gen:r}=this;if(n.opts.unevaluated&&(!0!==n.props||!0!==n.items))return r.if(t,(()=>this.mergeEvaluated(e,u.Name))),!0}}function P(e,t,n,r){const o=new E(e,n,t);"code"in n?n.code(o,r):o.$data&&n.validate?l.funcKeywordCode(o,n):"macro"in n?l.macroKeywordCode(o,n):(n.compile||n.validate)&&l.funcKeywordCode(o,n)}t.KeywordCxt=E;const A=/^\/(?:[^~]|~0|~1)*$/,$=/^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;function C(e,{dataLevel:t,dataNames:n,dataPathArr:r}){let o,i;if(""===e)return p.default.rootData;if("/"===e[0]){if(!A.test(e))throw new Error(`Invalid JSON-pointer: ${e}`);o=e,i=p.default.rootData}else{const a=$.exec(e);if(!a)throw new Error(`Invalid JSON-pointer: ${e}`);const s=+a[1];if(o=a[2],"#"===o){if(s>=t)throw new Error(l("property/index",s));return r[t-s]}if(s>t)throw new Error(l("data",s));if(i=n[t-s],!o)return i}let a=i;const s=o.split("/");for(const e of s)e&&(i=u._`${i}${u.getProperty(f.unescapeJsonPointer(e))}`,a=u._`${a} && ${i}`);return a;function l(e,n){return`Cannot access ${e} ${n} levels up, current level is ${t}`}}t.getData=C},6488:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateKeywordUsage=t.validSchemaType=t.funcKeywordCode=t.macroKeywordCode=void 0;const r=n(4475),o=n(5018),i=n(8619),a=n(1885);function s(e){const{gen:t,data:n,it:o}=e;t.if(o.parentData,(()=>t.assign(n,r._`${o.parentData}[${o.parentDataProperty}]`)))}function l(e,t,n){if(void 0===n)throw new Error(`keyword "${t}" failed to compile`);return e.scopeValue("keyword","function"==typeof n?{ref:n}:{ref:n,code:r.stringify(n)})}t.macroKeywordCode=function(e,t){const{gen:n,keyword:o,schema:i,parentSchema:a,it:s}=e,c=t.macro.call(s.self,i,a,s),u=l(n,o,c);!1!==s.opts.validateSchema&&s.self.validateSchema(c,!0);const p=n.name("valid");e.subschema({schema:c,schemaPath:r.nil,errSchemaPath:`${s.errSchemaPath}/${o}`,topSchemaRef:u,compositeRule:!0},p),e.pass(p,(()=>e.error(!0)))},t.funcKeywordCode=function(e,t){var n;const{gen:c,keyword:u,schema:p,parentSchema:d,$data:f,it:h}=e;!function({schemaEnv:e},t){if(t.async&&!e.$async)throw new Error("async keyword in sync schema")}(h,t);const m=!f&&t.compile?t.compile.call(h.self,p,d,h):t.validate,g=l(c,u,m),y=c.let("valid");function v(n=(t.async?r._`await `:r.nil)){const a=h.opts.passContext?o.default.this:o.default.self,s=!("compile"in t&&!f||!1===t.schema);c.assign(y,r._`${n}${i.callValidateCode(e,g,a,s)}`,t.modifying)}function b(e){var n;c.if(r.not(null!==(n=t.valid)&&void 0!==n?n:y),e)}e.block$data(y,(function(){if(!1===t.errors)v(),t.modifying&&s(e),b((()=>e.error()));else{const n=t.async?function(){const e=c.let("ruleErrs",null);return c.try((()=>v(r._`await `)),(t=>c.assign(y,!1).if(r._`${t} instanceof ${h.ValidationError}`,(()=>c.assign(e,r._`${t}.errors`)),(()=>c.throw(t))))),e}():function(){const e=r._`${g}.errors`;return c.assign(e,null),v(r.nil),e}();t.modifying&&s(e),b((()=>function(e,t){const{gen:n}=e;n.if(r._`Array.isArray(${t})`,(()=>{n.assign(o.default.vErrors,r._`${o.default.vErrors} === null ? ${t} : ${o.default.vErrors}.concat(${t})`).assign(o.default.errors,r._`${o.default.vErrors}.length`),a.extendErrors(e)}),(()=>e.error()))}(e,n)))}})),e.ok(null!==(n=t.valid)&&void 0!==n?n:y)},t.validSchemaType=function(e,t,n=!1){return!t.length||t.some((t=>"array"===t?Array.isArray(e):"object"===t?e&&"object"==typeof e&&!Array.isArray(e):typeof e==t||n&&void 0===e))},t.validateKeywordUsage=function({schema:e,opts:t,self:n,errSchemaPath:r},o,i){if(Array.isArray(o.keyword)?!o.keyword.includes(i):o.keyword!==i)throw new Error("ajv implementation error");const a=o.dependencies;if(null==a?void 0:a.some((t=>!Object.prototype.hasOwnProperty.call(e,t))))throw new Error(`parent schema must have dependencies of ${i}: ${a.join(",")}`);if(o.validateSchema&&!o.validateSchema(e[i])){const e=`keyword "${i}" value is invalid at path "${r}": `+n.errorsText(o.validateSchema.errors);if("log"!==t.validateSchema)throw new Error(e);n.logger.error(e)}}},4688:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.extendSubschemaMode=t.extendSubschemaData=t.getSubschema=void 0;const r=n(4475),o=n(6124);t.getSubschema=function(e,{keyword:t,schemaProp:n,schema:i,schemaPath:a,errSchemaPath:s,topSchemaRef:l}){if(void 0!==t&&void 0!==i)throw new Error('both "keyword" and "schema" passed, only one allowed');if(void 0!==t){const i=e.schema[t];return void 0===n?{schema:i,schemaPath:r._`${e.schemaPath}${r.getProperty(t)}`,errSchemaPath:`${e.errSchemaPath}/${t}`}:{schema:i[n],schemaPath:r._`${e.schemaPath}${r.getProperty(t)}${r.getProperty(n)}`,errSchemaPath:`${e.errSchemaPath}/${t}/${o.escapeFragment(n)}`}}if(void 0!==i){if(void 0===a||void 0===s||void 0===l)throw new Error('"schemaPath", "errSchemaPath" and "topSchemaRef" are required with "schema"');return{schema:i,schemaPath:a,topSchemaRef:l,errSchemaPath:s}}throw new Error('either "keyword" or "schema" must be passed')},t.extendSubschemaData=function(e,t,{dataProp:n,dataPropType:i,data:a,dataTypes:s,propertyName:l}){if(void 0!==a&&void 0!==n)throw new Error('both "data" and "dataProp" passed, only one allowed');const{gen:c}=t;if(void 0!==n){const{errorPath:a,dataPathArr:s,opts:l}=t;u(c.let("data",r._`${t.data}${r.getProperty(n)}`,!0)),e.errorPath=r.str`${a}${o.getErrorPath(n,i,l.jsPropertySyntax)}`,e.parentDataProperty=r._`${n}`,e.dataPathArr=[...s,e.parentDataProperty]}function u(n){e.data=n,e.dataLevel=t.dataLevel+1,e.dataTypes=[],t.definedProperties=new Set,e.parentData=t.data,e.dataNames=[...t.dataNames,n]}void 0!==a&&(u(a instanceof r.Name?a:c.let("data",a,!0)),void 0!==l&&(e.propertyName=l)),s&&(e.dataTypes=s)},t.extendSubschemaMode=function(e,{jtdDiscriminator:t,jtdMetadata:n,compositeRule:r,createErrors:o,allErrors:i}){void 0!==r&&(e.compositeRule=r),void 0!==o&&(e.createErrors=o),void 0!==i&&(e.allErrors=i),e.jtdDiscriminator=t,e.jtdMetadata=n}},3325:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CodeGen=t.Name=t.nil=t.stringify=t.str=t._=t.KeywordCxt=void 0;var r=n(1321);Object.defineProperty(t,"KeywordCxt",{enumerable:!0,get:function(){return r.KeywordCxt}});var o=n(4475);Object.defineProperty(t,"_",{enumerable:!0,get:function(){return o._}}),Object.defineProperty(t,"str",{enumerable:!0,get:function(){return o.str}}),Object.defineProperty(t,"stringify",{enumerable:!0,get:function(){return o.stringify}}),Object.defineProperty(t,"nil",{enumerable:!0,get:function(){return o.nil}}),Object.defineProperty(t,"Name",{enumerable:!0,get:function(){return o.Name}}),Object.defineProperty(t,"CodeGen",{enumerable:!0,get:function(){return o.CodeGen}});const i=n(8451),a=n(4143),s=n(3664),l=n(7805),c=n(4475),u=n(9826),p=n(7927),d=n(6124),f=n(425),h=["removeAdditional","useDefaults","coerceTypes"],m=new Set(["validate","serialize","parse","wrapper","root","schema","keyword","pattern","formats","validate$data","func","obj","Error"]),g={errorDataPath:"",format:"`validateFormats: false` can be used instead.",nullable:'"nullable" keyword is supported by default.',jsonPointers:"Deprecated jsPropertySyntax can be used instead.",extendRefs:"Deprecated ignoreKeywordsWithRef can be used instead.",missingRefs:"Pass empty schema with $id that should be ignored to ajv.addSchema.",processCode:"Use option `code: {process: (code, schemaEnv: object) => string}`",sourceCode:"Use option `code: {source: true}`",strictDefaults:"It is default now, see option `strict`.",strictKeywords:"It is default now, see option `strict`.",uniqueItems:'"uniqueItems" keyword is always validated.',unknownFormats:"Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",cache:"Map is used as cache, schema object as key.",serialize:"Map is used as cache, schema object as key.",ajvErrors:"It is default now."},y={ignoreKeywordsWithRef:"",jsPropertySyntax:"",unicode:'"minLength"/"maxLength" account for unicode characters by default.'};function v(e){var t,n,r,o,i,a,s,l,c,u,p,d,f,h,m,g,y,v,b,w,x,k;const _=e.strict,O=null===(t=e.code)||void 0===t?void 0:t.optimize,S=!0===O||void 0===O?1:O||0;return{strictSchema:null===(r=null!==(n=e.strictSchema)&&void 0!==n?n:_)||void 0===r||r,strictNumbers:null===(i=null!==(o=e.strictNumbers)&&void 0!==o?o:_)||void 0===i||i,strictTypes:null!==(s=null!==(a=e.strictTypes)&&void 0!==a?a:_)&&void 0!==s?s:"log",strictTuples:null!==(c=null!==(l=e.strictTuples)&&void 0!==l?l:_)&&void 0!==c?c:"log",strictRequired:null!==(p=null!==(u=e.strictRequired)&&void 0!==u?u:_)&&void 0!==p&&p,code:e.code?{...e.code,optimize:S}:{optimize:S},loopRequired:null!==(d=e.loopRequired)&&void 0!==d?d:200,loopEnum:null!==(f=e.loopEnum)&&void 0!==f?f:200,meta:null===(h=e.meta)||void 0===h||h,messages:null===(m=e.messages)||void 0===m||m,inlineRefs:null===(g=e.inlineRefs)||void 0===g||g,schemaId:null!==(y=e.schemaId)&&void 0!==y?y:"$id",addUsedSchema:null===(v=e.addUsedSchema)||void 0===v||v,validateSchema:null===(b=e.validateSchema)||void 0===b||b,validateFormats:null===(w=e.validateFormats)||void 0===w||w,unicodeRegExp:null===(x=e.unicodeRegExp)||void 0===x||x,int32range:null===(k=e.int32range)||void 0===k||k}}class b{constructor(e={}){this.schemas={},this.refs={},this.formats={},this._compilations=new Set,this._loading={},this._cache=new Map,e=this.opts={...e,...v(e)};const{es5:t,lines:n}=this.opts.code;this.scope=new c.ValueScope({scope:{},prefixes:m,es5:t,lines:n}),this.logger=function(e){if(!1===e)return E;if(void 0===e)return console;if(e.log&&e.warn&&e.error)return e;throw new Error("logger must implement log, warn and error methods")}(e.logger);const r=e.validateFormats;e.validateFormats=!1,this.RULES=s.getRules(),w.call(this,g,e,"NOT SUPPORTED"),w.call(this,y,e,"DEPRECATED","warn"),this._metaOpts=S.call(this),e.formats&&_.call(this),this._addVocabularies(),this._addDefaultMetaSchema(),e.keywords&&O.call(this,e.keywords),"object"==typeof e.meta&&this.addMetaSchema(e.meta),k.call(this),e.validateFormats=r}_addVocabularies(){this.addKeyword("$async")}_addDefaultMetaSchema(){const{$data:e,meta:t,schemaId:n}=this.opts;let r=f;"id"===n&&(r={...f},r.id=r.$id,delete r.$id),t&&e&&this.addMetaSchema(r,r[n],!1)}defaultMeta(){const{meta:e,schemaId:t}=this.opts;return this.opts.defaultMeta="object"==typeof e?e[t]||e:void 0}validate(e,t){let n;if("string"==typeof e){if(n=this.getSchema(e),!n)throw new Error(`no schema with key or ref "${e}"`)}else n=this.compile(e);const r=n(t);return"$async"in n||(this.errors=n.errors),r}compile(e,t){const n=this._addSchema(e,t);return n.validate||this._compileSchemaEnv(n)}compileAsync(e,t){if("function"!=typeof this.opts.loadSchema)throw new Error("options.loadSchema should be a function");const{loadSchema:n}=this.opts;return r.call(this,e,t);async function r(e,t){await o.call(this,e.$schema);const n=this._addSchema(e,t);return n.validate||i.call(this,n)}async function o(e){e&&!this.getSchema(e)&&await r.call(this,{$ref:e},!0)}async function i(e){try{return this._compileSchemaEnv(e)}catch(t){if(!(t instanceof a.default))throw t;return s.call(this,t),await l.call(this,t.missingSchema),i.call(this,e)}}function s({missingSchema:e,missingRef:t}){if(this.refs[e])throw new Error(`AnySchema ${e} is loaded but ${t} cannot be resolved`)}async function l(e){const n=await c.call(this,e);this.refs[e]||await o.call(this,n.$schema),this.refs[e]||this.addSchema(n,e,t)}async function c(e){const t=this._loading[e];if(t)return t;try{return await(this._loading[e]=n(e))}finally{delete this._loading[e]}}}addSchema(e,t,n,r=this.opts.validateSchema){if(Array.isArray(e)){for(const t of e)this.addSchema(t,void 0,n,r);return this}let o;if("object"==typeof e){const{schemaId:t}=this.opts;if(o=e[t],void 0!==o&&"string"!=typeof o)throw new Error(`schema ${t} must be string`)}return t=u.normalizeId(t||o),this._checkUnique(t),this.schemas[t]=this._addSchema(e,n,t,r,!0),this}addMetaSchema(e,t,n=this.opts.validateSchema){return this.addSchema(e,t,!0,n),this}validateSchema(e,t){if("boolean"==typeof e)return!0;let n;if(n=e.$schema,void 0!==n&&"string"!=typeof n)throw new Error("$schema must be a string");if(n=n||this.opts.defaultMeta||this.defaultMeta(),!n)return this.logger.warn("meta-schema not available"),this.errors=null,!0;const r=this.validate(n,e);if(!r&&t){const e="schema is invalid: "+this.errorsText();if("log"!==this.opts.validateSchema)throw new Error(e);this.logger.error(e)}return r}getSchema(e){let t;for(;"string"==typeof(t=x.call(this,e));)e=t;if(void 0===t){const{schemaId:n}=this.opts,r=new l.SchemaEnv({schema:{},schemaId:n});if(t=l.resolveSchema.call(this,r,e),!t)return;this.refs[e]=t}return t.validate||this._compileSchemaEnv(t)}removeSchema(e){if(e instanceof RegExp)return this._removeAllSchemas(this.schemas,e),this._removeAllSchemas(this.refs,e),this;switch(typeof e){case"undefined":return this._removeAllSchemas(this.schemas),this._removeAllSchemas(this.refs),this._cache.clear(),this;case"string":{const t=x.call(this,e);return"object"==typeof t&&this._cache.delete(t.schema),delete this.schemas[e],delete this.refs[e],this}case"object":{const t=e;this._cache.delete(t);let n=e[this.opts.schemaId];return n&&(n=u.normalizeId(n),delete this.schemas[n],delete this.refs[n]),this}default:throw new Error("ajv.removeSchema: invalid parameter")}}addVocabulary(e){for(const t of e)this.addKeyword(t);return this}addKeyword(e,t){let n;if("string"==typeof e)n=e,"object"==typeof t&&(this.logger.warn("these parameters are deprecated, see docs for addKeyword"),t.keyword=n);else{if("object"!=typeof e||void 0!==t)throw new Error("invalid addKeywords parameters");if(n=(t=e).keyword,Array.isArray(n)&&!n.length)throw new Error("addKeywords: keyword must be string or non-empty array")}if(A.call(this,n,t),!t)return d.eachItem(n,(e=>$.call(this,e))),this;R.call(this,t);const r={...t,type:p.getJSONTypes(t.type),schemaType:p.getJSONTypes(t.schemaType)};return d.eachItem(n,0===r.type.length?e=>$.call(this,e,r):e=>r.type.forEach((t=>$.call(this,e,r,t)))),this}getKeyword(e){const t=this.RULES.all[e];return"object"==typeof t?t.definition:!!t}removeKeyword(e){const{RULES:t}=this;delete t.keywords[e],delete t.all[e];for(const n of t.rules){const t=n.rules.findIndex((t=>t.keyword===e));t>=0&&n.rules.splice(t,1)}return this}addFormat(e,t){return"string"==typeof t&&(t=new RegExp(t)),this.formats[e]=t,this}errorsText(e=this.errors,{separator:t=", ",dataVar:n="data"}={}){return e&&0!==e.length?e.map((e=>`${n}${e.instancePath} ${e.message}`)).reduce(((e,n)=>e+t+n)):"No errors"}$dataMetaSchema(e,t){const n=this.RULES.all;e=JSON.parse(JSON.stringify(e));for(const r of t){const t=r.split("/").slice(1);let o=e;for(const e of t)o=o[e];for(const e in n){const t=n[e];if("object"!=typeof t)continue;const{$data:r}=t.definition,i=o[e];r&&i&&(o[e]=T(i))}}return e}_removeAllSchemas(e,t){for(const n in e){const r=e[n];t&&!t.test(n)||("string"==typeof r?delete e[n]:r&&!r.meta&&(this._cache.delete(r.schema),delete e[n]))}}_addSchema(e,t,n,r=this.opts.validateSchema,o=this.opts.addUsedSchema){let i;const{schemaId:a}=this.opts;if("object"==typeof e)i=e[a];else{if(this.opts.jtd)throw new Error("schema must be object");if("boolean"!=typeof e)throw new Error("schema must be object or boolean")}let s=this._cache.get(e);if(void 0!==s)return s;const c=u.getSchemaRefs.call(this,e);return n=u.normalizeId(i||n),s=new l.SchemaEnv({schema:e,schemaId:a,meta:t,baseId:n,localRefs:c}),this._cache.set(s.schema,s),o&&!n.startsWith("#")&&(n&&this._checkUnique(n),this.refs[n]=s),r&&this.validateSchema(e,!0),s}_checkUnique(e){if(this.schemas[e]||this.refs[e])throw new Error(`schema with key or id "${e}" already exists`)}_compileSchemaEnv(e){if(e.meta?this._compileMetaSchema(e):l.compileSchema.call(this,e),!e.validate)throw new Error("ajv implementation error");return e.validate}_compileMetaSchema(e){const t=this.opts;this.opts=this._metaOpts;try{l.compileSchema.call(this,e)}finally{this.opts=t}}}function w(e,t,n,r="error"){for(const o in e){const i=o;i in t&&this.logger[r](`${n}: option ${o}. ${e[i]}`)}}function x(e){return e=u.normalizeId(e),this.schemas[e]||this.refs[e]}function k(){const e=this.opts.schemas;if(e)if(Array.isArray(e))this.addSchema(e);else for(const t in e)this.addSchema(e[t],t)}function _(){for(const e in this.opts.formats){const t=this.opts.formats[e];t&&this.addFormat(e,t)}}function O(e){if(Array.isArray(e))this.addVocabulary(e);else{this.logger.warn("keywords option as map is deprecated, pass array");for(const t in e){const n=e[t];n.keyword||(n.keyword=t),this.addKeyword(n)}}}function S(){const e={...this.opts};for(const t of h)delete e[t];return e}t.default=b,b.ValidationError=i.default,b.MissingRefError=a.default;const E={log(){},warn(){},error(){}},P=/^[a-z_$][a-z0-9_$:-]*$/i;function A(e,t){const{RULES:n}=this;if(d.eachItem(e,(e=>{if(n.keywords[e])throw new Error(`Keyword ${e} is already defined`);if(!P.test(e))throw new Error(`Keyword ${e} has invalid name`)})),t&&t.$data&&!("code"in t)&&!("validate"in t))throw new Error('$data keyword must have "code" or "validate" function')}function $(e,t,n){var r;const o=null==t?void 0:t.post;if(n&&o)throw new Error('keyword with "post" flag cannot have "type"');const{RULES:i}=this;let a=o?i.post:i.rules.find((({type:e})=>e===n));if(a||(a={type:n,rules:[]},i.rules.push(a)),i.keywords[e]=!0,!t)return;const s={keyword:e,definition:{...t,type:p.getJSONTypes(t.type),schemaType:p.getJSONTypes(t.schemaType)}};t.before?C.call(this,a,s,t.before):a.rules.push(s),i.all[e]=s,null===(r=t.implements)||void 0===r||r.forEach((e=>this.addKeyword(e)))}function C(e,t,n){const r=e.rules.findIndex((e=>e.keyword===n));r>=0?e.rules.splice(r,0,t):(e.rules.push(t),this.logger.warn(`rule ${n} is not defined`))}function R(e){let{metaSchema:t}=e;void 0!==t&&(e.$data&&this.opts.$data&&(t=T(t)),e.validateSchema=this.compile(t,!0))}const j={$ref:"https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#"};function T(e){return{anyOf:[e,j]}}},412:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4063);r.code='require("ajv/dist/runtime/equal").default',t.default=r},5872:function(e,t){"use strict";function n(e){const t=e.length;let n,r=0,o=0;for(;o=55296&&n<=56319&&or.str`must NOT have more than ${e} items`,params:({params:{len:e}})=>r._`{limit: ${e}}`},code(e){const{parentSchema:t,it:n}=e,{items:r}=t;Array.isArray(r)?a(e,r):o.checkStrictMode(n,'"additionalItems" is ignored when "items" is not an array of schemas')}};function a(e,t){const{gen:n,schema:i,data:a,keyword:s,it:l}=e;l.items=!0;const c=n.const("len",r._`${a}.length`);if(!1===i)e.setParams({len:t.length}),e.pass(r._`${c} <= ${t.length}`);else if("object"==typeof i&&!o.alwaysValidSchema(l,i)){const i=n.var("valid",r._`${c} <= ${t.length}`);n.if(r.not(i),(()=>function(i){n.forRange("i",t.length,c,(t=>{e.subschema({keyword:s,dataProp:t,dataPropType:o.Type.Num},i),l.allErrors||n.if(r.not(i),(()=>n.break()))}))}(i))),e.ok(i)}}t.validateAdditionalItems=a,t.default=i},1422:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i=n(5018),a=n(6124),s={keyword:"additionalProperties",type:["object"],schemaType:["boolean","object"],allowUndefined:!0,trackErrors:!0,error:{message:"must NOT have additional properties",params:({params:e})=>o._`{additionalProperty: ${e.additionalProperty}}`},code(e){const{gen:t,parentSchema:n,data:s,errsCount:l,it:c}=e,{schema:u=c.opts.defaultAdditionalProperties}=e;if(!l)throw new Error("ajv implementation error");const{allErrors:p,opts:d}=c;if(c.props=!0,"all"!==d.removeAdditional&&a.alwaysValidSchema(c,u))return;const f=r.allSchemaProperties(n.properties),h=r.allSchemaProperties(n.patternProperties);function m(e){t.code(o._`delete ${s}[${e}]`)}function g(n){if("all"===d.removeAdditional||d.removeAdditional&&!1===u)m(n);else{if(!1===u)return e.setParams({additionalProperty:n}),e.error(),void(p||t.break());if("object"==typeof u&&!a.alwaysValidSchema(c,u)){const r=t.name("valid");"failing"===d.removeAdditional?(y(n,r,!1),t.if(o.not(r),(()=>{e.reset(),m(n)}))):(y(n,r),p||t.if(o.not(r),(()=>t.break())))}}}function y(t,n,r){const o={keyword:"additionalProperties",dataProp:t,dataPropType:a.Type.Str};!1===r&&Object.assign(o,{compositeRule:!0,createErrors:!1,allErrors:!1}),e.subschema(o,n)}t.forIn("key",s,(i=>{f.length||h.length?t.if(function(i){let s;if(f.length>8){const e=a.schemaRefOrVal(c,n.properties,"properties");s=r.isOwnProperty(t,e,i)}else s=f.length?o.or(...f.map((e=>o._`${i} === ${e}`))):o.nil;return h.length&&(s=o.or(s,...h.map((t=>o._`${r.usePattern(e,t)}.test(${i})`)))),o.not(s)}(i),(()=>g(i))):g(i)})),e.ok(o._`${l} === ${i.default.errors}`)}};t.default=s},5716:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6124),o={keyword:"allOf",schemaType:"array",code(e){const{gen:t,schema:n,it:o}=e;if(!Array.isArray(n))throw new Error("ajv implementation error");const i=t.name("valid");n.forEach(((t,n)=>{if(r.alwaysValidSchema(o,t))return;const a=e.subschema({keyword:"allOf",schemaProp:n},i);e.ok(i),e.mergeEvaluated(a)}))}};t.default=o},1668:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r={keyword:"anyOf",schemaType:"array",trackErrors:!0,code:n(8619).validateUnion,error:{message:"must match a schema in anyOf"}};t.default=r},9564:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"contains",type:"array",schemaType:["object","boolean"],before:"uniqueItems",trackErrors:!0,error:{message:({params:{min:e,max:t}})=>void 0===t?r.str`must contain at least ${e} valid item(s)`:r.str`must contain at least ${e} and no more than ${t} valid item(s)`,params:({params:{min:e,max:t}})=>void 0===t?r._`{minContains: ${e}}`:r._`{minContains: ${e}, maxContains: ${t}}`},code(e){const{gen:t,schema:n,parentSchema:i,data:a,it:s}=e;let l,c;const{minContains:u,maxContains:p}=i;s.opts.next?(l=void 0===u?1:u,c=p):l=1;const d=t.const("len",r._`${a}.length`);if(e.setParams({min:l,max:c}),void 0===c&&0===l)return void o.checkStrictMode(s,'"minContains" == 0 without "maxContains": "contains" keyword ignored');if(void 0!==c&&l>c)return o.checkStrictMode(s,'"minContains" > "maxContains" is always invalid'),void e.fail();if(o.alwaysValidSchema(s,n)){let t=r._`${d} >= ${l}`;return void 0!==c&&(t=r._`${t} && ${d} <= ${c}`),void e.pass(t)}s.items=!0;const f=t.name("valid");if(void 0===c&&1===l)h(f,(()=>t.if(f,(()=>t.break()))));else{t.let(f,!1);const e=t.name("_valid"),n=t.let("count",0);h(e,(()=>t.if(e,(()=>function(e){t.code(r._`${e}++`),void 0===c?t.if(r._`${e} >= ${l}`,(()=>t.assign(f,!0).break())):(t.if(r._`${e} > ${c}`,(()=>t.assign(f,!1).break())),1===l?t.assign(f,!0):t.if(r._`${e} >= ${l}`,(()=>t.assign(f,!0))))}(n)))))}function h(n,r){t.forRange("i",0,d,(t=>{e.subschema({keyword:"contains",dataProp:t,dataPropType:o.Type.Num,compositeRule:!0},n),r()}))}e.result(f,(()=>e.reset()))}};t.default=i},1117:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateSchemaDeps=t.validatePropertyDeps=t.error=void 0;const r=n(4475),o=n(6124),i=n(8619);t.error={message:({params:{property:e,depsCount:t,deps:n}})=>{const o=1===t?"property":"properties";return r.str`must have ${o} ${n} when property ${e} is present`},params:({params:{property:e,depsCount:t,deps:n,missingProperty:o}})=>r._`{property: ${e}, - missingProperty: ${o}, - depsCount: ${t}, - deps: ${n}}`};const a={keyword:"dependencies",type:"object",schemaType:"object",error:t.error,code(e){const[t,n]=function({schema:e}){const t={},n={};for(const r in e)"__proto__"!==r&&((Array.isArray(e[r])?t:n)[r]=e[r]);return[t,n]}(e);s(e,t),l(e,n)}};function s(e,t=e.schema){const{gen:n,data:o,it:a}=e;if(0===Object.keys(t).length)return;const s=n.let("missing");for(const l in t){const c=t[l];if(0===c.length)continue;const u=i.propertyInData(n,o,l,a.opts.ownProperties);e.setParams({property:l,depsCount:c.length,deps:c.join(", ")}),a.allErrors?n.if(u,(()=>{for(const t of c)i.checkReportMissingProp(e,t)})):(n.if(r._`${u} && (${i.checkMissingProp(e,c,s)})`),i.reportMissingProp(e,s),n.else())}}function l(e,t=e.schema){const{gen:n,data:r,keyword:a,it:s}=e,l=n.name("valid");for(const c in t)o.alwaysValidSchema(s,t[c])||(n.if(i.propertyInData(n,r,c,s.opts.ownProperties),(()=>{const t=e.subschema({keyword:a,schemaProp:c},l);e.mergeValidEvaluated(t,l)}),(()=>n.var(l,!0))),e.ok(l))}t.validatePropertyDeps=s,t.validateSchemaDeps=l,t.default=a},5184:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"if",schemaType:["object","boolean"],trackErrors:!0,error:{message:({params:e})=>r.str`must match "${e.ifClause}" schema`,params:({params:e})=>r._`{failingKeyword: ${e.ifClause}}`},code(e){const{gen:t,parentSchema:n,it:i}=e;void 0===n.then&&void 0===n.else&&o.checkStrictMode(i,'"if" without "then" and "else" is ignored');const s=a(i,"then"),l=a(i,"else");if(!s&&!l)return;const c=t.let("valid",!0),u=t.name("_valid");if(function(){const t=e.subschema({keyword:"if",compositeRule:!0,createErrors:!1,allErrors:!1},u);e.mergeEvaluated(t)}(),e.reset(),s&&l){const n=t.let("ifClause");e.setParams({ifClause:n}),t.if(u,p("then",n),p("else",n))}else s?t.if(u,p("then")):t.if(r.not(u),p("else"));function p(n,o){return()=>{const i=e.subschema({keyword:n},u);t.assign(c,u),e.mergeValidEvaluated(i,c),o?t.assign(o,r._`${n}`):e.setParams({ifClause:n})}}e.pass(c,(()=>e.error(!0)))}};function a(e,t){const n=e.schema[t];return void 0!==n&&!o.alwaysValidSchema(e,n)}t.default=i},9616:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(3074),o=n(6988),i=n(6348),a=n(9822),s=n(9564),l=n(1117),c=n(4002),u=n(1422),p=n(9690),d=n(9883),f=n(8435),h=n(1668),m=n(9684),g=n(5716),y=n(5184),v=n(5642);t.default=function(e=!1){const t=[f.default,h.default,m.default,g.default,y.default,v.default,c.default,u.default,l.default,p.default,d.default];return e?t.push(o.default,a.default):t.push(r.default,i.default),t.push(s.default),t}},6348:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateTuple=void 0;const r=n(4475),o=n(6124),i=n(8619),a={keyword:"items",type:"array",schemaType:["object","array","boolean"],before:"uniqueItems",code(e){const{schema:t,it:n}=e;if(Array.isArray(t))return s(e,"additionalItems",t);n.items=!0,o.alwaysValidSchema(n,t)||e.ok(i.validateArray(e))}};function s(e,t,n=e.schema){const{gen:i,parentSchema:a,data:s,keyword:l,it:c}=e;!function(e){const{opts:r,errSchemaPath:i}=c,a=n.length,s=a===e.minItems&&(a===e.maxItems||!1===e[t]);if(r.strictTuples&&!s){const e=`"${l}" is ${a}-tuple, but minItems or maxItems/${t} are not specified or different at path "${i}"`;o.checkStrictMode(c,e,r.strictTuples)}}(a),c.opts.unevaluated&&n.length&&!0!==c.items&&(c.items=o.mergeEvaluated.items(i,n.length,c.items));const u=i.name("valid"),p=i.const("len",r._`${s}.length`);n.forEach(((t,n)=>{o.alwaysValidSchema(c,t)||(i.if(r._`${p} > ${n}`,(()=>e.subschema({keyword:l,schemaProp:n,dataProp:n},u))),e.ok(u))}))}t.validateTuple=s,t.default=a},9822:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(8619),a=n(3074),s={keyword:"items",type:"array",schemaType:["object","boolean"],before:"uniqueItems",error:{message:({params:{len:e}})=>r.str`must NOT have more than ${e} items`,params:({params:{len:e}})=>r._`{limit: ${e}}`},code(e){const{schema:t,parentSchema:n,it:r}=e,{prefixItems:s}=n;r.items=!0,o.alwaysValidSchema(r,t)||(s?a.validateAdditionalItems(e,s):e.ok(i.validateArray(e)))}};t.default=s},8435:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6124),o={keyword:"not",schemaType:["object","boolean"],trackErrors:!0,code(e){const{gen:t,schema:n,it:o}=e;if(r.alwaysValidSchema(o,n))return void e.fail();const i=t.name("valid");e.subschema({keyword:"not",compositeRule:!0,createErrors:!1,allErrors:!1},i),e.result(i,(()=>e.error()),(()=>e.reset()))},error:{message:"must NOT be valid"}};t.default=o},9684:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"oneOf",schemaType:"array",trackErrors:!0,error:{message:"must match exactly one schema in oneOf",params:({params:e})=>r._`{passingSchemas: ${e.passing}}`},code(e){const{gen:t,schema:n,parentSchema:i,it:a}=e;if(!Array.isArray(n))throw new Error("ajv implementation error");if(a.opts.discriminator&&i.discriminator)return;const s=n,l=t.let("valid",!1),c=t.let("passing",null),u=t.name("_valid");e.setParams({passing:c}),t.block((function(){s.forEach(((n,i)=>{let s;o.alwaysValidSchema(a,n)?t.var(u,!0):s=e.subschema({keyword:"oneOf",schemaProp:i,compositeRule:!0},u),i>0&&t.if(r._`${u} && ${l}`).assign(l,!1).assign(c,r._`[${c}, ${i}]`).else(),t.if(u,(()=>{t.assign(l,!0),t.assign(c,i),s&&e.mergeEvaluated(s,r.Name)}))}))})),e.result(l,(()=>e.reset()),(()=>e.error(!0)))}};t.default=i},9883:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i=n(6124),a=n(6124),s={keyword:"patternProperties",type:"object",schemaType:"object",code(e){const{gen:t,schema:n,data:s,parentSchema:l,it:c}=e,{opts:u}=c,p=r.allSchemaProperties(n),d=p.filter((e=>i.alwaysValidSchema(c,n[e])));if(0===p.length||d.length===p.length&&(!c.opts.unevaluated||!0===c.props))return;const f=u.strictSchema&&!u.allowMatchingProperties&&l.properties,h=t.name("valid");!0===c.props||c.props instanceof o.Name||(c.props=a.evaluatedPropsToName(t,c.props));const{props:m}=c;function g(e){for(const t in f)new RegExp(e).test(t)&&i.checkStrictMode(c,`property ${t} matches pattern ${e} (use allowMatchingProperties)`)}function y(n){t.forIn("key",s,(i=>{t.if(o._`${r.usePattern(e,n)}.test(${i})`,(()=>{const r=d.includes(n);r||e.subschema({keyword:"patternProperties",schemaProp:n,dataProp:i,dataPropType:a.Type.Str},h),c.opts.unevaluated&&!0!==m?t.assign(o._`${m}[${i}]`,!0):r||c.allErrors||t.if(o.not(h),(()=>t.break()))}))}))}!function(){for(const e of p)f&&g(e),c.allErrors?y(e):(t.var(h,!0),y(e),t.if(h))}()}};t.default=s},6988:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6348),o={keyword:"prefixItems",type:"array",schemaType:["array"],before:"uniqueItems",code:e=>r.validateTuple(e,"items")};t.default=o},9690:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(1321),o=n(8619),i=n(6124),a=n(1422),s={keyword:"properties",type:"object",schemaType:"object",code(e){const{gen:t,schema:n,parentSchema:s,data:l,it:c}=e;("all"===c.opts.removeAdditional&&void 0===s.additionalProperties||!1===c.opts.defaultAdditionalProperties)&&a.default.code(new r.KeywordCxt(c,a.default,"additionalProperties"));const u=o.allSchemaProperties(n);for(const e of u)c.definedProperties.add(e);c.opts.unevaluated&&u.length&&!0!==c.props&&(c.props=i.mergeEvaluated.props(t,i.toHash(u),c.props));const p=u.filter((e=>!i.alwaysValidSchema(c,n[e])));if(0===p.length)return;const d=t.name("valid");for(const n of p)f(n)?h(n):(t.if(o.propertyInData(t,l,n,c.opts.ownProperties)),h(n),c.allErrors||t.else().var(d,!0),t.endIf()),e.it.definedProperties.add(n),e.ok(d);function f(e){return c.opts.useDefaults&&!c.compositeRule&&void 0!==n[e].default}function h(t){e.subschema({keyword:"properties",schemaProp:t,dataProp:t},d)}}};t.default=s},4002:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"propertyNames",type:"object",schemaType:["object","boolean"],error:{message:"property name must be valid",params:({params:e})=>r._`{propertyName: ${e.propertyName}}`},code(e){const{gen:t,schema:n,data:i,it:a}=e;if(o.alwaysValidSchema(a,n))return;const s=t.name("valid");t.forIn("key",i,(n=>{e.setParams({propertyName:n}),e.subschema({keyword:"propertyNames",data:n,dataTypes:["string"],propertyName:n,compositeRule:!0},s),t.if(r.not(s),(()=>{e.error(!0),a.allErrors||t.break()}))})),e.ok(s)}};t.default=i},5642:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6124),o={keyword:["then","else"],schemaType:["object","boolean"],code({keyword:e,parentSchema:t,it:n}){void 0===t.if&&r.checkStrictMode(n,`"${e}" without "if" is ignored`)}};t.default=o},8619:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateUnion=t.validateArray=t.usePattern=t.callValidateCode=t.schemaProperties=t.allSchemaProperties=t.noPropertyInData=t.propertyInData=t.isOwnProperty=t.hasPropFunc=t.reportMissingProp=t.checkMissingProp=t.checkReportMissingProp=void 0;const r=n(4475),o=n(6124),i=n(5018);function a(e){return e.scopeValue("func",{ref:Object.prototype.hasOwnProperty,code:r._`Object.prototype.hasOwnProperty`})}function s(e,t,n){return r._`${a(e)}.call(${t}, ${n})`}function l(e,t,n,o){const i=r._`${t}${r.getProperty(n)} === undefined`;return o?r.or(i,r.not(s(e,t,n))):i}function c(e){return e?Object.keys(e).filter((e=>"__proto__"!==e)):[]}t.checkReportMissingProp=function(e,t){const{gen:n,data:o,it:i}=e;n.if(l(n,o,t,i.opts.ownProperties),(()=>{e.setParams({missingProperty:r._`${t}`},!0),e.error()}))},t.checkMissingProp=function({gen:e,data:t,it:{opts:n}},o,i){return r.or(...o.map((o=>r.and(l(e,t,o,n.ownProperties),r._`${i} = ${o}`))))},t.reportMissingProp=function(e,t){e.setParams({missingProperty:t},!0),e.error()},t.hasPropFunc=a,t.isOwnProperty=s,t.propertyInData=function(e,t,n,o){const i=r._`${t}${r.getProperty(n)} !== undefined`;return o?r._`${i} && ${s(e,t,n)}`:i},t.noPropertyInData=l,t.allSchemaProperties=c,t.schemaProperties=function(e,t){return c(t).filter((n=>!o.alwaysValidSchema(e,t[n])))},t.callValidateCode=function({schemaCode:e,data:t,it:{gen:n,topSchemaRef:o,schemaPath:a,errorPath:s},it:l},c,u,p){const d=p?r._`${e}, ${t}, ${o}${a}`:t,f=[[i.default.instancePath,r.strConcat(i.default.instancePath,s)],[i.default.parentData,l.parentData],[i.default.parentDataProperty,l.parentDataProperty],[i.default.rootData,i.default.rootData]];l.opts.dynamicRef&&f.push([i.default.dynamicAnchors,i.default.dynamicAnchors]);const h=r._`${d}, ${n.object(...f)}`;return u!==r.nil?r._`${c}.call(${u}, ${h})`:r._`${c}(${h})`},t.usePattern=function({gen:e,it:{opts:t}},n){const o=t.unicodeRegExp?"u":"";return e.scopeValue("pattern",{key:n,ref:new RegExp(n,o),code:r._`new RegExp(${n}, ${o})`})},t.validateArray=function(e){const{gen:t,data:n,keyword:i,it:a}=e,s=t.name("valid");if(a.allErrors){const e=t.let("valid",!0);return l((()=>t.assign(e,!1))),e}return t.var(s,!0),l((()=>t.break())),s;function l(a){const l=t.const("len",r._`${n}.length`);t.forRange("i",0,l,(n=>{e.subschema({keyword:i,dataProp:n,dataPropType:o.Type.Num},s),t.if(r.not(s),a)}))}},t.validateUnion=function(e){const{gen:t,schema:n,keyword:i,it:a}=e;if(!Array.isArray(n))throw new Error("ajv implementation error");if(n.some((e=>o.alwaysValidSchema(a,e)))&&!a.opts.unevaluated)return;const s=t.let("valid",!1),l=t.name("_valid");t.block((()=>n.forEach(((n,o)=>{const a=e.subschema({keyword:i,schemaProp:o,compositeRule:!0},l);t.assign(s,r._`${s} || ${l}`),e.mergeValidEvaluated(a,l)||t.if(r.not(s))})))),e.result(s,(()=>e.reset()),(()=>e.error(!0)))}},5060:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n={keyword:"id",code(){throw new Error('NOT SUPPORTED: keyword "id", use "$id" for schema ID')}};t.default=n},8223:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(5060),o=n(4028),i=["$schema","$id","$defs","$vocabulary",{keyword:"$comment"},"definitions",r.default,o.default];t.default=i},4028:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.callRef=t.getValidate=void 0;const r=n(4143),o=n(8619),i=n(4475),a=n(5018),s=n(7805),l=n(6124),c={keyword:"$ref",schemaType:"string",code(e){const{gen:t,schema:n,it:o}=e,{baseId:a,schemaEnv:l,validateName:c,opts:d,self:f}=o,{root:h}=l;if(("#"===n||"#/"===n)&&a===h.baseId)return function(){if(l===h)return p(e,c,l,l.$async);const n=t.scopeValue("root",{ref:h});return p(e,i._`${n}.validate`,h,h.$async)}();const m=s.resolveRef.call(f,h,a,n);if(void 0===m)throw new r.default(a,n);return m instanceof s.SchemaEnv?function(t){const n=u(e,t);p(e,n,t,t.$async)}(m):function(r){const o=t.scopeValue("schema",!0===d.code.source?{ref:r,code:i.stringify(r)}:{ref:r}),a=t.name("valid"),s=e.subschema({schema:r,dataTypes:[],schemaPath:i.nil,topSchemaRef:o,errSchemaPath:n},a);e.mergeEvaluated(s),e.ok(a)}(m)}};function u(e,t){const{gen:n}=e;return t.validate?n.scopeValue("validate",{ref:t.validate}):i._`${n.scopeValue("wrapper",{ref:t})}.validate`}function p(e,t,n,r){const{gen:s,it:c}=e,{allErrors:u,schemaEnv:p,opts:d}=c,f=d.passContext?a.default.this:i.nil;function h(e){const t=i._`${e}.errors`;s.assign(a.default.vErrors,i._`${a.default.vErrors} === null ? ${t} : ${a.default.vErrors}.concat(${t})`),s.assign(a.default.errors,i._`${a.default.vErrors}.length`)}function m(e){var t;if(!c.opts.unevaluated)return;const r=null===(t=null==n?void 0:n.validate)||void 0===t?void 0:t.evaluated;if(!0!==c.props)if(r&&!r.dynamicProps)void 0!==r.props&&(c.props=l.mergeEvaluated.props(s,r.props,c.props));else{const t=s.var("props",i._`${e}.evaluated.props`);c.props=l.mergeEvaluated.props(s,t,c.props,i.Name)}if(!0!==c.items)if(r&&!r.dynamicItems)void 0!==r.items&&(c.items=l.mergeEvaluated.items(s,r.items,c.items));else{const t=s.var("items",i._`${e}.evaluated.items`);c.items=l.mergeEvaluated.items(s,t,c.items,i.Name)}}r?function(){if(!p.$async)throw new Error("async schema referenced by sync schema");const n=s.let("valid");s.try((()=>{s.code(i._`await ${o.callValidateCode(e,t,f)}`),m(t),u||s.assign(n,!0)}),(e=>{s.if(i._`!(${e} instanceof ${c.ValidationError})`,(()=>s.throw(e))),h(e),u||s.assign(n,!1)})),e.ok(n)}():function(){const n=s.name("visitedNodes");s.code(i._`const ${n} = visitedNodesForRef.get(${t}) || new Set()`),s.if(i._`!${n}.has(${e.data})`,(()=>{s.code(i._`visitedNodesForRef.set(${t}, ${n})`),s.code(i._`const dataNode = ${e.data}`),s.code(i._`${n}.add(dataNode)`);const r=e.result(o.callValidateCode(e,t,f),(()=>m(t)),(()=>h(t)));return s.code(i._`${n}.delete(dataNode)`),r}))}()}t.getValidate=u,t.callRef=p,t.default=c},5522:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6545),i={keyword:"discriminator",type:"object",schemaType:"object",error:{message:({params:{discrError:e,tagName:t}})=>e===o.DiscrError.Tag?`tag "${t}" must be string`:`value of tag "${t}" must be in oneOf`,params:({params:{discrError:e,tag:t,tagName:n}})=>r._`{error: ${e}, tag: ${n}, tagValue: ${t}}`},code(e){const{gen:t,data:n,schema:i,parentSchema:a,it:s}=e,{oneOf:l}=a;if(!s.opts.discriminator)throw new Error("discriminator: requires discriminator option");const c=i.propertyName;if("string"!=typeof c)throw new Error("discriminator: requires propertyName");if(!l)throw new Error("discriminator: requires oneOf keyword");const u=t.let("valid",!1),p=t.const("tag",r._`${n}${r.getProperty(c)}`);function d(n){const o=t.name("valid"),i=e.subschema({keyword:"oneOf",schemaProp:n},o);return e.mergeEvaluated(i,r.Name),o}function f(e){return e.hasOwnProperty("$ref")}t.if(r._`typeof ${p} == "string"`,(()=>function(){const n=function(){var e;const t={},n=o(a);let r=!0;for(let t=0;te.error(!1,{discrError:o.DiscrError.Tag,tag:p,tagName:c}))),e.ok(u)}};t.default=i},6545:function(e,t){"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.DiscrError=void 0,(n=t.DiscrError||(t.DiscrError={})).Tag="tag",n.Mapping="mapping"},6479:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8223),o=n(3799),i=n(9616),a=n(3815),s=n(4826),l=[r.default,o.default,i.default(),a.default,s.metadataVocabulary,s.contentVocabulary];t.default=l},157:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:"format",type:["number","string"],schemaType:"string",$data:!0,error:{message:({schemaCode:e})=>r.str`must match format "${e}"`,params:({schemaCode:e})=>r._`{format: ${e}}`},code(e,t){const{gen:n,data:o,$data:i,schema:a,schemaCode:s,it:l}=e,{opts:c,errSchemaPath:u,schemaEnv:p,self:d}=l;c.validateFormats&&(i?function(){const i=n.scopeValue("formats",{ref:d.formats,code:c.code.formats}),a=n.const("fDef",r._`${i}[${s}]`),l=n.let("fType"),u=n.let("format");n.if(r._`typeof ${a} == "object" && !(${a} instanceof RegExp)`,(()=>n.assign(l,r._`${a}.type || "string"`).assign(u,r._`${a}.validate`)),(()=>n.assign(l,r._`"string"`).assign(u,a))),e.fail$data(r.or(!1===c.strictSchema?r.nil:r._`${s} && !${u}`,function(){const e=p.$async?r._`(${a}.async ? await ${u}(${o}) : ${u}(${o}))`:r._`${u}(${o})`,n=r._`(typeof ${u} == "function" ? ${e} : ${u}.test(${o}))`;return r._`${u} && ${u} !== true && ${l} === ${t} && !${n}`}()))}():function(){const i=d.formats[a];if(!i)return void function(){if(!1!==c.strictSchema)throw new Error(e());function e(){return`unknown format "${a}" ignored in schema at path "${u}"`}d.logger.warn(e())}();if(!0===i)return;const[s,l,f]=function(e){const t=e instanceof RegExp?r.regexpCode(e):c.code.formats?r._`${c.code.formats}${r.getProperty(a)}`:void 0,o=n.scopeValue("formats",{key:a,ref:e,code:t});return"object"!=typeof e||e instanceof RegExp?["string",e,o]:[e.type||"string",e.validate,r._`${o}.validate`]}(i);s===t&&e.pass(function(){if("object"==typeof i&&!(i instanceof RegExp)&&i.async){if(!p.$async)throw new Error("async format in sync schema");return r._`await ${f}(${o})`}return"function"==typeof l?r._`${f}(${o})`:r._`${f}.test(${o})`}())}())}};t.default=o},3815:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=[n(157).default];t.default=r},4826:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.contentVocabulary=t.metadataVocabulary=void 0,t.metadataVocabulary=["title","description","default","deprecated","readOnly","writeOnly","examples"],t.contentVocabulary=["contentMediaType","contentEncoding","contentSchema"]},7535:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(412),a={keyword:"const",$data:!0,error:{message:"must be equal to constant",params:({schemaCode:e})=>r._`{allowedValue: ${e}}`},code(e){const{gen:t,data:n,$data:a,schemaCode:s,schema:l}=e;a||l&&"object"==typeof l?e.fail$data(r._`!${o.useFunc(t,i.default)}(${n}, ${s})`):e.fail(r._`${l} !== ${n}`)}};t.default=a},4147:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(412),a={keyword:"enum",schemaType:"array",$data:!0,error:{message:"must be equal to one of the allowed values",params:({schemaCode:e})=>r._`{allowedValues: ${e}}`},code(e){const{gen:t,data:n,$data:a,schema:s,schemaCode:l,it:c}=e;if(!a&&0===s.length)throw new Error("enum must have non-empty array");const u=s.length>=c.opts.loopEnum,p=o.useFunc(t,i.default);let d;if(u||a)d=t.let("valid"),e.block$data(d,(function(){t.assign(d,!1),t.forOf("v",l,(e=>t.if(r._`${p}(${n}, ${e})`,(()=>t.assign(d,!0).break()))))}));else{if(!Array.isArray(s))throw new Error("ajv implementation error");const e=t.const("vSchema",l);d=r.or(...s.map(((t,o)=>function(e,t){const o=s[t];return"object"==typeof o&&null!==o?r._`${p}(${n}, ${e}[${t}])`:r._`${n} === ${o}`}(e,o))))}e.pass(d)}};t.default=a},3799:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(9640),o=n(7692),i=n(3765),a=n(8582),s=n(6711),l=n(7835),c=n(8950),u=n(7326),p=n(7535),d=n(4147),f=[r.default,o.default,i.default,a.default,s.default,l.default,c.default,u.default,{keyword:"type",schemaType:["string","array"]},{keyword:"nullable",schemaType:"boolean"},p.default,d.default];t.default=f},8950:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:["maxItems","minItems"],type:"array",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const n="maxItems"===e?"more":"fewer";return r.str`must NOT have ${n} than ${t} items`},params:({schemaCode:e})=>r._`{limit: ${e}}`},code(e){const{keyword:t,data:n,schemaCode:o}=e,i="maxItems"===t?r.operators.GT:r.operators.LT;e.fail$data(r._`${n}.length ${i} ${o}`)}};t.default=o},3765:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(5872),a={keyword:["maxLength","minLength"],type:"string",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const n="maxLength"===e?"more":"fewer";return r.str`must NOT have ${n} than ${t} characters`},params:({schemaCode:e})=>r._`{limit: ${e}}`},code(e){const{keyword:t,data:n,schemaCode:a,it:s}=e,l="maxLength"===t?r.operators.GT:r.operators.LT,c=!1===s.opts.unicode?r._`${n}.length`:r._`${o.useFunc(e.gen,i.default)}(${n})`;e.fail$data(r._`${c} ${l} ${a}`)}};t.default=a},9640:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=r.operators,i={maximum:{okStr:"<=",ok:o.LTE,fail:o.GT},minimum:{okStr:">=",ok:o.GTE,fail:o.LT},exclusiveMaximum:{okStr:"<",ok:o.LT,fail:o.GTE},exclusiveMinimum:{okStr:">",ok:o.GT,fail:o.LTE}},a={message:({keyword:e,schemaCode:t})=>r.str`must be ${i[e].okStr} ${t}`,params:({keyword:e,schemaCode:t})=>r._`{comparison: ${i[e].okStr}, limit: ${t}}`},s={keyword:Object.keys(i),type:"number",schemaType:"number",$data:!0,error:a,code(e){const{keyword:t,data:n,schemaCode:o}=e;e.fail$data(r._`${n} ${i[t].fail} ${o} || isNaN(${n})`)}};t.default=s},6711:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:["maxProperties","minProperties"],type:"object",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const n="maxProperties"===e?"more":"fewer";return r.str`must NOT have ${n} than ${t} items`},params:({schemaCode:e})=>r._`{limit: ${e}}`},code(e){const{keyword:t,data:n,schemaCode:o}=e,i="maxProperties"===t?r.operators.GT:r.operators.LT;e.fail$data(r._`Object.keys(${n}).length ${i} ${o}`)}};t.default=o},7692:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:"multipleOf",type:"number",schemaType:"number",$data:!0,error:{message:({schemaCode:e})=>r.str`must be multiple of ${e}`,params:({schemaCode:e})=>r._`{multipleOf: ${e}}`},code(e){const{gen:t,data:n,schemaCode:o,it:i}=e,a=i.opts.multipleOfPrecision,s=t.let("res"),l=a?r._`Math.abs(Math.round(${s}) - ${s}) > 1e-${a}`:r._`${s} !== parseInt(${s})`;e.fail$data(r._`(${o} === 0 || (${s} = ${n}/${o}, ${l}))`)}};t.default=o},8582:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i={keyword:"pattern",type:"string",schemaType:"string",$data:!0,error:{message:({schemaCode:e})=>o.str`must match pattern "${e}"`,params:({schemaCode:e})=>o._`{pattern: ${e}}`},code(e){const{data:t,$data:n,schema:i,schemaCode:a,it:s}=e,l=s.opts.unicodeRegExp?"u":"",c=n?o._`(new RegExp(${a}, ${l}))`:r.usePattern(e,i);e.fail$data(o._`!${c}.test(${t})`)}};t.default=i},7835:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i=n(6124),a={keyword:"required",type:"object",schemaType:"array",$data:!0,error:{message:({params:{missingProperty:e}})=>o.str`must have required property '${e}'`,params:({params:{missingProperty:e}})=>o._`{missingProperty: ${e}}`},code(e){const{gen:t,schema:n,schemaCode:a,data:s,$data:l,it:c}=e,{opts:u}=c;if(!l&&0===n.length)return;const p=n.length>=u.loopRequired;if(c.allErrors?function(){if(p||l)e.block$data(o.nil,d);else for(const t of n)r.checkReportMissingProp(e,t)}():function(){const i=t.let("missing");if(p||l){const n=t.let("valid",!0);e.block$data(n,(()=>function(n,i){e.setParams({missingProperty:n}),t.forOf(n,a,(()=>{t.assign(i,r.propertyInData(t,s,n,u.ownProperties)),t.if(o.not(i),(()=>{e.error(),t.break()}))}),o.nil)}(i,n))),e.ok(n)}else t.if(r.checkMissingProp(e,n,i)),r.reportMissingProp(e,i),t.else()}(),u.strictRequired){const t=e.parentSchema.properties,{definedProperties:r}=e.it;for(const e of n)if(void 0===(null==t?void 0:t[e])&&!r.has(e)){const t=`required property "${e}" is not defined at "${c.schemaEnv.baseId+c.errSchemaPath}" (strictRequired)`;i.checkStrictMode(c,t,c.opts.strictRequired)}}function d(){t.forOf("prop",a,(n=>{e.setParams({missingProperty:n}),t.if(r.noPropertyInData(t,s,n,u.ownProperties),(()=>e.error()))}))}}};t.default=a},7326:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7927),o=n(4475),i=n(6124),a=n(412),s={keyword:"uniqueItems",type:"array",schemaType:"boolean",$data:!0,error:{message:({params:{i:e,j:t}})=>o.str`must NOT have duplicate items (items ## ${t} and ${e} are identical)`,params:({params:{i:e,j:t}})=>o._`{i: ${e}, j: ${t}}`},code(e){const{gen:t,data:n,$data:s,schema:l,parentSchema:c,schemaCode:u,it:p}=e;if(!s&&!l)return;const d=t.let("valid"),f=c.items?r.getSchemaTypes(c.items):[];function h(i,a){const s=t.name("item"),l=r.checkDataTypes(f,s,p.opts.strictNumbers,r.DataType.Wrong),c=t.const("indices",o._`{}`);t.for(o._`;${i}--;`,(()=>{t.let(s,o._`${n}[${i}]`),t.if(l,o._`continue`),f.length>1&&t.if(o._`typeof ${s} == "string"`,o._`${s} += "_"`),t.if(o._`typeof ${c}[${s}] == "number"`,(()=>{t.assign(a,o._`${c}[${s}]`),e.error(),t.assign(d,!1).break()})).code(o._`${c}[${s}] = ${i}`)}))}function m(r,s){const l=i.useFunc(t,a.default),c=t.name("outer");t.label(c).for(o._`;${r}--;`,(()=>t.for(o._`${s} = ${r}; ${s}--;`,(()=>t.if(o._`${l}(${n}[${r}], ${n}[${s}])`,(()=>{e.error(),t.assign(d,!1).break(c)}))))))}e.block$data(d,(function(){const r=t.let("i",o._`${n}.length`),i=t.let("j");e.setParams({i:r,j:i}),t.assign(d,!0),t.if(o._`${r} > 1`,(()=>(f.length>0&&!f.some((e=>"object"===e||"array"===e))?h:m)(r,i)))}),o._`${u} === false`),e.ok(d)}};t.default=s},4029:function(e){"use strict";var t=e.exports=function(e,t,r){"function"==typeof t&&(r=t,t={}),n(t,"function"==typeof(r=t.cb||r)?r:r.pre||function(){},r.post||function(){},e,"",e)};function n(e,r,o,i,a,s,l,c,u,p){if(i&&"object"==typeof i&&!Array.isArray(i)){for(var d in r(i,a,s,l,c,u,p),i){var f=i[d];if(Array.isArray(f)){if(d in t.arrayKeywords)for(var h=0;hn.addProblemToIgnore(e))),fileDependencies:o.getFiles(),rootType:S.DefinitionRoot,refTypes:A.refTypes,visitorsData:A.visitorsData}}))}function k(e,t){switch(t){case d.OasMajorVersion.Version3:switch(e){case"Schema":return"schemas";case"Parameter":return"parameters";case"Response":return"responses";case"Example":return"examples";case"RequestBody":return"requestBodies";case"Header":return"headers";case"SecuritySchema":return"securitySchemes";case"Link":return"links";case"Callback":return"callbacks";default:return null}case d.OasMajorVersion.Version2:switch(e){case"Schema":return"definitions";case"Parameter":return"parameters";case"Response":return"responses";default:return null}}}function _(e,t,n,r,a,s){let l;const c={ref:{leave(o,l,c){if(!c.location||void 0===c.node)return void m.reportUnresolvedRef(c,l.report,l.location);if(c.location.source===r.source&&c.location.source===l.location.source&&"scalar"!==l.type.name&&!t)return;if(n&&y.isRedoclyRegistryURL(o.$ref))return;if(s&&f.isAbsoluteUrl(o.$ref))return;const d=k(l.type.name,e);d?t?(p(d,c,l),u(o,c,l)):(o.$ref=p(d,c,l),function(e,t,n){const o=i.makeRefId(n.location.source.absoluteRef,e.$ref);a.set(o,{document:r,isRemote:!1,node:t.node,nodePointer:e.$ref,resolved:!0})}(o,c,l)):u(o,c,l)}},DefinitionRoot:{enter(t){e===d.OasMajorVersion.Version3?l=t.components=t.components||{}:e===d.OasMajorVersion.Version2&&(l=t)}}};function u(e,t,n){g.isPlainObject(t.node)?(delete e.$ref,Object.assign(e,t.node)):n.parent[n.key]=t.node}function p(t,n,r){l[t]=l[t]||{};const o=function(e,t,n){const[r,o]=[e.location.source.absoluteRef,e.location.pointer],i=l[t];let a="";const s=o.slice(2).split("/").filter(Boolean);for(;s.length>0;)if(a=s.pop()+(a?`-${a}`:""),!i||!i[a]||h(i[a],e,n))return a;if(a=f.refBaseName(r)+(a?`_${a}`:""),!i[a]||h(i[a],e,n))return a;const c=a;let u=2;for(;i[a]&&!h(i[a],e,n);)a=`${c}-${u}`,u++;return i[a]||n.report({message:`Two schemas are referenced with the same name but different content. Renamed ${c} to ${a}.`,location:n.location,forceSeverity:"warn"}),a}(n,t,r);return l[t][o]=n.node,e===d.OasMajorVersion.Version3?`#/components/${t}/${o}`:`#/${t}/${o}`}function h(e,t,n){var r;return!(!f.isRef(e)||(null===(r=n.resolve(e).location)||void 0===r?void 0:r.absolutePointer)!==t.location.absolutePointer)||o(e,t.node)}return e===d.OasMajorVersion.Version3&&(c.DiscriminatorMapping={leave(n,r){for(const o of Object.keys(n)){const i=n[o],a=r.resolve({$ref:i});if(!a.location||void 0===a.node)return void m.reportUnresolvedRef(a,r.report,r.location.child(o));const s=k("Schema",e);t?p(s,a,r):n[o]=p(s,a,r)}}}),c}!function(e){e.Version2="oas2",e.Version3_0="oas3_0",e.Version3_1="oas3_1"}(w=t.OasVersion||(t.OasVersion={})),t.bundle=function(e){return r(this,void 0,void 0,(function*(){const{ref:t,doc:n,externalRefResolver:r=new i.BaseResolver(e.config.resolve),base:o=null}=e;if(!t&&!n)throw new Error("Document or reference is required.\n");const a=void 0!==n?n:yield r.resolveDocument(o,t,!0);if(a instanceof Error)throw a;return x(Object.assign(Object.assign({document:a},e),{config:e.config.lint,externalRefResolver:r}))}))},t.bundleDocument=x,t.mapTypeToComponent=k},6877:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={rules:{"info-description":"error","info-contact":"error","info-license":"error","info-license-url":"error","tag-description":"error","tags-alphabetical":"error","parameter-description":"error","no-identical-paths":"error","no-ambiguous-paths":"error","no-path-trailing-slash":"error","path-segment-plural":"error","path-declaration-must-exist":"error","path-not-include-query":"error","path-parameters-defined":"error","operation-description":"error","operation-2xx-response":"error","operation-4xx-response":"error",assertions:"error","operation-operationId":"error","operation-summary":"error","operation-operationId-unique":"error","operation-operationId-url-safe":"error","operation-parameters-unique":"error","operation-tag-defined":"error","operation-security-defined":"error","operation-singular-tag":"error","no-unresolved-refs":"error","no-enum-type-mismatch":"error","boolean-parameter-prefixes":"error","paths-kebab-case":"error","no-http-verbs-in-paths":"error","path-excludes-patterns":{severity:"error",patterns:[]},"request-mime-type":"error",spec:"error","no-invalid-schema-examples":"error","no-invalid-parameter-examples":"error","scalar-property-missing-example":"error"},oas3_0Rules:{"no-invalid-media-type-examples":"error","no-server-example.com":"error","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"error","no-undefined-server-variable":"error","no-servers-empty-enum":"error"},oas3_1Rules:{"no-server-example.com":"error","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"error","no-undefined-server-variable":"error","no-servers-empty-enum":"error"}}},6242:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.defaultPlugin=t.builtInConfigs=void 0;const r=n(8057),o=n(6877),i=n(9016),a=n(226),s=n(7523),l=n(226),c=n(7523),u=n(1753),p=n(7060);t.builtInConfigs={recommended:r.default,minimal:i.default,all:o.default,"redocly-registry":{decorators:{"registry-dependencies":"on"}}},t.defaultPlugin={id:"",rules:{oas3:a.rules,oas2:s.rules},preprocessors:{oas3:l.preprocessors,oas2:c.preprocessors},decorators:{oas3:u.decorators,oas2:p.decorators},configs:t.builtInConfigs}},7040:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))},o=this&&this.__rest||function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o{if(p.isString(e)&&s.isAbsoluteUrl(e))throw new Error(a.red("We don't support remote plugins yet."));const o=p.isString(e)?n(i.resolve(i.dirname(t),e)):e,l=o.id;if("string"!=typeof l)throw new Error(a.red(`Plugin must define \`id\` property in ${a.blue(e.toString())}.`));if(r.has(l)){const t=r.get(l);throw new Error(a.red(`Plugin "id" must be unique. Plugin ${a.blue(e.toString())} uses id "${a.blue(l)}" already seen in ${a.blue(t)}`))}r.set(l,e.toString());const c=Object.assign(Object.assign({id:l},o.configs?{configs:o.configs}:{}),o.typeExtension?{typeExtension:o.typeExtension}:{});if(o.rules){if(!o.rules.oas3&&!o.rules.oas2)throw new Error(`Plugin rules must have \`oas3\` or \`oas2\` rules "${e}.`);c.rules={},o.rules.oas3&&(c.rules.oas3=u.prefixRules(o.rules.oas3,l)),o.rules.oas2&&(c.rules.oas2=u.prefixRules(o.rules.oas2,l))}if(o.preprocessors){if(!o.preprocessors.oas3&&!o.preprocessors.oas2)throw new Error(`Plugin \`preprocessors\` must have \`oas3\` or \`oas2\` preprocessors "${e}.`);c.preprocessors={},o.preprocessors.oas3&&(c.preprocessors.oas3=u.prefixRules(o.preprocessors.oas3,l)),o.preprocessors.oas2&&(c.preprocessors.oas2=u.prefixRules(o.preprocessors.oas2,l))}if(o.decorators){if(!o.decorators.oas3&&!o.decorators.oas2)throw new Error(`Plugin \`decorators\` must have \`oas3\` or \`oas2\` decorators "${e}.`);c.decorators={},o.decorators.oas3&&(c.decorators.oas3=u.prefixRules(o.decorators.oas3,l)),o.decorators.oas2&&(c.decorators.oas2=u.prefixRules(o.decorators.oas2,l))}return c})).filter(p.notUndefined)}function h({rawConfig:e,configPath:t="",resolver:n}){var o,i;return r(this,void 0,void 0,(function*(){const{apis:r={},lint:a={}}=e;let s={};for(const[e,l]of Object.entries(r||{})){if(null===(i=null===(o=l.lint)||void 0===o?void 0:o.extends)||void 0===i?void 0:i.some(p.isNotString))throw new Error("Error configuration format not detected in extends value must contain strings");const r=v(a,l.lint),c=yield g({lintConfig:r,configPath:t,resolver:n});s[e]=Object.assign(Object.assign({},l),{lint:c})}return s}))}function m({lintConfig:e,configPath:t="",resolver:n=new l.BaseResolver},a=[],d=[]){var h,g,v;return r(this,void 0,void 0,(function*(){if(a.includes(t))throw new Error(`Circular dependency in config file: "${t}"`);const l=u.getUniquePlugins(f([...(null==e?void 0:e.plugins)||[],c.defaultPlugin],t)),b=null===(h=null==e?void 0:e.plugins)||void 0===h?void 0:h.filter(p.isString).map((e=>i.resolve(i.dirname(t),e))),w=s.isAbsoluteUrl(t)?t:t&&i.resolve(t),x=yield Promise.all((null===(g=null==e?void 0:e.extends)||void 0===g?void 0:g.map((e=>r(this,void 0,void 0,(function*(){if(!s.isAbsoluteUrl(e)&&!i.extname(e))return y(e,l);const o=s.isAbsoluteUrl(e)?e:s.isAbsoluteUrl(t)?new URL(e,t).href:i.resolve(i.dirname(t),e),c=yield function(e,t){return r(this,void 0,void 0,(function*(){try{const n=yield t.loadExternalRef(e),r=u.transformConfig(p.parseYaml(n.body));if(!r.lint)throw new Error(`Lint configuration format not detected: "${e}"`);return r.lint}catch(t){throw new Error(`Failed to load "${e}": ${t.message}`)}}))}(o,n);return yield m({lintConfig:c,configPath:o,resolver:n},[...a,w],d)})))))||[]),k=u.mergeExtends([...x,Object.assign(Object.assign({},e),{plugins:l,extends:void 0,extendPaths:[...a,w],pluginPaths:b})]),{plugins:_=[]}=k,O=o(k,["plugins"]);return Object.assign(Object.assign({},O),{extendPaths:null===(v=O.extendPaths)||void 0===v?void 0:v.filter((e=>e&&!s.isAbsoluteUrl(e))),plugins:u.getUniquePlugins(_),recommendedFallback:null==e?void 0:e.recommendedFallback,doNotResolveExamples:null==e?void 0:e.doNotResolveExamples})}))}function g(e,t=[],n=[]){return r(this,void 0,void 0,(function*(){const r=yield m(e,t,n);return Object.assign(Object.assign({},r),{rules:r.rules&&b(r.rules)})}))}function y(e,t){var n;const{pluginId:r,configName:o}=u.parsePresetName(e),i=t.find((e=>e.id===r));if(!i)throw new Error(`Invalid config ${a.red(e)}: plugin ${r} is not included.`);const s=null===(n=i.configs)||void 0===n?void 0:n[o];if(!s)throw new Error(r?`Invalid config ${a.red(e)}: plugin ${r} doesn't export config with name ${o}.`:`Invalid config ${a.red(e)}: there is no such built-in config.`);return s}function v(e,t){return Object.assign(Object.assign(Object.assign({},e),t),{rules:Object.assign(Object.assign({},null==e?void 0:e.rules),null==t?void 0:t.rules),oas2Rules:Object.assign(Object.assign({},null==e?void 0:e.oas2Rules),null==t?void 0:t.oas2Rules),oas3_0Rules:Object.assign(Object.assign({},null==e?void 0:e.oas3_0Rules),null==t?void 0:t.oas3_0Rules),oas3_1Rules:Object.assign(Object.assign({},null==e?void 0:e.oas3_1Rules),null==t?void 0:t.oas3_1Rules),preprocessors:Object.assign(Object.assign({},null==e?void 0:e.preprocessors),null==t?void 0:t.preprocessors),oas2Preprocessors:Object.assign(Object.assign({},null==e?void 0:e.oas2Preprocessors),null==t?void 0:t.oas2Preprocessors),oas3_0Preprocessors:Object.assign(Object.assign({},null==e?void 0:e.oas3_0Preprocessors),null==t?void 0:t.oas3_0Preprocessors),oas3_1Preprocessors:Object.assign(Object.assign({},null==e?void 0:e.oas3_1Preprocessors),null==t?void 0:t.oas3_1Preprocessors),decorators:Object.assign(Object.assign({},null==e?void 0:e.decorators),null==t?void 0:t.decorators),oas2Decorators:Object.assign(Object.assign({},null==e?void 0:e.oas2Decorators),null==t?void 0:t.oas2Decorators),oas3_0Decorators:Object.assign(Object.assign({},null==e?void 0:e.oas3_0Decorators),null==t?void 0:t.oas3_0Decorators),oas3_1Decorators:Object.assign(Object.assign({},null==e?void 0:e.oas3_1Decorators),null==t?void 0:t.oas3_1Decorators),recommendedFallback:!(null==t?void 0:t.extends)&&e.recommendedFallback})}function b(e){if(!e)return e;const t={},n=[];for(const[r,o]of Object.entries(e))if(r.startsWith("assert/")&&"object"==typeof o&&null!==o){const e=o;n.push(Object.assign(Object.assign({},e),{assertionId:r.replace("assert/","")}))}else t[r]=o;return n.length>0&&(t.assertions=n),t}t.resolveConfig=function(e,t){var n,o,i,a,s;return r(this,void 0,void 0,(function*(){if(null===(o=null===(n=e.lint)||void 0===n?void 0:n.extends)||void 0===o?void 0:o.some(p.isNotString))throw new Error("Error configuration format not detected in extends value must contain strings");const r=new l.BaseResolver(u.getResolveConfig(e.resolve)),c=null!==(a=null===(i=null==e?void 0:e.lint)||void 0===i?void 0:i.extends)&&void 0!==a?a:["recommended"],f=!(null===(s=null==e?void 0:e.lint)||void 0===s?void 0:s.extends),m=Object.assign(Object.assign({},null==e?void 0:e.lint),{extends:c,recommendedFallback:f}),y=yield h({rawConfig:Object.assign(Object.assign({},e),{lint:m}),configPath:t,resolver:r}),v=yield g({lintConfig:m,configPath:t,resolver:r});return new d.Config(Object.assign(Object.assign({},e),{apis:y,lint:v}),t)}))},t.resolvePlugins=f,t.resolveApis=h,t.resolveLint=g,t.resolvePreset=y},3777:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Config=t.LintConfig=t.AVAILABLE_REGIONS=t.DOMAINS=t.DEFAULT_REGION=t.IGNORE_FILE=t.env=void 0;const r=n(5101),o=n(6470),i=n(5273),a=n(771),s=n(1510),l=n(2565);t.env="undefined"!=typeof process&&{}||{},t.IGNORE_FILE=".redocly.lint-ignore.yaml",t.DEFAULT_REGION="us",t.DOMAINS=function(){const e={us:"redocly.com",eu:"eu.redocly.com"},n=t.env.REDOCLY_DOMAIN;return(null==n?void 0:n.endsWith(".redocly.host"))&&(e[n.split(".")[0]]=n),"redoc.online"===n&&(e[n]=n),e}(),t.AVAILABLE_REGIONS=Object.keys(t.DOMAINS);class c{constructor(e,n){this.rawConfig=e,this.configFile=n,this.ignore={},this._usedRules=new Set,this._usedVersions=new Set,this.plugins=e.plugins||[],this.doNotResolveExamples=!!e.doNotResolveExamples,this.recommendedFallback=e.recommendedFallback||!1,this.rules={[s.OasVersion.Version2]:Object.assign(Object.assign({},e.rules),e.oas2Rules),[s.OasVersion.Version3_0]:Object.assign(Object.assign({},e.rules),e.oas3_0Rules),[s.OasVersion.Version3_1]:Object.assign(Object.assign({},e.rules),e.oas3_1Rules)},this.preprocessors={[s.OasVersion.Version2]:Object.assign(Object.assign({},e.preprocessors),e.oas2Preprocessors),[s.OasVersion.Version3_0]:Object.assign(Object.assign({},e.preprocessors),e.oas3_0Preprocessors),[s.OasVersion.Version3_1]:Object.assign(Object.assign({},e.preprocessors),e.oas3_1Preprocessors)},this.decorators={[s.OasVersion.Version2]:Object.assign(Object.assign({},e.decorators),e.oas2Decorators),[s.OasVersion.Version3_0]:Object.assign(Object.assign({},e.decorators),e.oas3_0Decorators),[s.OasVersion.Version3_1]:Object.assign(Object.assign({},e.decorators),e.oas3_1Decorators)},this.extendPaths=e.extendPaths||[],this.pluginPaths=e.pluginPaths||[];const a=this.configFile?o.dirname(this.configFile):"undefined"!=typeof process&&process.cwd()||"",l=o.join(a,t.IGNORE_FILE);if(r.hasOwnProperty("existsSync")&&r.existsSync(l)){this.ignore=i.parseYaml(r.readFileSync(l,"utf-8"))||{};for(const e of Object.keys(this.ignore)){this.ignore[o.resolve(o.dirname(l),e)]=this.ignore[e];for(const t of Object.keys(this.ignore[e]))this.ignore[e][t]=new Set(this.ignore[e][t]);delete this.ignore[e]}}}saveIgnore(){const e=this.configFile?o.dirname(this.configFile):process.cwd(),n=o.join(e,t.IGNORE_FILE),s={};for(const t of Object.keys(this.ignore)){const n=s[a.slash(o.relative(e,t))]=this.ignore[t];for(const e of Object.keys(n))n[e]=Array.from(n[e])}r.writeFileSync(n,"# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n# See https://redoc.ly/docs/cli/ for more information.\n"+i.stringifyYaml(s))}addIgnore(e){const t=this.ignore,n=e.location[0];if(void 0===n.pointer)return;const r=t[n.source.absoluteRef]=t[n.source.absoluteRef]||{};(r[e.ruleId]=r[e.ruleId]||new Set).add(n.pointer)}addProblemToIgnore(e){const t=e.location[0];if(void 0===t.pointer)return e;const n=(this.ignore[t.source.absoluteRef]||{})[e.ruleId],r=n&&n.has(t.pointer);return r?Object.assign(Object.assign({},e),{ignored:r}):e}extendTypes(e,t){let n=e;for(const e of this.plugins)if(void 0!==e.typeExtension)switch(t){case s.OasVersion.Version3_0:case s.OasVersion.Version3_1:if(!e.typeExtension.oas3)continue;n=e.typeExtension.oas3(n,t);case s.OasVersion.Version2:if(!e.typeExtension.oas2)continue;n=e.typeExtension.oas2(n,t);default:throw new Error("Not implemented")}return n}getRuleSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.rules[t][e]||"off";return"string"==typeof n?{severity:n}:Object.assign({severity:"error"},n)}getPreprocessorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.preprocessors[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getDecoratorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.decorators[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getUnusedRules(){const e=[],t=[],n=[];for(const r of Array.from(this._usedVersions))e.push(...Object.keys(this.rules[r]).filter((e=>!this._usedRules.has(e)))),t.push(...Object.keys(this.decorators[r]).filter((e=>!this._usedRules.has(e)))),n.push(...Object.keys(this.preprocessors[r]).filter((e=>!this._usedRules.has(e))));return{rules:e,preprocessors:n,decorators:t}}getRulesForOasVersion(e){switch(e){case s.OasMajorVersion.Version3:const e=[];return this.plugins.forEach((t=>{var n;return(null===(n=t.preprocessors)||void 0===n?void 0:n.oas3)&&e.push(t.preprocessors.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.rules)||void 0===n?void 0:n.oas3)&&e.push(t.rules.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.decorators)||void 0===n?void 0:n.oas3)&&e.push(t.decorators.oas3)})),e;case s.OasMajorVersion.Version2:const t=[];return this.plugins.forEach((e=>{var n;return(null===(n=e.preprocessors)||void 0===n?void 0:n.oas2)&&t.push(e.preprocessors.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.rules)||void 0===n?void 0:n.oas2)&&t.push(e.rules.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.decorators)||void 0===n?void 0:n.oas2)&&t.push(e.decorators.oas2)})),t}}skipRules(e){for(const t of e||[])for(const e of Object.values(s.OasVersion))this.rules[e][t]&&(this.rules[e][t]="off")}skipPreprocessors(e){for(const t of e||[])for(const e of Object.values(s.OasVersion))this.preprocessors[e][t]&&(this.preprocessors[e][t]="off")}skipDecorators(e){for(const t of e||[])for(const e of Object.values(s.OasVersion))this.decorators[e][t]&&(this.decorators[e][t]="off")}}t.LintConfig=c,t.Config=class{constructor(e,t){this.rawConfig=e,this.configFile=t,this.apis=e.apis||{},this.lint=new c(e.lint||{},t),this["features.openapi"]=e["features.openapi"]||{},this["features.mockServer"]=e["features.mockServer"]||{},this.resolve=l.getResolveConfig(null==e?void 0:e.resolve),this.region=e.region,this.organization=e.organization}}},8698:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||r(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),o(n(3777),t),o(n(3865),t),o(n(5030),t),o(n(6242),t),o(n(9129),t),o(n(2565),t),o(n(7040),t)},9129:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getConfig=t.findConfig=t.CONFIG_FILE_NAMES=t.loadConfig=void 0;const o=n(5101),i=n(6470),a=n(1094),s=n(771),l=n(3777),c=n(2565),u=n(7040);function p(e){if(!o.hasOwnProperty("existsSync"))return;const n=t.CONFIG_FILE_NAMES.map((t=>e?i.resolve(e,t):t)).filter(o.existsSync);if(n.length>1)throw new Error(`\n Multiple configuration files are not allowed. \n Found the following files: ${n.join(", ")}. \n Please use 'redocly.yaml' instead.\n `);return n[0]}function d(e=p()){return r(this,void 0,void 0,(function*(){if(!e)return{};try{const t=(yield s.loadYaml(e))||{};return c.transformConfig(t)}catch(t){throw new Error(`Error parsing config file at '${e}': ${t.message}`)}}))}t.loadConfig=function(e=p(),t,n){return r(this,void 0,void 0,(function*(){const o=yield d(e);return"function"==typeof n&&(yield n(o)),yield function({rawConfig:e,customExtends:t,configPath:n}){var o;return r(this,void 0,void 0,(function*(){void 0!==t?(e.lint=e.lint||{},e.lint.extends=t):s.isEmptyObject(e);const r=new a.RedoclyClient,i=yield r.getTokens();if(i.length){e.resolve||(e.resolve={}),e.resolve.http||(e.resolve.http={}),e.resolve.http.headers=[...null!==(o=e.resolve.http.headers)&&void 0!==o?o:[]];for(const t of i){const n=l.DOMAINS[t.region];e.resolve.http.headers.push({matches:`https://api.${n}/registry/**`,name:"Authorization",envVariable:void 0,value:t.token},..."us"===t.region?[{matches:"https://api.redoc.ly/registry/**",name:"Authorization",envVariable:void 0,value:t.token}]:[])}}return u.resolveConfig(e,n)}))}({rawConfig:o,customExtends:t,configPath:e})}))},t.CONFIG_FILE_NAMES=["redocly.yaml","redocly.yml",".redocly.yaml",".redocly.yml"],t.findConfig=p,t.getConfig=d},9016:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={rules:{"info-description":"warn","info-contact":"off","info-license":"off","info-license-url":"off","tag-description":"warn","tags-alphabetical":"off","parameter-description":"off","no-path-trailing-slash":"warn","no-identical-paths":"warn","no-ambiguous-paths":"warn","path-declaration-must-exist":"warn","path-not-include-query":"warn","path-parameters-defined":"warn","operation-description":"off","operation-2xx-response":"warn","operation-4xx-response":"off",assertions:"warn","operation-operationId":"warn","operation-summary":"warn","operation-operationId-unique":"warn","operation-parameters-unique":"warn","operation-tag-defined":"off","operation-security-defined":"warn","operation-operationId-url-safe":"warn","operation-singular-tag":"off","no-unresolved-refs":"error","no-enum-type-mismatch":"warn","boolean-parameter-prefixes":"off","paths-kebab-case":"off",spec:"error"},oas3_0Rules:{"no-invalid-media-type-examples":{severity:"warn",disallowAdditionalProperties:!0},"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"warn","no-example-value-and-externalValue":"warn","no-unused-components":"warn","no-undefined-server-variable":"warn","no-servers-empty-enum":"error"},oas3_1Rules:{"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"warn","no-example-value-and-externalValue":"warn","no-unused-components":"warn","no-undefined-server-variable":"warn","no-servers-empty-enum":"error"}}},8057:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={rules:{"info-description":"warn","info-contact":"off","info-license":"warn","info-license-url":"warn","tag-description":"warn","tags-alphabetical":"off","parameter-description":"off","no-path-trailing-slash":"error","no-identical-paths":"error","no-ambiguous-paths":"warn","path-declaration-must-exist":"error","path-not-include-query":"error","path-parameters-defined":"error","operation-description":"off","operation-2xx-response":"warn",assertions:"warn","operation-4xx-response":"warn","operation-operationId":"warn","operation-summary":"error","operation-operationId-unique":"error","operation-operationId-url-safe":"error","operation-parameters-unique":"error","operation-tag-defined":"off","operation-security-defined":"error","operation-singular-tag":"off","no-unresolved-refs":"error","no-enum-type-mismatch":"error","boolean-parameter-prefixes":"off","paths-kebab-case":"off",spec:"error"},oas3_0Rules:{"no-invalid-media-type-examples":{severity:"warn",disallowAdditionalProperties:!0},"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"warn","no-undefined-server-variable":"error","no-servers-empty-enum":"error"},oas3_1Rules:{"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"warn","no-undefined-server-variable":"error","no-servers-empty-enum":"error"}}},5030:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.initRules=void 0;const r=n(771);t.initRules=function(e,t,n,o){return e.flatMap((e=>Object.keys(e).map((r=>{const i=e[r],a="rules"===n?t.getRuleSettings(r,o):"preprocessors"===n?t.getPreprocessorSettings(r,o):t.getDecoratorSettings(r,o);if("off"===a.severity)return;const s=i(a);return Array.isArray(s)?s.map((e=>({severity:a.severity,ruleId:r,visitor:e}))):{severity:a.severity,ruleId:r,visitor:s}})))).flatMap((e=>e)).filter(r.notUndefined)}},3865:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},2565:function(e,t,n){"use strict";var r=this&&this.__rest||function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o-1){const[t,n]=e.split("/");return{pluginId:t,configName:n}}return{pluginId:"",configName:e}},t.transformApiDefinitionsToApis=s,t.prefixRules=function(e,t){if(!t)return e;const n={};for(const r of Object.keys(e))n[`${t}/${r}`]=e[r];return n},t.mergeExtends=function(e){const t={rules:{},oas2Rules:{},oas3_0Rules:{},oas3_1Rules:{},preprocessors:{},oas2Preprocessors:{},oas3_0Preprocessors:{},oas3_1Preprocessors:{},decorators:{},oas2Decorators:{},oas3_0Decorators:{},oas3_1Decorators:{},plugins:[],pluginPaths:[],extendPaths:[]};for(let n of e){if(n.extends)throw new Error(`\`extends\` is not supported in shared configs yet: ${JSON.stringify(n,null,2)}.`);Object.assign(t.rules,n.rules),Object.assign(t.oas2Rules,n.oas2Rules),i.assignExisting(t.oas2Rules,n.rules||{}),Object.assign(t.oas3_0Rules,n.oas3_0Rules),i.assignExisting(t.oas3_0Rules,n.rules||{}),Object.assign(t.oas3_1Rules,n.oas3_1Rules),i.assignExisting(t.oas3_1Rules,n.rules||{}),Object.assign(t.preprocessors,n.preprocessors),Object.assign(t.oas2Preprocessors,n.oas2Preprocessors),i.assignExisting(t.oas2Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_0Preprocessors,n.oas3_0Preprocessors),i.assignExisting(t.oas3_0Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_1Preprocessors,n.oas3_1Preprocessors),i.assignExisting(t.oas3_1Preprocessors,n.preprocessors||{}),Object.assign(t.decorators,n.decorators),Object.assign(t.oas2Decorators,n.oas2Decorators),i.assignExisting(t.oas2Decorators,n.decorators||{}),Object.assign(t.oas3_0Decorators,n.oas3_0Decorators),i.assignExisting(t.oas3_0Decorators,n.decorators||{}),Object.assign(t.oas3_1Decorators,n.oas3_1Decorators),i.assignExisting(t.oas3_1Decorators,n.decorators||{}),t.plugins.push(...n.plugins||[]),t.pluginPaths.push(...n.pluginPaths||[]),t.extendPaths.push(...new Set(n.extendPaths))}return t},t.getMergedConfig=function(e,t){var n,r,o,i,s,l;const c=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.lint)||void 0===t?void 0:t.extendPaths})),null===(r=null===(n=e.rawConfig)||void 0===n?void 0:n.lint)||void 0===r?void 0:r.extendPaths].flat().filter(Boolean),u=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.lint)||void 0===t?void 0:t.pluginPaths})),null===(i=null===(o=e.rawConfig)||void 0===o?void 0:o.lint)||void 0===i?void 0:i.pluginPaths].flat().filter(Boolean);return t?new a.Config(Object.assign(Object.assign({},e.rawConfig),{lint:Object.assign(Object.assign({},e.apis[t]?e.apis[t].lint:e.rawConfig.lint),{extendPaths:c,pluginPaths:u}),"features.openapi":Object.assign(Object.assign({},e["features.openapi"]),null===(s=e.apis[t])||void 0===s?void 0:s["features.openapi"]),"features.mockServer":Object.assign(Object.assign({},e["features.mockServer"]),null===(l=e.apis[t])||void 0===l?void 0:l["features.mockServer"])}),e.configFile):e},t.transformConfig=function(e){if(e.apis&&e.apiDefinitions)throw new Error("Do not use 'apiDefinitions' field. Use 'apis' instead.\n");if(e["features.openapi"]&&e.referenceDocs)throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");const t=e,{apiDefinitions:n,referenceDocs:i}=t,a=r(t,["apiDefinitions","referenceDocs"]);return n&&process.stderr.write(`The ${o.yellow("apiDefinitions")} field is deprecated. Use ${o.green("apis")} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`),i&&process.stderr.write(`The ${o.yellow("referenceDocs")} field is deprecated. Use ${o.green("features.openapi")} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`),Object.assign({"features.openapi":i,apis:s(n)},a)},t.getResolveConfig=function(e){var t,n;return{http:{headers:null!==(n=null===(t=null==e?void 0:e.http)||void 0===t?void 0:t.headers)&&void 0!==n?n:[],customFetch:void 0}}},t.getUniquePlugins=function(e){const t=new Set,n=[];for(const r of e)t.has(r.id)?r.id&&process.stderr.write(`Duplicate plugin id "${o.yellow(r.id)}".\n`):(n.push(r),t.add(r.id));return n}},1988:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.checkIfMatchByStrategy=t.filter=void 0;const r=n(7468),o=n(771);function i(e){return Array.isArray(e)?e:[e]}t.filter=function(e,t,n){const{parent:i,key:a}=t;let s=!1;if(Array.isArray(e))for(let o=0;oe.includes(t))):"all"===n&&t.every((t=>e.includes(t)))):e===t)}},9244:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FilterIn=void 0;const r=n(1988);t.FilterIn=({property:e,value:t,matchStrategy:n})=>{const o=n||"any",i=n=>(null==n?void 0:n[e])&&!r.checkIfMatchByStrategy(null==n?void 0:n[e],t,o);return{any:{enter:(e,t)=>{r.filter(e,t,i)}}}}},8623:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FilterOut=void 0;const r=n(1988);t.FilterOut=({property:e,value:t,matchStrategy:n})=>{const o=n||"any",i=n=>r.checkIfMatchByStrategy(null==n?void 0:n[e],t,o);return{any:{enter:(e,t)=>{r.filter(e,t,i)}}}}},4555:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoDescriptionOverride=void 0;const r=n(771);t.InfoDescriptionOverride=({filePath:e})=>({Info:{leave(t,{report:n,location:o}){if(!e)throw new Error('Parameter "filePath" is not provided for "info-description-override" rule');try{t.description=r.readFileAsStringSync(e)}catch(e){n({message:`Failed to read markdown override file for "info.description".\n${e.message}`,location:o.child("description")})}}}})},7802:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationDescriptionOverride=void 0;const r=n(771);t.OperationDescriptionOverride=({operationIds:e})=>({Operation:{leave(t,{report:n,location:o}){if(!t.operationId)return;if(!e)throw new Error('Parameter "operationIds" is not provided for "operation-description-override" rule');const i=t.operationId;if(e[i])try{t.description=r.readFileAsStringSync(e[i])}catch(e){n({message:`Failed to read markdown override file for operation "${i}".\n${e.message}`,location:o.child("operationId").key()})}}}})},2287:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RegistryDependencies=void 0;const r=n(1094);t.RegistryDependencies=()=>{let e=new Set;return{DefinitionRoot:{leave(t,n){n.getVisitorData().links=Array.from(e)}},ref(t){if(t.$ref){const n=t.$ref.split("#/")[0];r.isRedoclyRegistryURL(n)&&e.add(n)}}}}},5830:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveXInternal=void 0;const r=n(771),o=n(7468);t.RemoveXInternal=({internalFlagProperty:e})=>{const t=e||"x-internal";return{any:{enter:(e,n)=>{!function(e,n){var i,a,s,l;const{parent:c,key:u}=n;let p=!1;if(Array.isArray(e))for(let r=0;r({Tag:{leave(t,{report:n}){if(!e)throw new Error('Parameter "tagNames" is not provided for "tag-description-override" rule');if(e[t.name])try{t.description=r.readFileAsStringSync(e[t.name])}catch(e){n({message:`Failed to read markdown override file for tag "${t.name}".\n${e.message}`})}}}})},7060:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decorators=void 0;const r=n(2287),o=n(7802),i=n(423),a=n(4555),s=n(5830),l=n(9244),c=n(8623);t.decorators={"registry-dependencies":r.RegistryDependencies,"operation-description-override":o.OperationDescriptionOverride,"tag-description-override":i.TagDescriptionOverride,"info-description-override":a.InfoDescriptionOverride,"remove-x-internal":s.RemoveXInternal,"filter-in":l.FilterIn,"filter-out":c.FilterOut}},1753:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decorators=void 0;const r=n(2287),o=n(7802),i=n(423),a=n(4555),s=n(5830),l=n(9244),c=n(8623);t.decorators={"registry-dependencies":r.RegistryDependencies,"operation-description-override":o.OperationDescriptionOverride,"tag-description-override":i.TagDescriptionOverride,"info-description-override":a.InfoDescriptionOverride,"remove-x-internal":s.RemoveXInternal,"filter-in":l.FilterIn,"filter-out":c.FilterOut}},5273:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.stringifyYaml=t.parseYaml=void 0;const r=n(3320),o=r.JSON_SCHEMA.extend({implicit:[r.types.merge],explicit:[r.types.binary,r.types.omap,r.types.pairs,r.types.set]});t.parseYaml=(e,t)=>r.load(e,Object.assign({schema:o},t)),t.stringifyYaml=(e,t)=>r.dump(e,t)},1510:function(e,t){"use strict";var n,r;Object.defineProperty(t,"__esModule",{value:!0}),t.openAPIMajor=t.detectOpenAPI=t.OasMajorVersion=t.OasVersion=void 0,function(e){e.Version2="oas2",e.Version3_0="oas3_0",e.Version3_1="oas3_1"}(n=t.OasVersion||(t.OasVersion={})),function(e){e.Version2="oas2",e.Version3="oas3"}(r=t.OasMajorVersion||(t.OasMajorVersion={})),t.detectOpenAPI=function(e){if("object"!=typeof e)throw new Error("Document must be JSON object, got "+typeof e);if(!e.openapi&&!e.swagger)throw new Error("This doesn’t look like an OpenAPI document.\n");if(e.openapi&&"string"!=typeof e.openapi)throw new Error(`Invalid OpenAPI version: should be a string but got "${typeof e.openapi}"`);if(e.openapi&&e.openapi.startsWith("3.0"))return n.Version3_0;if(e.openapi&&e.openapi.startsWith("3.1"))return n.Version3_1;if(e.swagger&&"2.0"===e.swagger)return n.Version2;throw new Error(`Unsupported OpenAPI Version: ${e.openapi||e.swagger}`)},t.openAPIMajor=function(e){return e===n.Version2?r.Version2:r.Version3}},1094:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.isRedoclyRegistryURL=t.RedoclyClient=void 0;const o=n(2116),i=n(6470),a=n(6918),s=n(8836),l=n(1390),c=n(3777),u=n(771),p=".redocly-config.json";t.RedoclyClient=class{constructor(e){this.accessTokens={},this.region=this.loadRegion(e),this.loadTokens(),this.domain=e?c.DOMAINS[e]:c.env.REDOCLY_DOMAIN||c.DOMAINS[c.DEFAULT_REGION],c.env.REDOCLY_DOMAIN=this.domain,this.registryApi=new l.RegistryApi(this.accessTokens,this.region)}loadRegion(e){if(e&&!c.DOMAINS[e])throw new Error(`Invalid argument: region in config file.\nGiven: ${s.green(e)}, choices: "us", "eu".`);return c.env.REDOCLY_DOMAIN?c.AVAILABLE_REGIONS.find((e=>c.DOMAINS[e]===c.env.REDOCLY_DOMAIN))||c.DEFAULT_REGION:e||c.DEFAULT_REGION}getRegion(){return this.region}hasTokens(){return u.isNotEmptyObject(this.accessTokens)}hasToken(){return!!this.accessTokens[this.region]}getAuthorizationHeader(){return r(this,void 0,void 0,(function*(){return this.accessTokens[this.region]}))}setAccessTokens(e){this.accessTokens=e}loadTokens(){const e=i.resolve(a.homedir(),p),t=this.readCredentialsFile(e);u.isNotEmptyObject(t)&&this.setAccessTokens(Object.assign(Object.assign({},t),t.token&&!t[this.region]&&{[this.region]:t.token})),c.env.REDOCLY_AUTHORIZATION&&this.setAccessTokens(Object.assign(Object.assign({},this.accessTokens),{[this.region]:c.env.REDOCLY_AUTHORIZATION}))}getAllTokens(){return Object.entries(this.accessTokens).filter((([e])=>c.AVAILABLE_REGIONS.includes(e))).map((([e,t])=>({region:e,token:t})))}getValidTokens(){return r(this,void 0,void 0,(function*(){const e=this.getAllTokens(),t=yield Promise.allSettled(e.map((({token:e,region:t})=>this.verifyToken(e,t))));return e.filter(((e,n)=>"fulfilled"===t[n].status)).map((({token:e,region:t})=>({token:e,region:t,valid:!0})))}))}getTokens(){return r(this,void 0,void 0,(function*(){return this.hasTokens()?yield this.getValidTokens():[]}))}isAuthorizedWithRedoclyByRegion(){return r(this,void 0,void 0,(function*(){if(!this.hasTokens())return!1;const e=this.accessTokens[this.region];if(!e)return!1;try{return yield this.verifyToken(e,this.region),!0}catch(e){return!1}}))}isAuthorizedWithRedocly(){return r(this,void 0,void 0,(function*(){return this.hasTokens()&&u.isNotEmptyObject(yield this.getValidTokens())}))}readCredentialsFile(e){return o.existsSync(e)?JSON.parse(o.readFileSync(e,"utf-8")):{}}verifyToken(e,t,n=!1){return r(this,void 0,void 0,(function*(){return this.registryApi.authStatus(e,t,n)}))}login(e,t=!1){return r(this,void 0,void 0,(function*(){const n=i.resolve(a.homedir(),p);try{yield this.verifyToken(e,this.region,t)}catch(e){throw new Error("Authorization failed. Please check if you entered a valid API key.")}const r=Object.assign(Object.assign({},this.readCredentialsFile(n)),{[this.region]:e,token:e});this.accessTokens=r,this.registryApi.setAccessTokens(r),o.writeFileSync(n,JSON.stringify(r,null,2))}))}logout(){const e=i.resolve(a.homedir(),p);o.existsSync(e)&&o.unlinkSync(e)}},t.isRedoclyRegistryURL=function(e){const t=c.env.REDOCLY_DOMAIN||c.DOMAINS[c.DEFAULT_REGION],n="redocly.com"===t?"redoc.ly":t;return!(!e.startsWith(`https://api.${t}/registry/`)&&!e.startsWith(`https://api.${n}/registry/`))}},1390:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.RegistryApi=void 0;const o=n(8150),i=n(3777),a=n(771),s=n(3244).i8;t.RegistryApi=class{constructor(e,t){this.accessTokens=e,this.region=t}get accessToken(){return a.isNotEmptyObject(this.accessTokens)&&this.accessTokens[this.region]}getBaseUrl(e=i.DEFAULT_REGION){return`https://api.${i.DOMAINS[e]}/registry`}setAccessTokens(e){return this.accessTokens=e,this}request(e="",t={},n){return r(this,void 0,void 0,(function*(){const r=Object.assign({},t.headers||{},{"x-redocly-cli-version":s});if(!r.hasOwnProperty("authorization"))throw new Error("Unauthorized");const i=yield o.default(`${this.getBaseUrl(n)}${e}`,Object.assign({},t,{headers:r}));if(401===i.status)throw new Error("Unauthorized");if(404===i.status){const e=yield i.json();throw new Error(e.code)}return i}))}authStatus(e,t,n=!1){return r(this,void 0,void 0,(function*(){try{const n=yield this.request("",{headers:{authorization:e}},t);return yield n.json()}catch(e){throw n&&console.log(e),e}}))}prepareFileUpload({organizationId:e,name:t,version:n,filesHash:o,filename:i,isUpsert:a}){return r(this,void 0,void 0,(function*(){const r=yield this.request(`/${e}/${t}/${n}/prepare-file-upload`,{method:"POST",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({filesHash:o,filename:i,isUpsert:a})},this.region);if(r.ok)return r.json();throw new Error("Could not prepare file upload")}))}pushApi({organizationId:e,name:t,version:n,rootFilePath:o,filePaths:i,branch:a,isUpsert:s,isPublic:l,batchId:c,batchSize:u}){return r(this,void 0,void 0,(function*(){if(!(yield this.request(`/${e}/${t}/${n}`,{method:"PUT",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({rootFilePath:o,filePaths:i,branch:a,isUpsert:s,isPublic:l,batchId:c,batchSize:u})},this.region)).ok)throw new Error("Could not push api")}))}}},7468:function(e,t){"use strict";function n(e,t){return""===e&&(e="#/"),"/"===e[e.length-1]?e+t:e+"/"+t}Object.defineProperty(t,"__esModule",{value:!0}),t.isMappingRef=t.isAbsoluteUrl=t.refBaseName=t.pointerBaseName=t.parsePointer=t.parseRef=t.escapePointer=t.unescapePointer=t.Location=t.isRef=t.joinPointer=void 0,t.joinPointer=n,t.isRef=function(e){return e&&"string"==typeof e.$ref};class r{constructor(e,t){this.source=e,this.pointer=t}child(e){return new r(this.source,n(this.pointer,(Array.isArray(e)?e:[e]).map(i).join("/")))}key(){return Object.assign(Object.assign({},this),{reportOnKey:!0})}get absolutePointer(){return this.source.absoluteRef+("#/"===this.pointer?"":this.pointer)}}function o(e){return decodeURIComponent(e.replace(/~1/g,"/").replace(/~0/g,"~"))}function i(e){return"number"==typeof e?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}t.Location=r,t.unescapePointer=o,t.escapePointer=i,t.parseRef=function(e){const[t,n]=e.split("#/");return{uri:t||null,pointer:n?n.split("/").map(o).filter(Boolean):[]}},t.parsePointer=function(e){return e.substr(2).split("/").map(o)},t.pointerBaseName=function(e){const t=e.split("/");return t[t.length-1]},t.refBaseName=function(e){const t=e.split(/[\/\\]/);return t[t.length-1].replace(/\.[^.]+$/,"")},t.isAbsoluteUrl=function(e){return e.startsWith("http://")||e.startsWith("https://")},t.isMappingRef=function(e){return e.startsWith("#")||e.startsWith("https://")||e.startsWith("http://")||e.startsWith("./")||e.startsWith("../")||e.indexOf("/")>-1}},4182:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.resolveDocument=t.BaseResolver=t.makeDocumentFromString=t.makeRefId=t.YamlParseError=t.ResolveError=t.Source=void 0;const o=n(3197),i=n(6470),a=n(7468),s=n(5220),l=n(771);class c{constructor(e,t,n){this.absoluteRef=e,this.body=t,this.mimeType=n}getAst(e){var t;return void 0===this._ast&&(this._ast=null!==(t=e(this.body,{filename:this.absoluteRef}))&&void 0!==t?t:void 0,this._ast&&0===this._ast.kind&&""===this._ast.value&&1!==this._ast.startPosition&&(this._ast.startPosition=1,this._ast.endPosition=1)),this._ast}getLines(){return void 0===this._lines&&(this._lines=this.body.split(/\r\n|[\n\r]/g)),this._lines}}t.Source=c;class u extends Error{constructor(e){super(e.message),this.originalError=e,Object.setPrototypeOf(this,u.prototype)}}t.ResolveError=u;const p=/\((\d+):(\d+)\)$/;class d extends Error{constructor(e,t){super(e.message.split("\n")[0]),this.originalError=e,this.source=t,Object.setPrototypeOf(this,d.prototype);const[,n,r]=this.message.match(p)||[];this.line=parseInt(n,10),this.col=parseInt(r,10)}}function f(e,t){return e+"::"+t}function h(e,t){return{prev:e,node:t}}t.YamlParseError=d,t.makeRefId=f,t.makeDocumentFromString=function(e,t){const n=new c(t,e);try{return{source:n,parsed:l.parseYaml(e,{filename:t})}}catch(e){throw new d(e,n)}},t.BaseResolver=class{constructor(e={http:{headers:[]}}){this.config=e,this.cache=new Map}getFiles(){return new Set(Array.from(this.cache.keys()))}resolveExternalRef(e,t){return a.isAbsoluteUrl(t)?t:e&&a.isAbsoluteUrl(e)?new URL(t,e).href:i.resolve(e?i.dirname(e):process.cwd(),t)}loadExternalRef(e){return r(this,void 0,void 0,(function*(){try{if(a.isAbsoluteUrl(e)){const{body:t,mimeType:n}=yield l.readFileFromUrl(e,this.config.http);return new c(e,t,n)}return new c(e,yield o.promises.readFile(e,"utf-8"))}catch(e){throw new u(e)}}))}parseDocument(e,t=!1){var n;const r=e.absoluteRef.substr(e.absoluteRef.lastIndexOf("."));if(![".json",".json",".yml",".yaml"].includes(r)&&!(null===(n=e.mimeType)||void 0===n?void 0:n.match(/(json|yaml|openapi)/))&&!t)return{source:e,parsed:e.body};try{return{source:e,parsed:l.parseYaml(e.body,{filename:e.absoluteRef})}}catch(t){throw new d(t,e)}}resolveDocument(e,t,n=!1){return r(this,void 0,void 0,(function*(){const r=this.resolveExternalRef(e,t),o=this.cache.get(r);if(o)return o;const i=this.loadExternalRef(r).then((e=>this.parseDocument(e,n)));return this.cache.set(r,i),i}))}};const m={name:"unknown",properties:{}},g={name:"scalar",properties:{}};t.resolveDocument=function(e){return r(this,void 0,void 0,(function*(){const{rootDocument:t,externalRefResolver:n,rootType:o}=e,i=new Map,l=new Set,c=[];let u;!function e(t,o,u,p){function d(e,t,o){return r(this,void 0,void 0,(function*(){if(function(e,t){for(;e;){if(e.node===t)return!0;e=e.prev}return!1}(o.prev,t))throw new Error("Self-referencing circular pointer");const{uri:r,pointer:s}=a.parseRef(t.$ref),l=null!==r;let c;try{c=l?yield n.resolveDocument(e.source.absoluteRef,r):e}catch(n){const r={resolved:!1,isRemote:l,document:void 0,error:n},o=f(e.source.absoluteRef,t.$ref);return i.set(o,r),r}let u={resolved:!0,document:c,isRemote:l,node:e.parsed,nodePointer:"#/"},p=c.parsed;const m=s;for(let e of m){if("object"!=typeof p){p=void 0;break}if(void 0!==p[e])p=p[e],u.nodePointer=a.joinPointer(u.nodePointer,a.escapePointer(e));else{if(!a.isRef(p)){p=void 0;break}if(u=yield d(c,p,h(o,p)),c=u.document||c,"object"!=typeof u.node){p=void 0;break}p=u.node[e],u.nodePointer=a.joinPointer(u.nodePointer,a.escapePointer(e))}}u.node=p,u.document=c;const g=f(e.source.absoluteRef,t.$ref);return u.document&&a.isRef(p)&&(u=yield d(u.document,p,h(o,p))),i.set(g,u),Object.assign({},u)}))}!function t(n,r,i){if("object"!=typeof n||null===n)return;const u=`${r.name}::${i}`;if(!l.has(u))if(l.add(u),Array.isArray(n)){const e=r.items;if(r!==m&&void 0===e)return;for(let r=0;r{t.resolved&&e(t.node,t.document,t.nodePointer,r)}));c.push(t)}}}(t,p,o.source.absoluteRef+u)}(t.parsed,t,"#/",o);do{u=yield Promise.all(c)}while(c.length!==u.length);return i}))}},7275:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateJsonSchema=t.releaseAjvInstance=void 0;const r=n(5499),o=n(7468);let i=null;t.releaseAjvInstance=function(){i=null},t.validateJsonSchema=function(e,t,n,a,s,l){const c=function(e,t,n,o){const a=function(e,t){return i||(i=new r.default({schemaId:"$id",meta:!0,allErrors:!0,strictSchema:!1,inlineRefs:!1,validateSchema:!1,discriminator:!0,allowUnionTypes:!0,validateFormats:!1,defaultAdditionalProperties:!t,loadSchemaSync(t,n){const r=e({$ref:n},t.split("#")[0]);return!(!r||!r.location)&&Object.assign({$id:r.location.absolutePointer},r.node)},logger:!1})),i}(n,o);return a.getSchema(t.absolutePointer)||a.addSchema(Object.assign({$id:t.absolutePointer},e),t.absolutePointer),a.getSchema(t.absolutePointer)}(t,n,s,l);return c?{valid:!!c(e,{instancePath:a,parentData:{fake:{}},parentDataProperty:"fake",rootData:{},dynamicAnchors:{}}),errors:(c.errors||[]).map((function(e){let t=e.message,n="enum"===e.keyword?e.params.allowedValues:void 0;n&&(t+=` ${n.map((e=>`"${e}"`)).join(", ")}`),"type"===e.keyword&&(t=`type ${t}`);const r=e.instancePath.substring(a.length+1),i=r.substring(r.lastIndexOf("/")+1);if(i&&(t=`\`${i}\` property ${t}`),"additionalProperties"===e.keyword){const n=e.params.additionalProperty;t=`${t} \`${n}\``,e.instancePath+="/"+o.escapePointer(n)}return Object.assign(Object.assign({},e),{message:t,suggest:n})}))}:{valid:!0,errors:[]}}},9740:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.asserts=t.runOnValuesSet=t.runOnKeysSet=void 0;const r=n(771),o=n(5738);t.runOnKeysSet=new Set(["mutuallyExclusive","mutuallyRequired","enum","pattern","minLength","maxLength","casing","sortOrder","disallowed","required","requireAny","ref"]),t.runOnValuesSet=new Set(["pattern","enum","defined","undefined","nonEmpty","minLength","maxLength","casing","sortOrder","ref"]),t.asserts={pattern:(e,t,n)=>{if(void 0===e)return{isValid:!0};const i=r.isString(e)?[e]:e,a=o.regexFromString(t);for(let t of i)if(!(null==a?void 0:a.test(t)))return{isValid:!1,location:r.isString(e)?n:n.key()};return{isValid:!0}},enum:(e,t,n)=>{if(void 0===e)return{isValid:!0};const o=r.isString(e)?[e]:e;for(let i of o)if(!t.includes(i))return{isValid:!1,location:r.isString(e)?n:n.child(i).key()};return{isValid:!0}},defined:(e,t=!0,n)=>{const r=void 0!==e;return{isValid:t?r:!r,location:n}},required:(e,t,n)=>{for(const r of t)if(!e.includes(r))return{isValid:!1,location:n.key()};return{isValid:!0}},disallowed:(e,t,n)=>{if(void 0===e)return{isValid:!0};const o=r.isString(e)?[e]:e;for(let i of o)if(t.includes(i))return{isValid:!1,location:r.isString(e)?n:n.child(i).key()};return{isValid:!0}},undefined:(e,t=!0,n)=>{const r=void 0===e;return{isValid:t?r:!r,location:n}},nonEmpty:(e,t=!0,n)=>{const r=null==e||""===e;return{isValid:t?!r:r,location:n}},minLength:(e,t,n)=>void 0===e?{isValid:!0}:{isValid:e.length>=t,location:n},maxLength:(e,t,n)=>void 0===e?{isValid:!0}:{isValid:e.length<=t,location:n},casing:(e,t,n)=>{if(void 0===e)return{isValid:!0};const o=r.isString(e)?[e]:e;for(let i of o){let o=!1;switch(t){case"camelCase":o=!!i.match(/^[a-z][a-zA-Z0-9]+$/g);break;case"kebab-case":o=!!i.match(/^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g);break;case"snake_case":o=!!i.match(/^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g);break;case"PascalCase":o=!!i.match(/^[A-Z][a-zA-Z0-9]+$/g);break;case"MACRO_CASE":o=!!i.match(/^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g);break;case"COBOL-CASE":o=!!i.match(/^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g);break;case"flatcase":o=!!i.match(/^[a-z][a-z0-9]+$/g)}if(!o)return{isValid:!1,location:r.isString(e)?n:n.child(i).key()}}return{isValid:!0}},sortOrder:(e,t,n)=>void 0===e?{isValid:!0}:{isValid:o.isOrdered(e,t),location:n},mutuallyExclusive:(e,t,n)=>({isValid:o.getIntersectionLength(e,t)<2,location:n.key()}),mutuallyRequired:(e,t,n)=>({isValid:!(o.getIntersectionLength(e,t)>0)||o.getIntersectionLength(e,t)===t.length,location:n.key()}),requireAny:(e,t,n)=>({isValid:o.getIntersectionLength(e,t)>=1,location:n.key()}),ref:(e,t,n,r)=>{if(void 0===r)return{isValid:!0};const i=r.hasOwnProperty("$ref");if("boolean"==typeof t)return{isValid:t?i:!i,location:i?n:n.key()};const a=o.regexFromString(t);return{isValid:i&&(null==a?void 0:a.test(r.$ref)),location:i?n:n.key()}}}},4015:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Assertions=void 0;const r=n(9740),o=n(5738);t.Assertions=e=>{let t=[];const n=Object.values(e).filter((e=>"object"==typeof e&&null!==e));for(const[e,i]of n.entries()){const n=i.assertionId&&`${i.assertionId} assertion`||`assertion #${e+1}`;if(!i.subject)throw new Error(`${n}: 'subject' is required`);const a=Array.isArray(i.subject)?i.subject:[i.subject],s=Object.keys(r.asserts).filter((e=>void 0!==i[e])).map((e=>({assertId:n,name:e,conditions:i[e],message:i.message,severity:i.severity||"error",suggest:i.suggest||[],runsOnKeys:r.runOnKeysSet.has(e),runsOnValues:r.runOnValuesSet.has(e)}))),l=s.find((e=>e.runsOnKeys&&!e.runsOnValues)),c=s.find((e=>e.runsOnValues&&!e.runsOnKeys));if(c&&!i.property)throw new Error(`${c.name} can't be used on all keys. Please provide a single property.`);if(l&&i.property)throw new Error(`${l.name} can't be used on a single property. Please use 'property'.`);for(const e of a){const n=o.buildSubjectVisitor(i.property,s,i.context),r=o.buildVisitorObject(e,i.context,n);t.push(r)}}return t}},5738:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.regexFromString=t.isOrdered=t.getIntersectionLength=t.buildSubjectVisitor=t.buildVisitorObject=void 0;const r=n(7468),o=n(9740);function i({values:e,rawValues:t,assert:n,location:r,report:i}){const a=o.asserts[n.name](e,n.conditions,r,t);a.isValid||i({message:n.message||`The ${n.assertId} doesn't meet required conditions`,location:a.location||r,forceSeverity:n.severity,suggest:n.suggest,ruleId:n.assertId})}t.buildVisitorObject=function(e,t,n){if(!t)return{[e]:n};let r={};const o=r;for(let n=0;ni?!i.includes(t):a?a.includes(t):void 0}:{},r=r[o.type]}return r[e]=n,o},t.buildSubjectVisitor=function(e,t,n){return(o,{report:a,location:s,rawLocation:l,key:c,type:u,resolve:p,rawNode:d})=>{var f;if(n){const e=n[n.length-1];if(e.type===u.name){const t=e.matchParentKeys,n=e.excludeParentKeys;if(t&&!t.includes(c))return;if(n&&n.includes(c))return}}e&&(e=Array.isArray(e)?e:[e]);for(const n of t){const t="ref"===n.name?l:s;if(e)for(const s of e)i({values:r.isRef(o[s])?null===(f=p(o[s]))||void 0===f?void 0:f.node:o[s],rawValues:d[s],assert:n,location:t.child(s),report:a});else{const e="ref"===n.name?d:Object.keys(o);i({values:Object.keys(o),rawValues:e,assert:n,location:t,report:a})}}}},t.getIntersectionLength=function(e,t){const n=new Set(t);let r=0;for(const t of e)n.has(t)&&r++;return r},t.isOrdered=function(e,t){const n=t.direction||t,r=t.property;for(let t=1;t=i:o<=i))return!1}return!0},t.regexFromString=function(e){const t=e.match(/^\/(.*)\/(.*)|(.*)/);return t&&new RegExp(t[1]||t[3],t[2])}},8265:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoContact=void 0;const r=n(780);t.InfoContact=()=>({Info(e,{report:t,location:n}){e.contact||t({message:r.missingRequiredField("Info","contact"),location:n.child("contact").key()})}})},8675:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoDescription=void 0;const r=n(780);t.InfoDescription=()=>({Info(e,t){r.validateDefinedAndNonEmpty("description",e,t)}})},9622:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoLicense=void 0;const r=n(780);t.InfoLicense=()=>({Info(e,{report:t}){e.license||t({message:r.missingRequiredField("Info","license"),location:{reportOnKey:!0}})}})},476:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoLicenseUrl=void 0;const r=n(780);t.InfoLicenseUrl=()=>({License(e,t){r.validateDefinedAndNonEmpty("url",e,t)}})},3467:function(e,t){"use strict";function n(e,t){const n=e.split("/"),r=t.split("/");if(n.length!==r.length)return!1;let o=0,i=0,a=!0;for(let e=0;e({PathMap(e,{report:t,location:r}){const o=[];for(const i of Object.keys(e)){const e=o.find((e=>n(e,i)));e&&t({message:`Paths should resolve unambiguously. Found two ambiguous paths: \`${e}\` and \`${i}\`.`,location:r.child([i]).key()}),o.push(i)}}})},2319:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoEnumTypeMismatch=void 0;const r=n(780);t.NoEnumTypeMismatch=()=>({Schema(e,{report:t,location:n}){if(!e.enum||Array.isArray(e.enum)){if(e.enum&&e.type&&!Array.isArray(e.type)){const o=e.enum.filter((t=>!r.matchesJsonSchemaType(t,e.type,e.nullable)));for(const i of o)t({message:`All values of \`enum\` field must be of the same type as the \`type\` field: expected "${e.type}" but received "${r.oasTypeOf(i)}".`,location:n.child(["enum",e.enum.indexOf(i)])})}if(e.enum&&e.type&&Array.isArray(e.type)){const o={};for(const t of e.enum){o[t]=[];for(const n of e.type)r.matchesJsonSchemaType(t,n,e.nullable)||o[t].push(n);o[t].length!==e.type.length&&delete o[t]}for(const r of Object.keys(o))t({message:`Enum value \`${r}\` must be of one type. Allowed types: \`${e.type}\`.`,location:n.child(["enum",e.enum.indexOf(r)])})}}}})},525:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoHttpVerbsInPaths=void 0;const r=n(771),o=["get","head","post","put","patch","delete","options","trace"];t.NoHttpVerbsInPaths=({splitIntoWords:e})=>({PathItem(t,{key:n,report:i,location:a}){const s=n.toString();if(!s.startsWith("/"))return;const l=s.split("/");for(const t of l){if(!t||r.isPathParameter(t))continue;const n=n=>e?r.splitCamelCaseIntoWords(t).has(n):t.toLocaleLowerCase().includes(n);for(const e of o)n(e)&&i({message:`path \`${s}\` should not contain http verb ${e}`,location:a.key()})}}})},4628:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoIdenticalPaths=void 0,t.NoIdenticalPaths=()=>({PathMap(e,{report:t,location:n}){const r=new Map;for(const o of Object.keys(e)){const e=o.replace(/{.+?}/g,"{VARIABLE}"),i=r.get(e);i?t({message:`The path already exists which differs only by path parameter name(s): \`${i}\` and \`${o}\`.`,location:n.child([o]).key()}):r.set(e,o)}}})},1562:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoInvalidParameterExamples=void 0;const r=n(780);t.NoInvalidParameterExamples=e=>{var t;const n=null===(t=e.disallowAdditionalProperties)||void 0===t||t;return{Parameter:{leave(e,t){if(e.example&&r.validateExample(e.example,e.schema,t.location.child("example"),t,n),e.examples)for(const[n,o]of Object.entries(e.examples))"value"in o&&r.validateExample(o.value,e.schema,t.location.child(["examples",n]),t,!1)}}}}},78:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoInvalidSchemaExamples=void 0;const r=n(780);t.NoInvalidSchemaExamples=e=>{var t;const n=null===(t=e.disallowAdditionalProperties)||void 0===t||t;return{Schema:{leave(e,t){if(e.examples)for(const o of e.examples)r.validateExample(o,e,t.location.child(["examples",e.examples.indexOf(o)]),t,n);e.example&&r.validateExample(e.example,e,t.location.child("example"),t,!1)}}}}},700:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoPathTrailingSlash=void 0,t.NoPathTrailingSlash=()=>({PathItem(e,{report:t,key:n,location:r}){n.endsWith("/")&&"/"!==n&&t({message:`\`${n}\` should not have a trailing slash.`,location:r.key()})}})},5946:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Operation2xxResponse=void 0,t.Operation2xxResponse=()=>({ResponsesMap(e,{report:t}){Object.keys(e).some((e=>"default"===e||/2[Xx0-9]{2}/.test(e)))||t({message:"Operation must have at least one `2xx` response.",location:{reportOnKey:!0}})}})},5281:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Operation4xxResponse=void 0,t.Operation4xxResponse=()=>({ResponsesMap(e,{report:t}){Object.keys(e).some((e=>/4[Xx0-9]{2}/.test(e)))||t({message:"Operation must have at least one `4xx` response.",location:{reportOnKey:!0}})}})},3408:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationDescription=void 0;const r=n(780);t.OperationDescription=()=>({Operation(e,t){r.validateDefinedAndNonEmpty("description",e,t)}})},8742:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationIdUnique=void 0,t.OperationIdUnique=()=>{const e=new Set;return{Operation(t,{report:n,location:r}){t.operationId&&(e.has(t.operationId)&&n({message:"Every operation must have a unique `operationId`.",location:r.child([t.operationId])}),e.add(t.operationId))}}}},5064:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationIdUrlSafe=void 0;const n=/^[A-Za-z0-9-._~:/?#\[\]@!\$&'()*+,;=]*$/;t.OperationIdUrlSafe=()=>({Operation(e,{report:t,location:r}){e.operationId&&!n.test(e.operationId)&&t({message:"Operation `operationId` should not have URL invalid characters.",location:r.child(["operationId"])})}})},8786:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationOperationId=void 0;const r=n(780);t.OperationOperationId=()=>({DefinitionRoot:{PathItem:{Operation(e,t){r.validateDefinedAndNonEmpty("operationId",e,t)}}}})},4112:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationParametersUnique=void 0,t.OperationParametersUnique=()=>{let e,t;return{PathItem:{enter(){e=new Set},Parameter(t,{report:n,key:r,parentLocations:o}){const i=`${t.in}___${t.name}`;e.has(i)&&n({message:`Paths must have unique \`name\` + \`in\` parameters.\nRepeats of \`in:${t.in}\` + \`name:${t.name}\`.`,location:o.PathItem.child(["parameters",r])}),e.add(`${t.in}___${t.name}`)},Operation:{enter(){t=new Set},Parameter(e,{report:n,key:r,parentLocations:o}){const i=`${e.in}___${e.name}`;t.has(i)&&n({message:`Operations must have unique \`name\` + \`in\` parameters. Repeats of \`in:${e.in}\` + \`name:${e.name}\`.`,location:o.Operation.child(["parameters",r])}),t.add(i)}}}}}},7892:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationSecurityDefined=void 0,t.OperationSecurityDefined=()=>{let e=new Map;return{DefinitionRoot:{leave(t,{report:n}){for(const[t,r]of e.entries())if(!r.defined)for(const e of r.from)n({message:`There is no \`${t}\` security scheme defined.`,location:e.key()})}},SecurityScheme(t,{key:n}){e.set(n.toString(),{defined:!0,from:[]})},SecurityRequirement(t,{location:n}){for(const r of Object.keys(t)){const t=e.get(r),o=n.child([r]);t?t.from.push(o):e.set(r,{from:[o]})}}}}},8613:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationSingularTag=void 0,t.OperationSingularTag=()=>({Operation(e,{report:t,location:n}){e.tags&&e.tags.length>1&&t({message:"Operation `tags` object should have only one tag.",location:n.child(["tags"]).key()})}})},9578:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationSummary=void 0;const r=n(780);t.OperationSummary=()=>({Operation(e,t){r.validateDefinedAndNonEmpty("summary",e,t)}})},5097:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationTagDefined=void 0,t.OperationTagDefined=()=>{let e;return{DefinitionRoot(t){var n;e=new Set((null!==(n=t.tags)&&void 0!==n?n:[]).map((e=>e.name)))},Operation(t,{report:n,location:r}){if(t.tags)for(let o=0;o({Parameter(e,{report:t,location:n}){void 0===e.description?t({message:"Parameter object description must be present.",location:{reportOnKey:!0}}):e.description||t({message:"Parameter object description must be non-empty string.",location:n.child(["description"])})}})},7890:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathDeclarationMustExist=void 0,t.PathDeclarationMustExist=()=>({PathItem(e,{report:t,key:n}){-1!==n.indexOf("{}")&&t({message:"Path parameter declarations must be non-empty. `{}` is invalid.",location:{reportOnKey:!0}})}})},3689:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathExcludesPatterns=void 0,t.PathExcludesPatterns=({patterns:e})=>({PathItem(t,{report:n,key:r,location:o}){if(!e)throw new Error('Parameter "patterns" is not provided for "path-excludes-patterns" rule');const i=r.toString();if(i.startsWith("/")){const t=e.filter((e=>i.match(e)));for(const e of t)n({message:`path \`${i}\` should not match regex pattern: \`${e}\``,location:o.key()})}}})},2332:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathHttpVerbsOrder=void 0;const n=["get","head","post","put","patch","delete","options","trace"];t.PathHttpVerbsOrder=e=>{const t=e&&e.order||n;if(!Array.isArray(t))throw new Error("path-http-verbs-order `order` option must be an array");return{PathItem(e,{report:n,location:r}){const o=Object.keys(e).filter((e=>t.includes(e)));for(let e=0;e({PathMap:{PathItem(e,{report:t,key:n}){n.toString().includes("?")&&t({message:"Don't put query string items in the path, they belong in parameters with `in: query`.",location:{reportOnKey:!0}})}}})},7421:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathParamsDefined=void 0;const n=/\{([a-zA-Z0-9_.-]+)\}+/g;t.PathParamsDefined=()=>{let e,t,r;return{PathItem:{enter(o,{key:i}){t=new Set,r=i,e=new Set(Array.from(i.toString().matchAll(n)).map((e=>e[1])))},Parameter(n,{report:o,location:i}){"path"===n.in&&n.name&&(t.add(n.name),e.has(n.name)||o({message:`Path parameter \`${n.name}\` is not used in the path \`${r}\`.`,location:i.child(["name"])}))},Operation:{leave(n,{report:o,location:i}){for(const n of Array.from(e.keys()))t.has(n)||o({message:`The operation does not define the path parameter \`{${n}}\` expected by path \`${r}\`.`,location:i.child(["parameters"]).key()})},Parameter(n,{report:o,location:i}){"path"===n.in&&n.name&&(t.add(n.name),e.has(n.name)||o({message:`Path parameter \`${n.name}\` is not used in the path \`${r}\`.`,location:i.child(["name"])}))}}}}}},3807:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathSegmentPlural=void 0;const r=n(771);t.PathSegmentPlural=e=>{const{ignoreLastPathSegment:t,exceptions:n}=e;return{PathItem:{leave(e,{report:o,key:i,location:a}){const s=i.toString();if(s.startsWith("/")){const e=s.split("/");e.shift(),t&&e.length>1&&e.pop();for(const t of e)n&&n.includes(t)||!r.isPathParameter(t)&&r.isSingular(t)&&o({message:`path segment \`${t}\` should be plural.`,location:a.key()})}}}}}},9527:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathsKebabCase=void 0,t.PathsKebabCase=()=>({PathItem(e,{report:t,key:n}){n.substr(1).split("/").filter((e=>""!==e)).every((e=>/^{.+}$/.test(e)||/^[a-z0-9-.]+$/.test(e)))||t({message:`\`${n}\` does not use kebab-case.`,location:{reportOnKey:!0}})}})},5839:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseContainsHeader=void 0;const r=n(771);t.ResponseContainsHeader=e=>{const t=e.names||{};return{Operation:{Response:{enter:(e,{report:n,location:o,key:i})=>{var a;const s=t[i]||t[r.getMatchingStatusCodeRange(i)]||t[r.getMatchingStatusCodeRange(i).toLowerCase()]||[];for(const t of s)(null===(a=e.headers)||void 0===a?void 0:a[t])||n({message:`Response object must contain a "${t}" header.`,location:o.child("headers").key()})}}}}}},5669:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ScalarPropertyMissingExample=void 0;const r=n(1510),o=["string","integer","number","boolean","null"];t.ScalarPropertyMissingExample=()=>({SchemaProperties(e,{report:t,location:n,oasVersion:i,resolve:a}){for(const l of Object.keys(e)){const c=a(e[l]).node;c&&((s=c).type&&!(s.allOf||s.anyOf||s.oneOf)&&"binary"!==s.format&&(Array.isArray(s.type)?s.type.every((e=>o.includes(e))):o.includes(s.type)))&&void 0===c.example&&void 0===c.examples&&t({message:`Scalar property should have "example"${i===r.OasVersion.Version3_1?' or "examples"':""} defined.`,location:n.child(l).key()})}var s}})},6471:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OasSpec=void 0;const r=n(5220),o=n(780),i=n(7468),a=n(771);t.OasSpec=()=>({any(e,{report:t,type:n,location:s,key:l,resolve:c,ignoreNextVisitorsOnNode:u}){var p,d,f,h;const m=o.oasTypeOf(e);if(n.items)return void("array"!==m&&(t({message:`Expected type \`${n.name}\` (array) but got \`${m}\``}),u()));if("object"!==m)return t({message:`Expected type \`${n.name}\` (object) but got \`${m}\``}),void u();const g="function"==typeof n.required?n.required(e,l):n.required;for(let n of g||[])e.hasOwnProperty(n)||t({message:`The field \`${n}\` must be present on this level.`,location:[{reportOnKey:!0}]});const y=null===(p=n.allowed)||void 0===p?void 0:p.call(n,e);if(y&&a.isPlainObject(e))for(const r in e)y.includes(r)||n.extensionsPrefix&&r.startsWith(n.extensionsPrefix)||!Object.keys(n.properties).includes(r)||t({message:`The field \`${r}\` is not allowed here.`,location:s.child([r]).key()});const v=n.requiredOneOf||null;if(v){let r=!1;for(let t of v||[])e.hasOwnProperty(t)&&(r=!0);r||t({message:`Must contain at least one of the following fields: ${null===(d=n.requiredOneOf)||void 0===d?void 0:d.join(", ")}.`,location:[{reportOnKey:!0}]})}for(const a of Object.keys(e)){const l=s.child([a]);let u=e[a],p=n.properties[a];if(void 0===p&&(p=n.additionalProperties),"function"==typeof p&&(p=p(u,a)),r.isNamedType(p))continue;const d=p,m=o.oasTypeOf(u);if(void 0!==d){if(null!==d){if(!1!==d.resolvable&&i.isRef(u)&&(u=c(u).node),d.enum)d.enum.includes(u)||t({location:l,message:`\`${a}\` can be one of the following only: ${d.enum.map((e=>`"${e}"`)).join(", ")}.`,suggest:o.getSuggest(u,d.enum)});else if(d.type&&!o.matchesJsonSchemaType(u,d.type,!1))t({message:`Expected type \`${d.type}\` but got \`${m}\`.`,location:l});else if("array"===m&&(null===(f=d.items)||void 0===f?void 0:f.type)){const e=null===(h=d.items)||void 0===h?void 0:h.type;for(let n=0;ne[a]&&t({message:`The value of the ${a} field must be greater than or equal to ${d.minimum}`,location:s.child([a])})}}else{if(a.startsWith("x-"))continue;t({message:`Property \`${a}\` is not expected here.`,suggest:o.getSuggest(a,Object.keys(n.properties)),location:l.key()})}}}})},7281:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TagDescription=void 0;const r=n(780);t.TagDescription=()=>({Tag(e,t){r.validateDefinedAndNonEmpty("description",e,t)}})},6855:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TagsAlphabetical=void 0,t.TagsAlphabetical=()=>({DefinitionRoot(e,{report:t,location:n}){if(e.tags)for(let r=0;re.tags[r+1].name&&t({message:"The `tags` array should be in alphabetical order.",location:n.child(["tags",r])})}})},348:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reportUnresolvedRef=t.NoUnresolvedRefs=void 0;const r=n(4182);function o(e,t,n){var o;const i=e.error;i instanceof r.YamlParseError&&t({message:"Failed to parse: "+i.message,location:{source:i.source,pointer:void 0,start:{col:i.col,line:i.line}}});const a=null===(o=e.error)||void 0===o?void 0:o.message;t({location:n,message:"Can't resolve $ref"+(a?": "+a:"")})}t.NoUnresolvedRefs=()=>({ref:{leave(e,{report:t,location:n},r){void 0===r.node&&o(r,t,n)}},DiscriminatorMapping(e,{report:t,resolve:n,location:r}){for(const i of Object.keys(e)){const a=n({$ref:e[i]});if(void 0!==a.node)return;o(a,t,r.child(i))}}}),t.reportUnresolvedRef=o},9566:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BooleanParameterPrefixes=void 0,t.BooleanParameterPrefixes=e=>{const t=e.prefixes||["is","has"],n=new RegExp(`^(${t.join("|")})[A-Z-_]`),r=t.map((e=>`\`${e}\``)),o=1===r.length?r[0]:r.slice(0,-1).join(", ")+" or "+r[t.length-1];return{Parameter(e,{report:t,location:r}){"boolean"!==e.type||n.test(e.name)||t({message:`Boolean parameter \`${e.name}\` should have ${o} prefix.`,location:r.child("name")})}}}},7523:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.preprocessors=t.rules=void 0;const r=n(6471),o=n(78),i=n(1562),a=n(8675),s=n(8265),l=n(9622),c=n(476),u=n(9566),p=n(7281),d=n(6855),f=n(9527),h=n(2319),m=n(700),g=n(5946),y=n(5281),v=n(4015),b=n(8742),w=n(4112),x=n(7421),k=n(5097),_=n(7890),O=n(5064),S=n(3408),E=n(5023),P=n(3529),A=n(8613),$=n(7892),C=n(348),R=n(2332),j=n(4628),T=n(8786),I=n(9578),N=n(3467),D=n(525),L=n(3689),M=n(7028),F=n(1750),z=n(3807),U=n(5839),V=n(7899),B=n(5669);t.rules={spec:r.OasSpec,"no-invalid-schema-examples":o.NoInvalidSchemaExamples,"no-invalid-parameter-examples":i.NoInvalidParameterExamples,"info-description":a.InfoDescription,"info-contact":s.InfoContact,"info-license":l.InfoLicense,"info-license-url":c.InfoLicenseUrl,"tag-description":p.TagDescription,"tags-alphabetical":d.TagsAlphabetical,"paths-kebab-case":f.PathsKebabCase,"no-enum-type-mismatch":h.NoEnumTypeMismatch,"boolean-parameter-prefixes":u.BooleanParameterPrefixes,"no-path-trailing-slash":m.NoPathTrailingSlash,"operation-2xx-response":g.Operation2xxResponse,"operation-4xx-response":y.Operation4xxResponse,assertions:v.Assertions,"operation-operationId-unique":b.OperationIdUnique,"operation-parameters-unique":w.OperationParametersUnique,"path-parameters-defined":x.PathParamsDefined,"operation-tag-defined":k.OperationTagDefined,"path-declaration-must-exist":_.PathDeclarationMustExist,"operation-operationId-url-safe":O.OperationIdUrlSafe,"operation-operationId":T.OperationOperationId,"operation-summary":I.OperationSummary,"operation-description":S.OperationDescription,"path-not-include-query":E.PathNotIncludeQuery,"path-params-defined":x.PathParamsDefined,"parameter-description":P.ParameterDescription,"operation-singular-tag":A.OperationSingularTag,"operation-security-defined":$.OperationSecurityDefined,"no-unresolved-refs":C.NoUnresolvedRefs,"no-identical-paths":j.NoIdenticalPaths,"no-ambiguous-paths":N.NoAmbiguousPaths,"path-http-verbs-order":R.PathHttpVerbsOrder,"no-http-verbs-in-paths":D.NoHttpVerbsInPaths,"path-excludes-patterns":L.PathExcludesPatterns,"request-mime-type":M.RequestMimeType,"response-mime-type":F.ResponseMimeType,"path-segment-plural":z.PathSegmentPlural,"response-contains-header":U.ResponseContainsHeader,"response-contains-property":V.ResponseContainsProperty,"scalar-property-missing-example":B.ScalarPropertyMissingExample},t.preprocessors={}},4508:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(771);t.RemoveUnusedComponents=()=>{let e=new Map;function t(t,n,r){var o;e.set(t.absolutePointer,{used:(null===(o=e.get(t.absolutePointer))||void 0===o?void 0:o.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:o}){if(["Schema","Parameter","Response","SecurityScheme"].includes(n.name)){const n=r(t);if(!n.location)return;e.set(n.location.absolutePointer,{used:!0,name:o.toString()})}}},DefinitionRoot:{leave(t,n){const o=n.getVisitorData();o.removedCount=0;let i=new Set;e.forEach((e=>{const{used:n,name:r,componentType:a}=e;!n&&a&&(i.add(a),delete t[a][r],o.removedCount++)}));for(const e of i)r.isEmptyObject(t[e])&&delete t[e]}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"definitions",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedSecuritySchemes:{SecurityScheme(e,{location:n,key:r}){t(n,"securityDefinitions",r.toString())}}}}},7028:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RequestMimeType=void 0;const r=n(771);t.RequestMimeType=({allowedValues:e})=>({DefinitionRoot(t,n){r.validateMimeType({type:"consumes",value:t},n,e)},Operation:{leave(t,n){r.validateMimeType({type:"consumes",value:t},n,e)}}})},7899:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseContainsProperty=void 0;const r=n(771);t.ResponseContainsProperty=e=>{const t=e.names||{};let n;return{Operation:{Response:{skip:(e,t)=>"204"==`${t}`,enter:(e,t)=>{n=t.key},Schema(e,{report:o,location:i}){var a;if("object"!==e.type)return;const s=t[n]||t[r.getMatchingStatusCodeRange(n)]||t[r.getMatchingStatusCodeRange(n).toLowerCase()]||[];for(const t of s)(null===(a=e.properties)||void 0===a?void 0:a[t])||o({message:`Response object must contain a top-level "${t}" property.`,location:i.child("properties").key()})}}}}}},1750:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseMimeType=void 0;const r=n(771);t.ResponseMimeType=({allowedValues:e})=>({DefinitionRoot(t,n){r.validateMimeType({type:"produces",value:t},n,e)},Operation:{leave(t,n){r.validateMimeType({type:"produces",value:t},n,e)}}})},962:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BooleanParameterPrefixes=void 0,t.BooleanParameterPrefixes=e=>{const t=e.prefixes||["is","has"],n=new RegExp(`^(${t.join("|")})[A-Z-_]`),r=t.map((e=>`\`${e}\``)),o=1===r.length?r[0]:r.slice(0,-1).join(", ")+" or "+r[t.length-1];return{Parameter:{Schema(e,{report:t,parentLocations:r},i){"boolean"!==e.type||n.test(i.Parameter.name)||t({message:`Boolean parameter \`${i.Parameter.name}\` should have ${o} prefix.`,location:r.Parameter.child(["name"])})}}}}},226:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.preprocessors=t.rules=void 0;const r=n(6471),o=n(5946),i=n(5281),a=n(4015),s=n(8742),l=n(4112),c=n(7421),u=n(5097),p=n(1265),d=n(2319),f=n(700),h=n(7890),m=n(5064),g=n(6855),y=n(5486),v=n(2947),b=n(8675),w=n(7281),x=n(8265),k=n(9622),_=n(3408),O=n(897),S=n(5023),E=n(3529),P=n(8613),A=n(476),$=n(7892),C=n(348),R=n(962),j=n(9527),T=n(2332),I=n(7020),N=n(9336),D=n(4628),L=n(6208),M=n(8786),F=n(9578),z=n(3467),U=n(472),V=n(525),B=n(3736),q=n(503),W=n(3807),H=n(3689),Y=n(78),K=n(1562),G=n(5839),Q=n(7557),X=n(5669);t.rules={spec:r.OasSpec,"info-description":b.InfoDescription,"info-contact":x.InfoContact,"info-license":k.InfoLicense,"info-license-url":A.InfoLicenseUrl,"operation-2xx-response":o.Operation2xxResponse,"operation-4xx-response":i.Operation4xxResponse,assertions:a.Assertions,"operation-operationId-unique":s.OperationIdUnique,"operation-parameters-unique":l.OperationParametersUnique,"path-parameters-defined":c.PathParamsDefined,"operation-tag-defined":u.OperationTagDefined,"no-example-value-and-externalValue":p.NoExampleValueAndExternalValue,"no-enum-type-mismatch":d.NoEnumTypeMismatch,"no-path-trailing-slash":f.NoPathTrailingSlash,"no-empty-servers":I.NoEmptyServers,"path-declaration-must-exist":h.PathDeclarationMustExist,"operation-operationId-url-safe":m.OperationIdUrlSafe,"operation-operationId":M.OperationOperationId,"operation-summary":F.OperationSummary,"tags-alphabetical":g.TagsAlphabetical,"no-server-example.com":y.NoServerExample,"no-server-trailing-slash":v.NoServerTrailingSlash,"tag-description":w.TagDescription,"operation-description":_.OperationDescription,"no-unused-components":O.NoUnusedComponents,"path-not-include-query":S.PathNotIncludeQuery,"path-params-defined":c.PathParamsDefined,"parameter-description":E.ParameterDescription,"operation-singular-tag":P.OperationSingularTag,"operation-security-defined":$.OperationSecurityDefined,"no-unresolved-refs":C.NoUnresolvedRefs,"paths-kebab-case":j.PathsKebabCase,"boolean-parameter-prefixes":R.BooleanParameterPrefixes,"path-http-verbs-order":T.PathHttpVerbsOrder,"no-invalid-media-type-examples":N.ValidContentExamples,"no-identical-paths":D.NoIdenticalPaths,"no-ambiguous-paths":z.NoAmbiguousPaths,"no-undefined-server-variable":L.NoUndefinedServerVariable,"no-servers-empty-enum":U.NoEmptyEnumServers,"no-http-verbs-in-paths":V.NoHttpVerbsInPaths,"path-excludes-patterns":H.PathExcludesPatterns,"request-mime-type":B.RequestMimeType,"response-mime-type":q.ResponseMimeType,"path-segment-plural":W.PathSegmentPlural,"no-invalid-schema-examples":Y.NoInvalidSchemaExamples,"no-invalid-parameter-examples":K.NoInvalidParameterExamples,"response-contains-header":G.ResponseContainsHeader,"response-contains-property":Q.ResponseContainsProperty,"scalar-property-missing-example":X.ScalarPropertyMissingExample},t.preprocessors={}},7020:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoEmptyServers=void 0,t.NoEmptyServers=()=>({DefinitionRoot(e,{report:t,location:n}){e.hasOwnProperty("servers")?Array.isArray(e.servers)&&0!==e.servers.length||t({message:"Servers must be a non-empty array.",location:n.child(["servers"]).key()}):t({message:"Servers must be present.",location:n.child(["openapi"]).key()})}})},1265:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoExampleValueAndExternalValue=void 0,t.NoExampleValueAndExternalValue=()=>({Example(e,{report:t,location:n}){e.value&&e.externalValue&&t({message:"Example object can have either `value` or `externalValue` fields.",location:n.child(["value"]).key()})}})},9336:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ValidContentExamples=void 0;const r=n(7468),o=n(780);t.ValidContentExamples=e=>{var t;const n=null===(t=e.disallowAdditionalProperties)||void 0===t||t;return{MediaType:{leave(e,t){const{location:i,resolve:a}=t;if(e.schema)if(e.example)s(e.example,i.child("example"));else if(e.examples)for(const t of Object.keys(e.examples))s(e.examples[t],i.child(["examples",t,"value"]),!0);function s(i,s,l){if(r.isRef(i)){const e=a(i);if(!e.location)return;s=l?e.location.child("value"):e.location,i=e.node}o.validateExample(l?i.value:i,e.schema,s,t,n)}}}}}},5486:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoServerExample=void 0,t.NoServerExample=()=>({Server(e,{report:t,location:n}){-1!==["example.com","localhost"].indexOf(e.url)&&t({message:"Server `url` should not point at example.com.",location:n.child(["url"])})}})},2947:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoServerTrailingSlash=void 0,t.NoServerTrailingSlash=()=>({Server(e,{report:t,location:n}){e.url&&e.url.endsWith("/")&&"/"!==e.url&&t({message:"Server `url` should not have a trailing slash.",location:n.child(["url"])})}})},472:function(e,t){"use strict";var n;function r(e){var t;if(e.variables&&0===Object.keys(e.variables).length)return;const r=[];for(var o in e.variables){const i=e.variables[o];if(!i.enum)continue;if(Array.isArray(i.enum)&&0===(null===(t=i.enum)||void 0===t?void 0:t.length)&&r.push(n.empty),!i.default)continue;const a=e.variables[o].default;i.enum&&!i.enum.includes(a)&&r.push(n.invalidDefaultValue)}return r.length?r:void 0}Object.defineProperty(t,"__esModule",{value:!0}),t.NoEmptyEnumServers=void 0,function(e){e.empty="empty",e.invalidDefaultValue="invalidDefaultValue"}(n||(n={})),t.NoEmptyEnumServers=()=>({DefinitionRoot(e,{report:t,location:o}){if(!e.servers||0===e.servers.length)return;const i=[];if(Array.isArray(e.servers))for(const t of e.servers){const e=r(t);e&&i.push(...e)}else{const t=r(e.servers);if(!t)return;i.push(...t)}for(const e of i)e===n.empty&&t({message:"Server variable with `enum` must be a non-empty array.",location:o.child(["servers"]).key()}),e===n.invalidDefaultValue&&t({message:"Server variable define `enum` and `default`. `enum` must include default value",location:o.child(["servers"]).key()})}})},6208:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoUndefinedServerVariable=void 0,t.NoUndefinedServerVariable=()=>({Server(e,{report:t,location:n}){var r;if(!e.url)return;const o=(null===(r=e.url.match(/{[^}]+}/g))||void 0===r?void 0:r.map((e=>e.slice(1,e.length-1))))||[],i=(null==e?void 0:e.variables)&&Object.keys(e.variables)||[];for(const e of o)i.includes(e)||t({message:`The \`${e}\` variable is not defined in the \`variables\` objects.`,location:n.child(["url"])});for(const e of i)o.includes(e)||t({message:`The \`${e}\` variable is not used in the server's \`url\` field.`,location:n.child(["variables",e]).key(),from:n.child("url")})}})},897:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoUnusedComponents=void 0,t.NoUnusedComponents=()=>{let e=new Map;function t(t,n){var r;e.set(t.absolutePointer,{used:(null===(r=e.get(t.absolutePointer))||void 0===r?void 0:r.used)||!1,location:t,name:n})}return{ref(t,{type:n,resolve:r,key:o,location:i}){if(["Schema","Header","Parameter","Response","Example","RequestBody"].includes(n.name)){const n=r(t);if(!n.location)return;e.set(n.location.absolutePointer,{used:!0,name:o.toString(),location:i})}},DefinitionRoot:{leave(t,{report:n}){e.forEach((e=>{e.used||n({message:`Component: "${e.name}" is never used.`,location:e.location.key()})}))}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,r.toString())}},NamedExamples:{Example(e,{location:n,key:r}){t(n,r.toString())}},NamedRequestBodies:{RequestBody(e,{location:n,key:r}){t(n,r.toString())}},NamedHeaders:{Header(e,{location:n,key:r}){t(n,r.toString())}}}}},6350:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(771);t.RemoveUnusedComponents=()=>{let e=new Map;function t(t,n,r){var o;e.set(t.absolutePointer,{used:(null===(o=e.get(t.absolutePointer))||void 0===o?void 0:o.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:o}){if(["Schema","Header","Parameter","Response","Example","RequestBody"].includes(n.name)){const n=r(t);if(!n.location)return;e.set(n.location.absolutePointer,{used:!0,name:o.toString()})}}},DefinitionRoot:{leave(t,n){const o=n.getVisitorData();o.removedCount=0,e.forEach((e=>{const{used:n,componentType:i,name:a}=e;if(!n&&i){let e=t.components[i];delete e[a],o.removedCount++,r.isEmptyObject(e)&&delete t.components[i]}})),r.isEmptyObject(t.components)&&delete t.components}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"schemas",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedExamples:{Example(e,{location:n,key:r}){t(n,"examples",r.toString())}},NamedRequestBodies:{RequestBody(e,{location:n,key:r}){t(n,"requestBodies",r.toString())}},NamedHeaders:{Header(e,{location:n,key:r}){t(n,"headers",r.toString())}}}}},3736:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RequestMimeType=void 0;const r=n(771);t.RequestMimeType=({allowedValues:e})=>({PathMap:{RequestBody:{leave(t,n){r.validateMimeTypeOAS3({type:"consumes",value:t},n,e)}},Callback:{RequestBody(){},Response:{leave(t,n){r.validateMimeTypeOAS3({type:"consumes",value:t},n,e)}}}},WebhooksMap:{Response:{leave(t,n){r.validateMimeTypeOAS3({type:"consumes",value:t},n,e)}}}})},7557:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseContainsProperty=void 0;const r=n(771);t.ResponseContainsProperty=e=>{const t=e.names||{};let n;return{Operation:{Response:{skip:(e,t)=>"204"==`${t}`,enter:(e,t)=>{n=t.key},MediaType:{Schema(e,{report:o,location:i}){var a;if("object"!==e.type)return;const s=t[n]||t[r.getMatchingStatusCodeRange(n)]||t[r.getMatchingStatusCodeRange(n).toLowerCase()]||[];for(const t of s)(null===(a=e.properties)||void 0===a?void 0:a[t])||o({message:`Response object must contain a top-level "${t}" property.`,location:i.child("properties").key()})}}}}}}},503:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseMimeType=void 0;const r=n(771);t.ResponseMimeType=({allowedValues:e})=>({PathMap:{Response:{leave(t,n){r.validateMimeTypeOAS3({type:"produces",value:t},n,e)}},Callback:{Response(){},RequestBody:{leave(t,n){r.validateMimeTypeOAS3({type:"produces",value:t},n,e)}}}},WebhooksMap:{RequestBody:{leave(t,n){r.validateMimeTypeOAS3({type:"produces",value:t},n,e)}}}})},780:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateExample=t.getSuggest=t.validateDefinedAndNonEmpty=t.fieldNonEmpty=t.missingRequiredField=t.matchesJsonSchemaType=t.oasTypeOf=void 0;const r=n(9991),o=n(7468),i=n(7275);function a(e,t){return`${e} object should contain \`${t}\` field.`}function s(e,t){return`${e} object \`${t}\` must be non-empty string.`}t.oasTypeOf=function(e){return Array.isArray(e)?"array":null===e?"null":typeof e},t.matchesJsonSchemaType=function(e,t,n){if(n&&null===e)return null===e;switch(t){case"array":return Array.isArray(e);case"object":return"object"==typeof e&&null!==e&&!Array.isArray(e);case"null":return null===e;case"integer":return Number.isInteger(e);default:return typeof e===t}},t.missingRequiredField=a,t.fieldNonEmpty=s,t.validateDefinedAndNonEmpty=function(e,t,n){"object"==typeof t&&(void 0===t[e]?n.report({message:a(n.type.name,e),location:n.location.child([e]).key()}):t[e]||n.report({message:s(n.type.name,e),location:n.location.child([e]).key()}))},t.getSuggest=function(e,t){if("string"!=typeof e||!t.length)return[];const n=[];for(let o=0;oe.distance-t.distance)),n.map((e=>e.variant))},t.validateExample=function(e,t,n,{resolve:r,location:a,report:s},l){try{const{valid:c,errors:u}=i.validateJsonSchema(e,t,a.child("schema"),n.pointer,r,l);if(!c)for(let e of u)s({message:`Example value must conform to the schema: ${e.message}.`,location:Object.assign(Object.assign({},new o.Location(n.source,e.instancePath)),{reportOnKey:"additionalProperties"===e.keyword}),from:a,suggest:e.suggest})}catch(e){s({message:`Example validation errored: ${e.message}.`,location:a.child("schema"),from:a})}}},5220:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isNamedType=t.normalizeTypes=t.mapOf=t.listOf=void 0,t.listOf=function(e){return{name:`${e}List`,properties:{},items:e}},t.mapOf=function(e){return{name:`${e}Map`,properties:{},additionalProperties:()=>e}},t.normalizeTypes=function(e,t={}){const n={};for(const t of Object.keys(e))n[t]=Object.assign(Object.assign({},e[t]),{name:t});for(const e of Object.values(n))r(e);return n;function r(e){if(e.additionalProperties&&(e.additionalProperties=o(e.additionalProperties)),e.items&&(e.items=o(e.items)),e.properties){const n={};for(const[r,i]of Object.entries(e.properties))n[r]=o(i),t.doNotResolveExamples&&i&&i.isExample&&(n[r]=Object.assign(Object.assign({},i),{resolvable:!1}));e.properties=n}}function o(e){if("string"==typeof e){if(!n[e])throw new Error(`Unknown type name found: ${e}`);return n[e]}return"function"==typeof e?(t,n)=>o(e(t,n)):e&&e.name?(r(e=Object.assign({},e)),e):e&&e.directResolveAs?Object.assign(Object.assign({},e),{directResolveAs:o(e.directResolveAs)}):e}},t.isNamedType=function(e){return"string"==typeof(null==e?void 0:e.name)}},388:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas2Types=void 0;const r=n(5220),o=/^[0-9][0-9Xx]{2}$/,i={properties:{swagger:{type:"string"},info:"Info",host:{type:"string"},basePath:{type:"string"},schemes:{type:"array",items:{type:"string"}},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},paths:"PathMap",definitions:"NamedSchemas",parameters:"NamedParameters",responses:"NamedResponses",securityDefinitions:"NamedSecuritySchemes",security:r.listOf("SecurityRequirement"),tags:r.listOf("Tag"),externalDocs:"ExternalDocs"},required:["swagger","paths","info"]},a={properties:{$ref:{type:"string"},parameters:r.listOf("Parameter"),get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation"}},s={properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},parameters:r.listOf("Parameter"),responses:"ResponsesMap",schemes:{type:"array",items:{type:"string"}},deprecated:{type:"boolean"},security:r.listOf("SecurityRequirement"),"x-codeSamples":r.listOf("XCodeSample"),"x-code-samples":r.listOf("XCodeSample"),"x-hideTryItPanel":{type:"boolean"}},required:["responses"]},l={properties:{default:"Response"},additionalProperties:(e,t)=>o.test(t)?"Response":void 0},c={properties:{description:{type:"string"},schema:"Schema",headers:r.mapOf("Header"),examples:"Examples"},required:["description"]},u={properties:{format:{type:"string"},title:{type:"string"},description:{type:"string"},default:null,multipleOf:{type:"number"},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"number"},minLength:{type:"number"},pattern:{type:"string"},maxItems:{type:"number"},minItems:{type:"number"},uniqueItems:{type:"boolean"},maxProperties:{type:"number"},minProperties:{type:"number"},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{type:"string",enum:["object","array","string","number","integer","boolean","null"]},items:e=>Array.isArray(e)?r.listOf("Schema"):"Schema",allOf:r.listOf("Schema"),properties:"SchemaProperties",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",discriminator:{type:"string"},readOnly:{type:"boolean"},xml:"Xml",externalDocs:"ExternalDocs",example:{isExample:!0},"x-tags":{type:"array",items:{type:"string"}}}},p={properties:{type:{enum:["basic","apiKey","oauth2"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header"]},flow:{enum:["implicit","password","application","accessCode"]},authorizationUrl:{type:"string"},tokenUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","scopes"];case"application":case"password":return["type","flow","tokenUrl","scopes"];default:return["type","flow","scopes"]}default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"basic":return["type","description"];case"apiKey":return["type","name","in","description"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","description","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","description","scopes"];case"application":case"password":return["type","flow","tokenUrl","description","scopes"];default:return["type","flow","tokenUrl","authorizationUrl","description","scopes"]}default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas2Types={DefinitionRoot:i,Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs"},required:["name"]},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"]},SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},Info:{properties:{title:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License",version:{type:"string"}},required:["title","version"]},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}}},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"]},PathMap:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:a,Parameter:{properties:{name:{type:"string"},in:{type:"string",enum:["query","header","path","formData","body"]},description:{type:"string"},required:{type:"boolean"},schema:"Schema",type:{type:"string",enum:["string","number","integer","boolean","array","file"]},format:{type:"string"},allowEmptyValue:{type:"boolean"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required:e=>e&&e.in?"body"===e.in?["name","in","schema"]:"array"===e.type?["name","in","type","items"]:["name","in","type"]:["name","in"]},ParameterItems:{properties:{type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required:e=>e&&"array"===e.type?["type","items"]:["type"]},Operation:s,Examples:{properties:{},additionalProperties:{isExample:!0}},Header:{properties:{description:{type:"string"},type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required:e=>e&&"array"===e.type?["type","items"]:["type"]},ResponsesMap:l,Response:c,Schema:u,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}}},SchemaProperties:{properties:{},additionalProperties:"Schema"},NamedSchemas:r.mapOf("Schema"),NamedResponses:r.mapOf("Response"),NamedParameters:r.mapOf("Parameter"),NamedSecuritySchemes:r.mapOf("SecurityScheme"),SecurityScheme:p,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}}}},5241:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3Types=void 0;const r=n(5220),o=n(7468),i=/^[0-9][0-9Xx]{2}$/,a={properties:{openapi:null,info:"Info",servers:r.listOf("Server"),security:r.listOf("SecurityRequirement"),tags:r.listOf("Tag"),externalDocs:"ExternalDocs",paths:"PathMap",components:"Components","x-webhooks":"WebhooksMap"},required:["openapi","paths","info"]},s={properties:{url:{type:"string"},description:{type:"string"},variables:r.mapOf("ServerVariable")},required:["url"]},l={properties:{$ref:{type:"string"},servers:r.listOf("Server"),parameters:r.listOf("Parameter"),summary:{type:"string"},description:{type:"string"},get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation",trace:"Operation"}},c={properties:{name:{type:"string"},in:{enum:["query","header","path","cookie"]},description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:r.mapOf("Example"),content:"MediaTypeMap"},required:["name","in"],requiredOneOf:["schema","content"]},u={properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:r.listOf("Parameter"),security:r.listOf("SecurityRequirement"),servers:r.listOf("Server"),requestBody:"RequestBody",responses:"ResponsesMap",deprecated:{type:"boolean"},callbacks:r.mapOf("Callback"),"x-codeSamples":r.listOf("XCodeSample"),"x-code-samples":r.listOf("XCodeSample"),"x-hideTryItPanel":{type:"boolean"}},required:["responses"]},p={properties:{schema:"Schema",example:{isExample:!0},examples:r.mapOf("Example"),encoding:r.mapOf("Encoding")}},d={properties:{contentType:{type:"string"},headers:r.mapOf("Header"),style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"}}},f={properties:{description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:r.mapOf("Example"),content:"MediaTypeMap"}},h={properties:{default:"Response"},additionalProperties:(e,t)=>i.test(t)?"Response":void 0},m={properties:{description:{type:"string"},headers:r.mapOf("Header"),content:"MediaTypeMap",links:r.mapOf("Link")},required:["description"]},g={properties:{externalDocs:"ExternalDocs",discriminator:"Discriminator",title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{enum:["object","array","string","number","integer","boolean","null"]},allOf:r.listOf("Schema"),anyOf:r.listOf("Schema"),oneOf:r.listOf("Schema"),not:"Schema",properties:"SchemaProperties",items:e=>Array.isArray(e)?r.listOf("Schema"):"Schema",additionalItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},default:null,nullable:{type:"boolean"},readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",example:{isExample:!0},deprecated:{type:"boolean"},"x-tags":{type:"array",items:{type:"string"}}}},y={properties:{},additionalProperties:e=>o.isMappingRef(e)?{type:"string",directResolveAs:"Schema"}:{type:"string"}},v={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"SecuritySchemeFlows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":return["type","flows","description"];case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3Types={DefinitionRoot:a,Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs"},required:["name"]},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"]},Server:s,ServerVariable:{properties:{enum:{type:"array",items:{type:"string"}},default:{type:"string"},description:null},required:["default"]},SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License"},required:["title","version"]},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}}},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"]},PathMap:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:l,Parameter:c,Operation:u,Callback:r.mapOf("PathItem"),RequestBody:{properties:{description:{type:"string"},required:{type:"boolean"},content:"MediaTypeMap"},required:["content"]},MediaTypeMap:{properties:{},additionalProperties:"MediaType"},MediaType:p,Example:{properties:{value:{isExample:!0},summary:{type:"string"},description:{type:"string"},externalValue:{type:"string"}}},Encoding:d,Header:f,ResponsesMap:h,Response:m,Link:{properties:{operationRef:{type:"string"},operationId:{type:"string"},parameters:null,requestBody:null,description:{type:"string"},server:"Server"}},Schema:g,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}}},SchemaProperties:{properties:{},additionalProperties:"Schema"},DiscriminatorMapping:y,Discriminator:{properties:{propertyName:{type:"string"},mapping:"DiscriminatorMapping"},required:["propertyName"]},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks"}},NamedSchemas:r.mapOf("Schema"),NamedResponses:r.mapOf("Response"),NamedParameters:r.mapOf("Parameter"),NamedExamples:r.mapOf("Example"),NamedRequestBodies:r.mapOf("RequestBody"),NamedHeaders:r.mapOf("Header"),NamedSecuritySchemes:r.mapOf("SecurityScheme"),NamedLinks:r.mapOf("Link"),NamedCallbacks:r.mapOf("Callback"),ImplicitFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},authorizationUrl:{type:"string"}},required:["authorizationUrl","scopes"]},PasswordFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},ClientCredentials:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},AuthorizationCode:{properties:{refreshUrl:{type:"string"},authorizationUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["authorizationUrl","tokenUrl","scopes"]},SecuritySchemeFlows:{properties:{implicit:"ImplicitFlow",password:"PasswordFlow",clientCredentials:"ClientCredentials",authorizationCode:"AuthorizationCode"}},SecurityScheme:v,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}},WebhooksMap:{properties:{},additionalProperties:()=>"PathItem"}}},2608:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3_1Types=void 0;const r=n(5220),o=n(5241),i={properties:{openapi:null,info:"Info",servers:r.listOf("Server"),security:r.listOf("SecurityRequirement"),tags:r.listOf("Tag"),externalDocs:"ExternalDocs",paths:"PathMap",webhooks:"WebhooksMap",components:"Components",jsonSchemaDialect:{type:"string"}},required:["openapi","info"],requiredOneOf:["paths","components","webhooks"]},a={properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:r.listOf("Parameter"),security:r.listOf("SecurityRequirement"),servers:r.listOf("Server"),requestBody:"RequestBody",responses:"ResponsesMap",deprecated:{type:"boolean"},callbacks:r.mapOf("Callback"),"x-codeSamples":r.listOf("XCodeSample"),"x-code-samples":r.listOf("XCodeSample"),"x-hideTryItPanel":{type:"boolean"}}},s={properties:{$id:{type:"string"},id:{type:"string"},$schema:{type:"string"},definitions:"NamedSchemas",$defs:"NamedSchemas",$vocabulary:{type:"string"},externalDocs:"ExternalDocs",discriminator:"Discriminator",myArbitraryKeyword:{type:"boolean"},title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"number"},exclusiveMinimum:{type:"number"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:e=>Array.isArray(e)?{type:"array",items:{enum:["object","array","string","number","integer","boolean","null"]}}:{enum:["object","array","string","number","integer","boolean","null"]},allOf:r.listOf("Schema"),anyOf:r.listOf("Schema"),oneOf:r.listOf("Schema"),not:"Schema",if:"Schema",then:"Schema",else:"Schema",dependentSchemas:r.listOf("Schema"),prefixItems:r.listOf("Schema"),contains:"Schema",minContains:{type:"integer",minimum:0},maxContains:{type:"integer",minimum:0},patternProperties:{type:"object"},propertyNames:"Schema",unevaluatedItems:"Schema",unevaluatedProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",summary:{type:"string"},properties:"SchemaProperties",items:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},contentEncoding:{type:"string"},contentMediaType:{type:"string"},default:null,readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",examples:{type:"array"},example:{isExample:!0},deprecated:{type:"boolean"},const:null,$comment:{type:"string"},"x-tags":{type:"array",items:{type:"string"}}}},l={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect","mutualTLS"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"SecuritySchemeFlows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":switch(null==e?void 0:e.flows){case"implicit":return["type","flows","authorizationUrl","refreshUrl","description","scopes"];case"password":case"clientCredentials":return["type","flows","tokenUrl","refreshUrl","description","scopes"];default:return["type","flows","authorizationUrl","refreshUrl","tokenUrl","description","scopes"]}case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3_1Types=Object.assign(Object.assign({},o.Oas3Types),{Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},summary:{type:"string"},contact:"Contact",license:"License"},required:["title","version"]},DefinitionRoot:i,Schema:s,License:{properties:{name:{type:"string"},url:{type:"string"},identifier:{type:"string"}},required:["name"]},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks",pathItems:"NamedPathItems"}},NamedPathItems:r.mapOf("PathItem"),SecurityScheme:l,Operation:a})},771:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.isCustomRuleId=t.getMatchingStatusCodeRange=t.assignExisting=t.isNotString=t.isString=t.isNotEmptyObject=t.slash=t.isPathParameter=t.readFileAsStringSync=t.isSingular=t.validateMimeTypeOAS3=t.validateMimeType=t.splitCamelCaseIntoWords=t.omitObjectProps=t.pickObjectProps=t.readFileFromUrl=t.isEmptyArray=t.isEmptyObject=t.isPlainObject=t.notUndefined=t.loadYaml=t.popStack=t.pushStack=t.stringifyYaml=t.parseYaml=void 0;const o=n(3197),i=n(4099),a=n(8150),s=n(3450),l=n(5273),c=n(8698);var u=n(5273);function p(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function d(e,t){return t.match(/^https?:\/\//)||(e=e.replace(/^https?:\/\//,"")),i(e,t)}function f(e){return"string"==typeof e}Object.defineProperty(t,"parseYaml",{enumerable:!0,get:function(){return u.parseYaml}}),Object.defineProperty(t,"stringifyYaml",{enumerable:!0,get:function(){return u.stringifyYaml}}),t.pushStack=function(e,t){return{prev:e,value:t}},t.popStack=function(e){var t;return null!==(t=null==e?void 0:e.prev)&&void 0!==t?t:null},t.loadYaml=function(e){return r(this,void 0,void 0,(function*(){const t=yield o.promises.readFile(e,"utf-8");return l.parseYaml(t)}))},t.notUndefined=function(e){return void 0!==e},t.isPlainObject=p,t.isEmptyObject=function(e){return p(e)&&0===Object.keys(e).length},t.isEmptyArray=function(e){return Array.isArray(e)&&0===e.length},t.readFileFromUrl=function(e,t){return r(this,void 0,void 0,(function*(){const n={};for(const r of t.headers)d(e,r.matches)&&(n[r.name]=void 0!==r.envVariable?c.env[r.envVariable]||"":r.value);const r=yield(t.customFetch||a.default)(e,{headers:n});if(!r.ok)throw new Error(`Failed to load ${e}: ${r.status} ${r.statusText}`);return{body:yield r.text(),mimeType:r.headers.get("content-type")}}))},t.pickObjectProps=function(e,t){return Object.fromEntries(t.filter((t=>t in e)).map((t=>[t,e[t]])))},t.omitObjectProps=function(e,t){return Object.fromEntries(Object.entries(e).filter((([e])=>!t.includes(e))))},t.splitCamelCaseIntoWords=function(e){const t=e.split(/(?:[-._])|([A-Z][a-z]+)/).filter(Boolean).map((e=>e.toLocaleLowerCase())),n=e.split(/([A-Z]{2,})/).filter((e=>e&&e===e.toUpperCase())).map((e=>e.toLocaleLowerCase()));return new Set([...t,...n])},t.validateMimeType=function({type:e,value:t},{report:n,location:r},o){if(!o)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t[e])for(const i of t[e])o.includes(i)||n({message:`Mime type "${i}" is not allowed`,location:r.child(t[e].indexOf(i)).key()})},t.validateMimeTypeOAS3=function({type:e,value:t},{report:n,location:r},o){if(!o)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t.content)for(const e of Object.keys(t.content))o.includes(e)||n({message:`Mime type "${e}" is not allowed`,location:r.child("content").child(e).key()})},t.isSingular=function(e){return s.isSingular(e)},t.readFileAsStringSync=function(e){return o.readFileSync(e,"utf-8")},t.isPathParameter=function(e){return e.startsWith("{")&&e.endsWith("}")},t.slash=function(e){return/^\\\\\?\\/.test(e)?e:e.replace(/\\/g,"/")},t.isNotEmptyObject=function(e){return!!e&&Object.keys(e).length>0},t.isString=f,t.isNotString=function(e){return!f(e)},t.assignExisting=function(e,t){for(let n of Object.keys(t))e.hasOwnProperty(n)&&(e[n]=t[n])},t.getMatchingStatusCodeRange=e=>`${e}`.replace(/^(\d)\d\d$/,((e,t)=>`${t}XX`)),t.isCustomRuleId=function(e){return e.includes("/")}},8065:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.normalizeVisitors=void 0,t.normalizeVisitors=function(e,t){const n={any:{enter:[],leave:[]}};for(const e of Object.keys(t))n[e]={enter:[],leave:[]};n.ref={enter:[],leave:[]};for(const{ruleId:t,severity:n,visitor:r}of e)o({ruleId:t,severity:n},r,null);for(const e of Object.keys(n))n[e].enter.sort(((e,t)=>t.depth-e.depth)),n[e].leave.sort(((e,t)=>e.depth-t.depth));return n;function r(e,t,o,i,a=[]){if(a.includes(t))return;a=[...a,t];const s=new Set;for(let n of Object.values(t.properties))n!==o?"object"==typeof n&&null!==n&&n.name&&s.add(n):l(e,a);t.additionalProperties&&"function"!=typeof t.additionalProperties&&(t.additionalProperties===o?l(e,a):void 0!==t.additionalProperties.name&&s.add(t.additionalProperties)),t.items&&(t.items===o?l(e,a):void 0!==t.items.name&&s.add(t.items));for(let t of Array.from(s.values()))r(e,t,o,i,a);function l(e,t){for(const r of t.slice(1))n[r.name]=n[r.name]||{enter:[],leave:[]},n[r.name].enter.push(Object.assign(Object.assign({},e),{visit:()=>{},depth:0,context:{isSkippedLevel:!0,seen:new Set,parent:i}}))}}function o(e,i,a,s=0){const l=Object.keys(t);if(0===s)l.push("any"),l.push("ref");else{if(i.any)throw new Error("any() is allowed only on top level");if(i.ref)throw new Error("ref() is allowed only on top level")}for(const c of l){const l=i[c],u=n[c];if(!l)continue;let p,d,f;const h="object"==typeof l;if("ref"===c&&h&&l.skip)throw new Error("ref() visitor does not support skip");"function"==typeof l?p=l:h&&(p=l.enter,d=l.leave,f=l.skip);const m={activatedOn:null,type:t[c],parent:a,isSkippedLevel:!1};if("object"==typeof l&&o(e,l,m,s+1),a&&r(e,a.type,t[c],a),p||h){if(p&&"function"!=typeof p)throw new Error("DEV: should be function");u.enter.push(Object.assign(Object.assign({},e),{visit:p||(()=>{}),skip:f,depth:s,context:m}))}if(d){if("function"!=typeof d)throw new Error("DEV: should be function");u.leave.push(Object.assign(Object.assign({},e),{visit:d,depth:s,context:m}))}}}}},9443:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.walkDocument=void 0;const r=n(7468),o=n(4182),i=n(771),a=n(5220);function s(e){var t,n;const r={};for(;e.parent;)(null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.location)&&(r[e.parent.type.name]=null===(n=e.parent.activatedOn)||void 0===n?void 0:n.value.location),e=e.parent;return r}t.walkDocument=function(e){const{document:t,rootType:n,normalizedVisitors:l,resolvedRefMap:c,ctx:u}=e,p={},d=new Set;!function e(t,n,f,h,m){var g,y,v,b,w,x,k,_,O,S,E;const P=(e,t=$.source.absoluteRef)=>{if(!r.isRef(e))return{location:f,node:e};const n=o.makeRefId(t,e.$ref),i=c.get(n);if(!i)return{location:void 0,node:void 0};const{resolved:a,node:s,document:l,nodePointer:u,error:p}=i;return{location:a?new r.Location(l.source,u):p instanceof o.YamlParseError?new r.Location(p.source,""):void 0,node:s,error:p}},A=f;let $=f;const{node:C,location:R,error:j}=P(t),T=new Set;if(r.isRef(t)){const e=l.ref.enter;for(const{visit:r,ruleId:o,severity:i,context:a}of e)if(!d.has(t)){T.add(a);r(t,{report:N.bind(void 0,o,i),resolve:P,rawNode:t,rawLocation:A,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:D.bind(void 0,o)},{node:C,location:R,error:j}),(null==R?void 0:R.source.absoluteRef)&&u.refTypes&&u.refTypes.set(null==R?void 0:R.source.absoluteRef,n)}}if(void 0!==C&&R&&"scalar"!==n.name){$=R;const o=null===(y=null===(g=p[n.name])||void 0===g?void 0:g.has)||void 0===y?void 0:y.call(g,C);let s=!1;const c=l.any.enter.concat((null===(v=l[n.name])||void 0===v?void 0:v.enter)||[]),u=[];for(const{context:e,visit:r,skip:a,ruleId:l,severity:p}of c)if(e.isSkippedLevel)!e.parent.activatedOn||e.parent.activatedOn.value.nextLevelTypeActivated||e.seen.has(t)||(e.seen.add(t),s=!0,u.push(e));else if(e.parent&&e.parent.activatedOn&&(null===(b=e.activatedOn)||void 0===b?void 0:b.value.withParentNode)!==e.parent.activatedOn.value.node&&(null===(w=e.parent.activatedOn.value.nextLevelTypeActivated)||void 0===w?void 0:w.value)!==n||!e.parent&&!o){u.push(e);const o={node:C,location:R,nextLevelTypeActivated:null,withParentNode:null===(k=null===(x=e.parent)||void 0===x?void 0:x.activatedOn)||void 0===k?void 0:k.value.node,skipped:null!==(S=(null===(O=null===(_=e.parent)||void 0===_?void 0:_.activatedOn)||void 0===O?void 0:O.value.skipped)||(null==a?void 0:a(C,m)))&&void 0!==S&&S};e.activatedOn=i.pushStack(e.activatedOn,o);let c=e.parent;for(;c;)c.activatedOn.value.nextLevelTypeActivated=i.pushStack(c.activatedOn.value.nextLevelTypeActivated,n),c=c.parent;if(!o.skipped){s=!0,T.add(e);const{ignoreNextVisitorsOnNode:n}=I(r,C,t,e,l,p);if(n)break}}if(s||!o)if(p[n.name]=p[n.name]||new Set,p[n.name].add(C),Array.isArray(C)){const t=n.items;if(void 0!==t)for(let n=0;n!o.includes(e)))),r.isRef(t)&&o.push(...Object.keys(t).filter((e=>"$ref"!==e&&!o.includes(e))));for(const i of o){let o=C[i],s=R;void 0===o&&(o=t[i],s=f);let l=n.properties[i];void 0===l&&(l=n.additionalProperties),"function"==typeof l&&(l=l(o,i)),!a.isNamedType(l)&&(null==l?void 0:l.directResolveAs)&&(l=l.directResolveAs,o={$ref:o}),l&&void 0===l.name&&!1!==l.resolvable&&(l={name:"scalar",properties:{}}),a.isNamedType(l)&&("scalar"!==l.name||r.isRef(o))&&e(o,l,s.child([i]),C,i)}}const d=l.any.leave,h=((null===(E=l[n.name])||void 0===E?void 0:E.leave)||[]).concat(d);for(const e of u.reverse())if(e.isSkippedLevel)e.seen.delete(C);else if(e.activatedOn=i.popStack(e.activatedOn),e.parent){let t=e.parent;for(;t;)t.activatedOn.value.nextLevelTypeActivated=i.popStack(t.activatedOn.value.nextLevelTypeActivated),t=t.parent}for(const{context:e,visit:n,ruleId:r,severity:o}of h)!e.isSkippedLevel&&T.has(e)&&I(n,C,t,e,r,o)}if($=f,r.isRef(t)){const e=l.ref.leave;for(const{visit:r,ruleId:o,severity:i,context:a}of e)if(T.has(a)){r(t,{report:N.bind(void 0,o,i),resolve:P,rawNode:t,rawLocation:A,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:D.bind(void 0,o)},{node:C,location:R,error:j})}}function I(e,t,r,o,i,a){const l=N.bind(void 0,i,a);let c=!1;return e(t,{report:l,resolve:P,rawNode:r,location:$,rawLocation:A,type:n,parent:h,key:m,parentLocations:s(o),oasVersion:u.oasVersion,ignoreNextVisitorsOnNode:()=>{c=!0},getVisitorData:D.bind(void 0,i)},function(e){var t;const n={};for(;e.parent;)n[e.parent.type.name]=null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.node,e=e.parent;return n}(o),o),{ignoreNextVisitorsOnNode:c}}function N(e,t,n){const r=n.location?Array.isArray(n.location)?n.location:[n.location]:[Object.assign(Object.assign({},$),{reportOnKey:!1})];u.problems.push(Object.assign(Object.assign({ruleId:n.ruleId||e,severity:n.forceSeverity||t},n),{suggest:n.suggest||[],location:r.map((e=>Object.assign(Object.assign(Object.assign({},$),{reportOnKey:!1}),e)))}))}function D(e){return u.visitorsData[e]=u.visitorsData[e]||{},u.visitorsData[e]}}(t.parsed,n,new r.Location(t.source,"#/"),void 0,"")}},5019:function(e,t,n){var r=n(5623);e.exports=function(e){return e?("{}"===e.substr(0,2)&&(e="\\{\\}"+e.substr(2)),g(function(e){return e.split("\\\\").join(o).split("\\{").join(i).split("\\}").join(a).split("\\,").join(s).split("\\.").join(l)}(e),!0).map(u)):[]};var o="\0SLASH"+Math.random()+"\0",i="\0OPEN"+Math.random()+"\0",a="\0CLOSE"+Math.random()+"\0",s="\0COMMA"+Math.random()+"\0",l="\0PERIOD"+Math.random()+"\0";function c(e){return parseInt(e,10)==e?parseInt(e,10):e.charCodeAt(0)}function u(e){return e.split(o).join("\\").split(i).join("{").split(a).join("}").split(s).join(",").split(l).join(".")}function p(e){if(!e)return[""];var t=[],n=r("{","}",e);if(!n)return e.split(",");var o=n.pre,i=n.body,a=n.post,s=o.split(",");s[s.length-1]+="{"+i+"}";var l=p(a);return a.length&&(s[s.length-1]+=l.shift(),s.push.apply(s,l)),t.push.apply(t,s),t}function d(e){return"{"+e+"}"}function f(e){return/^-?0\d/.test(e)}function h(e,t){return e<=t}function m(e,t){return e>=t}function g(e,t){var n=[],o=r("{","}",e);if(!o)return[e];var i=o.pre,s=o.post.length?g(o.post,!1):[""];if(/\$$/.test(o.pre))for(var l=0;l=0;if(!x&&!k)return o.post.match(/,.*\}/)?g(e=o.pre+"{"+o.body+a+o.post):[e];if(x)y=o.body.split(/\.\./);else if(1===(y=p(o.body)).length&&1===(y=g(y[0],!1).map(d)).length)return s.map((function(e){return o.pre+y[0]+e}));if(x){var _=c(y[0]),O=c(y[1]),S=Math.max(y[0].length,y[1].length),E=3==y.length?Math.abs(c(y[2])):1,P=h;O<_&&(E*=-1,P=m);var A=y.some(f);v=[];for(var $=_;P($,O);$+=E){var C;if(w)"\\"===(C=String.fromCharCode($))&&(C="");else if(C=String($),A){var R=S-C.length;if(R>0){var j=new Array(R+1).join("0");C=$<0?"-"+j+C.slice(1):j+C}}v.push(C)}}else{v=[];for(var T=0;T(g(t),!(!n.nocomment&&"#"===t.charAt(0))&&new v(t,n).match(e));e.exports=r;const o=n(5751);r.sep=o.sep;const i=Symbol("globstar **");r.GLOBSTAR=i;const a=n(5019),s={"!":{open:"(?:(?!(?:",close:"))[^/]*?)"},"?":{open:"(?:",close:")?"},"+":{open:"(?:",close:")+"},"*":{open:"(?:",close:")*"},"@":{open:"(?:",close:")"}},l="[^/]",c="[^/]*?",u=e=>e.split("").reduce(((e,t)=>(e[t]=!0,e)),{}),p=u("().*{}+?[]^$\\!"),d=u("[.("),f=/\/+/;r.filter=(e,t={})=>(n,o,i)=>r(n,e,t);const h=(e,t={})=>{const n={};return Object.keys(e).forEach((t=>n[t]=e[t])),Object.keys(t).forEach((e=>n[e]=t[e])),n};r.defaults=e=>{if(!e||"object"!=typeof e||!Object.keys(e).length)return r;const t=r,n=(n,r,o)=>t(n,r,h(e,o));return(n.Minimatch=class extends t.Minimatch{constructor(t,n){super(t,h(e,n))}}).defaults=n=>t.defaults(h(e,n)).Minimatch,n.filter=(n,r)=>t.filter(n,h(e,r)),n.defaults=n=>t.defaults(h(e,n)),n.makeRe=(n,r)=>t.makeRe(n,h(e,r)),n.braceExpand=(n,r)=>t.braceExpand(n,h(e,r)),n.match=(n,r,o)=>t.match(n,r,h(e,o)),n},r.braceExpand=(e,t)=>m(e,t);const m=(e,t={})=>(g(e),t.nobrace||!/\{(?:(?!\{).)*\}/.test(e)?[e]:a(e)),g=e=>{if("string"!=typeof e)throw new TypeError("invalid pattern");if(e.length>65536)throw new TypeError("pattern is too long")},y=Symbol("subparse");r.makeRe=(e,t)=>new v(e,t||{}).makeRe(),r.match=(e,t,n={})=>{const r=new v(t,n);return e=e.filter((e=>r.match(e))),r.options.nonull&&!e.length&&e.push(t),e};class v{constructor(e,t){g(e),t||(t={}),this.options=t,this.set=[],this.pattern=e,this.regexp=null,this.negate=!1,this.comment=!1,this.empty=!1,this.partial=!!t.partial,this.make()}debug(){}make(){const e=this.pattern,t=this.options;if(!t.nocomment&&"#"===e.charAt(0))return void(this.comment=!0);if(!e)return void(this.empty=!0);this.parseNegate();let n=this.globSet=this.braceExpand();t.debug&&(this.debug=(...e)=>console.error(...e)),this.debug(this.pattern,n),n=this.globParts=n.map((e=>e.split(f))),this.debug(this.pattern,n),n=n.map(((e,t,n)=>e.map(this.parse,this))),this.debug(this.pattern,n),n=n.filter((e=>-1===e.indexOf(!1))),this.debug(this.pattern,n),this.set=n}parseNegate(){if(this.options.nonegate)return;const e=this.pattern;let t=!1,n=0;for(let r=0;r>> no match, partial?",e,d,t,f),d!==s))}if("string"==typeof u?(c=p===u,this.debug("string match",u,p,c)):(c=p.match(u),this.debug("pattern match",u,p,c)),!c)return!1}if(o===s&&a===l)return!0;if(o===s)return n;if(a===l)return o===s-1&&""===e[o];throw new Error("wtf?")}braceExpand(){return m(this.pattern,this.options)}parse(e,t){g(e);const n=this.options;if("**"===e){if(!n.noglobstar)return i;e="*"}if(""===e)return"";let r="",o=!!n.nocase,a=!1;const u=[],f=[];let h,m,v,b,w=!1,x=-1,k=-1;const _="."===e.charAt(0)?"":n.dot?"(?!(?:^|\\/)\\.{1,2}(?:$|\\/))":"(?!\\.)",O=()=>{if(h){switch(h){case"*":r+=c,o=!0;break;case"?":r+=l,o=!0;break;default:r+="\\"+h}this.debug("clearStateChar %j %j",h,r),h=!1}};for(let t,i=0;i(n||(n="\\"),t+t+n+"|"))),this.debug("tail=%j\n %s",e,e,v,r);const t="*"===v.type?c:"?"===v.type?l:"\\"+v.type;o=!0,r=r.slice(0,v.reStart)+t+"\\("+e}O(),a&&(r+="\\\\");const S=d[r.charAt(0)];for(let e=f.length-1;e>-1;e--){const n=f[e],o=r.slice(0,n.reStart),i=r.slice(n.reStart,n.reEnd-8);let a=r.slice(n.reEnd);const s=r.slice(n.reEnd-8,n.reEnd)+a,l=o.split("(").length-1;let c=a;for(let e=0;e(e=e.map((e=>"string"==typeof e?e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"):e===i?i:e._src)).reduce(((e,t)=>(e[e.length-1]===i&&t===i||e.push(t),e)),[]),e.forEach(((t,r)=>{t===i&&e[r-1]!==i&&(0===r?e.length>1?e[r+1]="(?:\\/|"+n+"\\/)?"+e[r+1]:e[r]=n:r===e.length-1?e[r-1]+="(?:\\/|"+n+")?":(e[r-1]+="(?:\\/|\\/"+n+"\\/)"+e[r+1],e[r+1]=i))})),e.filter((e=>e!==i)).join("/")))).join("|");o="^(?:"+o+")$",this.negate&&(o="^(?!"+o+").*$");try{this.regexp=new RegExp(o,r)}catch(e){this.regexp=!1}return this.regexp}match(e,t=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return""===e;if("/"===e&&t)return!0;const n=this.options;"/"!==o.sep&&(e=e.split(o.sep).join("/")),e=e.split(f),this.debug(this.pattern,"split",e);const r=this.set;let i;this.debug(this.pattern,"set",r);for(let t=e.length-1;t>=0&&(i=e[t],!i);t--);for(let o=0;o=0&&c>0){if(e===t)return[l,c];for(r=[],i=n.length;u>=0&&!s;)u==l?(r.push(u),l=n.indexOf(e,u+1)):1==r.length?s=[r.pop(),c]:((o=r.pop())=0?l:c;r.length&&(s=[i,a])}return s}e.exports=t,t.range=r},4480:function(e,t,n){"use strict";var r=n.g.process&&process.nextTick||n.g.setImmediate||function(e){setTimeout(e,0)};e.exports=function(e,t){return e?void t.then((function(t){r((function(){e(null,t)}))}),(function(t){r((function(){e(t)}))})):t}},4184:function(e,t){var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var e=[],t=0;tu;)if((s=l[u++])!=s)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},2092:function(e,t,n){var r=n(9974),o=n(8361),i=n(7908),a=n(7466),s=n(5417),l=[].push,c=function(e){var t=1==e,n=2==e,c=3==e,u=4==e,p=6==e,d=7==e,f=5==e||p;return function(h,m,g,y){for(var v,b,w=i(h),x=o(w),k=r(m,g,3),_=a(x.length),O=0,S=y||s,E=t?S(h,_):n||d?S(h,0):void 0;_>O;O++)if((f||O in x)&&(b=k(v=x[O],O,w),e))if(t)E[O]=b;else if(b)switch(e){case 3:return!0;case 5:return v;case 6:return O;case 2:l.call(E,v)}else switch(e){case 4:return!1;case 7:l.call(E,v)}return p?-1:c||u?u:E}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterOut:c(7)}},1194:function(e,t,n){var r=n(7293),o=n(5112),i=n(7392),a=o("species");e.exports=function(e){return i>=51||!r((function(){var t=[];return(t.constructor={})[a]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},5417:function(e,t,n){var r=n(111),o=n(3157),i=n(5112)("species");e.exports=function(e,t){var n;return o(e)&&("function"!=typeof(n=e.constructor)||n!==Array&&!o(n.prototype)?r(n)&&null===(n=n[i])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===t?0:t)}},4326:function(e){var t={}.toString;e.exports=function(e){return t.call(e).slice(8,-1)}},648:function(e,t,n){var r=n(1694),o=n(4326),i=n(5112)("toStringTag"),a="Arguments"==o(function(){return arguments}());e.exports=r?o:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),i))?n:a?o(t):"Object"==(r=o(t))&&"function"==typeof t.callee?"Arguments":r}},9920:function(e,t,n){var r=n(6656),o=n(3887),i=n(1236),a=n(3070);e.exports=function(e,t){for(var n=o(t),s=a.f,l=i.f,c=0;c=74)&&(r=a.match(/Chrome\/(\d+)/))&&(o=r[1]),e.exports=o&&+o},748:function(e){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:function(e,t,n){var r=n(7854),o=n(1236).f,i=n(8880),a=n(1320),s=n(3505),l=n(9920),c=n(4705);e.exports=function(e,t){var n,u,p,d,f,h=e.target,m=e.global,g=e.stat;if(n=m?r:g?r[h]||s(h,{}):(r[h]||{}).prototype)for(u in t){if(d=t[u],p=e.noTargetGet?(f=o(n,u))&&f.value:n[u],!c(m?u:h+(g?".":"#")+u,e.forced)&&void 0!==p){if(typeof d==typeof p)continue;l(d,p)}(e.sham||p&&p.sham)&&i(d,"sham",!0),a(n,u,d,e)}}},7293:function(e){e.exports=function(e){try{return!!e()}catch(e){return!0}}},9974:function(e,t,n){var r=n(3099);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},5005:function(e,t,n){var r=n(857),o=n(7854),i=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},7854:function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},6656:function(e,t,n){var r=n(7908),o={}.hasOwnProperty;e.exports=Object.hasOwn||function(e,t){return o.call(r(e),t)}},3501:function(e){e.exports={}},490:function(e,t,n){var r=n(5005);e.exports=r("document","documentElement")},4664:function(e,t,n){var r=n(9781),o=n(7293),i=n(317);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},8361:function(e,t,n){var r=n(7293),o=n(4326),i="".split;e.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==o(e)?i.call(e,""):Object(e)}:Object},2788:function(e,t,n){var r=n(5465),o=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(e){return o.call(e)}),e.exports=r.inspectSource},9909:function(e,t,n){var r,o,i,a=n(8536),s=n(7854),l=n(111),c=n(8880),u=n(6656),p=n(5465),d=n(6200),f=n(3501),h="Object already initialized",m=s.WeakMap;if(a||p.state){var g=p.state||(p.state=new m),y=g.get,v=g.has,b=g.set;r=function(e,t){if(v.call(g,e))throw new TypeError(h);return t.facade=e,b.call(g,e,t),t},o=function(e){return y.call(g,e)||{}},i=function(e){return v.call(g,e)}}else{var w=d("state");f[w]=!0,r=function(e,t){if(u(e,w))throw new TypeError(h);return t.facade=e,c(e,w,t),t},o=function(e){return u(e,w)?e[w]:{}},i=function(e){return u(e,w)}}e.exports={set:r,get:o,has:i,enforce:function(e){return i(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},3157:function(e,t,n){var r=n(4326);e.exports=Array.isArray||function(e){return"Array"==r(e)}},4705:function(e,t,n){var r=n(7293),o=/#|\.prototype\./,i=function(e,t){var n=s[a(e)];return n==c||n!=l&&("function"==typeof t?r(t):!!t)},a=i.normalize=function(e){return String(e).replace(o,".").toLowerCase()},s=i.data={},l=i.NATIVE="N",c=i.POLYFILL="P";e.exports=i},111:function(e){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},1913:function(e){e.exports=!1},133:function(e,t,n){var r=n(7392),o=n(7293);e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},8536:function(e,t,n){var r=n(7854),o=n(2788),i=r.WeakMap;e.exports="function"==typeof i&&/native code/.test(o(i))},30:function(e,t,n){var r,o=n(9670),i=n(6048),a=n(748),s=n(3501),l=n(490),c=n(317),u=n(6200)("IE_PROTO"),p=function(){},d=function(e){return" - - - \ No newline at end of file diff --git a/src/management_api/email/templates/devs_message.html b/src/management_api/email/templates/devs_message.html deleted file mode 100644 index 2af6605..0000000 --- a/src/management_api/email/templates/devs_message.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - Account Confirmation - - -
-
-
-
-
-

Account Confirmation

-
-
-

Dear EOD-STOCK-API Developer,

- Request Error - Request URL : {{ url }} - Request Method : {{ method }} - Headers : {{ headers }} - -
-
-
-
-
- - - - - - - - - diff --git a/src/management_api/email/templates/payment_confirmation.html b/src/management_api/email/templates/payment_confirmation.html deleted file mode 100644 index ea574b4..0000000 --- a/src/management_api/email/templates/payment_confirmation.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - Account Confirmation - - -
-
-
-
-
-

Account Confirmation

-
-
-

Dear {{ client_name }},

-

Thank you for your payment of {{ amount }} for our {{ plan_name }} subscription.

-

Your payment has been successfully processed and your subscription is now active.

-

If you have any questions or concerns, please don't hesitate to contact us at - support@eod-stock-api.site.

-

Best regards,

-

https://eod-stock-api.site

-
-
-
-
-
- - - - - - - - - diff --git a/src/management_api/email/templates/subscription_welcome.html b/src/management_api/email/templates/subscription_welcome.html deleted file mode 100644 index f03fce6..0000000 --- a/src/management_api/email/templates/subscription_welcome.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - Sign Up Confirmation - - -
-
-
-
-
-

Sign Up Confirmation

-
-
-

Dear {{ client_name }},

-

Thank you for signing up for our {{ plan_name }} subscription!

-

We're excited to have you on board and can't wait to provide you with our top-notch services.

-

If you have any questions or concerns, please don't hesitate to contact us at - support@eod-stock-api.site.

-

Best regards,

-

https://eod-stock-api.site

-
-
-
-
-
- - - - - - - - -l> \ No newline at end of file diff --git a/src/management_api/models/__init__.py b/src/management_api/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/models/apikeys.py b/src/management_api/models/apikeys.py deleted file mode 100644 index 9cc5be2..0000000 --- a/src/management_api/models/apikeys.py +++ /dev/null @@ -1,15 +0,0 @@ -from pydantic import BaseModel -from src.management_api.models.subscriptions import SubscriptionUpdate - - -class ApiKeysModel(BaseModel): - uuid: str - api_key: str - duration: int - rate_limit: int - is_active: bool - subscription: SubscriptionUpdate - - class Config: - title = "API Keys Model" - extra = "allow" diff --git a/src/management_api/models/authentication.py b/src/management_api/models/authentication.py deleted file mode 100644 index 5a5dcfd..0000000 --- a/src/management_api/models/authentication.py +++ /dev/null @@ -1,49 +0,0 @@ -from pydantic import BaseModel, validator, root_validator - - -class LoginData(BaseModel): - """ - Model for user login data - Attributes: - email (str): User's email address - password (str): User's password - """ - - email: str - password: str - - @classmethod - @validator('email') - def validate_email(cls, v): - if "@" not in v: - raise ValueError('Not a Valid Email Address') - return v - - class Config: - title = "Login Model" - extra = "forbid" - - -class AuthorizationRequest(BaseModel): - """ - Model for authorization request - - Attributes: - uuid (str): Client's UUID - path (str): Path login or authorization Request - method (str): Method of request which will be used to access the path (default: 'GET') - """ - uuid: str - path: str - method: str - - @root_validator - def validate_not_empty(cls, values): - for key, value in values.items(): - if not value: - raise ValueError(f"{key} must not be empty") - return values - - class Config: - title = "User Authorization Model" - extra = "allow" diff --git a/src/management_api/models/contact.py b/src/management_api/models/contact.py deleted file mode 100644 index ae76817..0000000 --- a/src/management_api/models/contact.py +++ /dev/null @@ -1,14 +0,0 @@ -from pydantic import BaseModel - - -class ContactModel(BaseModel): - uuid: str - contact_id: str - name: str - email: str - message: str - timestamp: float - - class Config: - title = "Contact Model" - extra = "forbid" diff --git a/src/management_api/models/paypal.py b/src/management_api/models/paypal.py deleted file mode 100644 index e1c7ab2..0000000 --- a/src/management_api/models/paypal.py +++ /dev/null @@ -1,17 +0,0 @@ -from pydantic import BaseModel - - -class PayPalIPN(BaseModel): - """ - Model to handle data from PayPal IPN - """ - txn_id: str | None - txn_type: str | None - payment_status: str | None - mc_gross: float | None - mc_currency: str | None - custom: str | None - - class Config: - title = "PayPalIPN Model" - extra = "ignore" diff --git a/src/management_api/models/plans.py b/src/management_api/models/plans.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/models/subscriptions.py b/src/management_api/models/subscriptions.py deleted file mode 100644 index 619a04f..0000000 --- a/src/management_api/models/subscriptions.py +++ /dev/null @@ -1,38 +0,0 @@ -from pydantic import BaseModel - - -class SubscriptionCreate(BaseModel): - """ - BaseClass for creating New Subscriptions - """ - plan_id: str - uuid: str - paypal_id: str - billing_token: str - payer_id: str - subscription_id: str - facilitatorAccessToken: str - payment_method: str - - class Config: - title = "Subscription Created Schema" - description = "Used to create new Subscription Models" - - -class SubscriptionUpdate(BaseModel): - """ - PayPal Subscriptions Update Model - """ - subscription_id: str - plan_id: str - uuid: str | None = None - time_subscribed: float | None = None - payment_day: str | None = None - _is_active: bool | None = None - api_requests_balance: int | None = None - approval_url: str | None = None - paypal_id: str | None = None - - class Config: - title = "PayPal Subscription Schema" - description = "Used to create a subscription model for paypal subscriptions" diff --git a/src/management_api/models/users.py b/src/management_api/models/users.py deleted file mode 100644 index c8a4004..0000000 --- a/src/management_api/models/users.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field - -from src.management_api.models.apikeys import ApiKeysModel -from src.utils.utils import create_id - - -class AccountUpdate(BaseModel): - uuid: str - first_name: str | None = None - second_name: str | None = None - surname: str | None = None - email: str | None = None - cell: str | None = None - is_admin: bool | None = None - is_deleted: bool | None = None - - class Config: - title = "Account Update Schema" - - -class AccountCreate(BaseModel): - uuid: str | None = Field(default_factory=create_id) - first_name: str | None - second_name: str | None - surname: str | None - email: str | None - cell: str | None - password: str | None - - class Config: - title = "Account Created Schema" - - -class UserResponseSchema(BaseModel): - status: bool - payload: Optional[AccountUpdate] - message: str - - class Config: - title = "User Response Schema" - - -class DeleteResponseSchema(BaseModel): - status: bool = True - message: str - - class Config: - title = "Delete Response Schema" - - -class LoginResponseSchema(BaseModel): - uuid: str - first_name: str - second_name: str | None - surname: str - email: str - cell: str - password: str - is_admin: bool | None - is_deleted: bool | None - apikeys: ApiKeysModel - - class Config: - title = "Login Response Schema" - - -class UsersResponseSchema(BaseModel): - status: bool = True - payload: list[LoginResponseSchema] - message: str - - class Config: - title = "Users Response Schema" diff --git a/src/management_api/routers/__init__.py b/src/management_api/routers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/routers/authorization/__init__.py b/src/management_api/routers/authorization/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/routers/authorization/authorization.py b/src/management_api/routers/authorization/authorization.py deleted file mode 100644 index 2a35eaa..0000000 --- a/src/management_api/routers/authorization/authorization.py +++ /dev/null @@ -1,131 +0,0 @@ -import hmac -import random -import string - -from fastapi import APIRouter, Request -from starlette.responses import JSONResponse - -from src.cache.cache import redis_cache -from src.database.account.account import Account, TwoFactorLoginData -from src.database.database_sessions import sessions -from src.management_api.admin.authentication import authenticate_app, get_headers -from src.management_api.email.email import email_process -from src.management_api.models.authentication import LoginData, AuthorizationRequest -from src.management_api.routers.authorization.route_authorization import check_authorization -from src.utils.my_logger import init_logger - -auth_router = APIRouter() - -auth_logger = init_logger("auth_logger") - - -# noinspection PyUnusedLocal -@auth_router.api_route(path="/auth/login", methods=["POST"], include_in_schema=True) -@authenticate_app -async def login(login_data: LoginData, request: Request): - """ - **login** - used to log in user and also create two-factor authentication code, - - :param request: - :param login_data: - :return: - """ - user_data: dict[str, str] = login_data.dict() - email = user_data.get("email") - password = user_data.get("password") - auth_logger.info(f"login into account : {email}") - with next(sessions) as session: - user_instance = await Account.login(username=email, password=password, session=session) - if user_instance: - auth_logger.info(f"user instance : {user_instance.to_dict()}") - # Once the user is logged in generate and send two factor auth - code = await generate_and_send_two_factor_code(email=email) - two_factor_key = f"two_factor_code_{user_instance.to_dict().get('uuid')}" - # Two-factor authentication will expire after 5 minutes - await redis_cache.set(key=two_factor_key, value=code, ttl=60*5) - - payload = dict(status=True, payload=user_instance.to_dict(), message="successfully logged in") - else: - payload = dict(status=False, payload={}, message="User not found") - headers = await get_headers(user_data=user_instance.to_dict()) - - return JSONResponse(content=payload, status_code=200, headers=headers) - - -# noinspection PyUnusedLocal -@auth_router.api_route(path="/auth/login/two-factor", methods=["POST"], include_in_schema=True) -async def authenticate_two_factor(two_factor_data: TwoFactorLoginData, request: Request): - """ - **authenticate_two_factor** - Endpoint to authenticate two-factor authentication code. - if code does not authenticate in five minutes then the client app will not login the user - - :param two_factor_data: TwoFactorLoginData - data containing user's email and two-factor authentication code - :param request: Request - the incoming request - :return: JSONResponse - a response containing the login status and user data - """ - user_data: dict[str, str] = two_factor_data.dict() - email: str = user_data.get("email") - code: str = user_data.get("code") - auth_logger.info(f"Authenticating two-factor code for account: {email}") - - with next(sessions) as session: - # Retrieve the two-factor authentication key stored in Redis - user_instance: Account = await Account.get_by_email(email, session=session) - - two_factor_key = f"two_factor_key_{user_instance.to_dict().get('uuid')}" - stored_key = await redis_cache.get(two_factor_key) - if stored_key is None: - payload: dict[str, str | bool] = dict(status=False, payload={}, message="Authentication key Expired") - return JSONResponse(content=payload, status_code=401) - - # Use HMAC to compare the user's input key with the stored key - stored_key_bytes: bytes = stored_key.encode('utf-8') - code_bytes: bytes = code.encode('utf-8') - - if not hmac.compare_digest(stored_key_bytes, code_bytes): - # Authentication failed - payload: dict[str, str | bool, dict[str, str]] = dict(status=False, payload={}, message="invalid two-factor authentication key") - return JSONResponse(content=payload, status_code=401) - - # Authentication succeeded - user_instance: Account = await Account.get_by_email(email=email, session=session) - - payload = dict(status=True, payload=user_instance.to_dict(), message="successfully authenticated two-factor") - headers = await get_headers(user_data=user_instance.to_dict()) - return JSONResponse(content=payload, status_code=200, headers=headers) - - -# noinspection PyUnusedLocal -@auth_router.api_route(path="/auth/authorize", methods=["POST"], include_in_schema=True) -@authenticate_app -async def authorization(auth_data: AuthorizationRequest, request: Request): - """ - **authorization** - authorizes requests to specific resources within the admin application - - :type request: Request - :param request: - :type auth_data: AuthorizationRequest - :param auth_data: authorization data class - :return: payload : dict[str, str| bool dict[str|bool]], status_code - """ - - is_authorized: bool = await check_authorization(uuid=auth_data.uuid, path=auth_data.path, method=auth_data.method) - message: str = "User is Authorized" if is_authorized else "User not Authorized" - payload: dict[str, bool | str] = dict(status=True, payload=dict(is_authorized=is_authorized), message=message) - headers: dict[str, dict[str, bool | str]] = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=200, headers=headers) - - -async def generate_and_send_two_factor_code(email: str) -> str: - """ - Generate a two-factor code and schedule an email to be sent later. - Returns the generated code. - """ - # Generate a random code - code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) - # Add the code and email to the queue - await email_process.send_two_factor_code_email(email=email, code=code) - return code diff --git a/src/management_api/routers/authorization/route_authorization.py b/src/management_api/routers/authorization/route_authorization.py deleted file mode 100644 index 4ddab00..0000000 --- a/src/management_api/routers/authorization/route_authorization.py +++ /dev/null @@ -1,76 +0,0 @@ -""" - This Module is responsible with route authorization -""" - -import re - -from src.database.account.account import Account -from src.database.database_sessions import sessions -from src.utils.my_logger import init_logger - -auth_logger = init_logger("auth_logger") - -ALLOWED_ROUTES = { - "regular": { - "/home": ["GET"], - "/account": ["GET", "PUT", "POST", "DELETE"], - "/account/two-factor": ["GET", "PUT", "POST"], - "/profile": ["GET", "PUT"], - "/user": ["GET", "PUT", "POST"], - "/auth/authorize": ["POST"], - "/auth/login": ["GET"], - "/logout": ["GET"], - "/login": ["GET", "POST"], - r"/account/\w+": ["GET", "PUT", "DELETE"], - "/update-api-key": ["POST"] - }, - "admin": { - "/dashboard/users": ["GET", "POST", "PUT", "DELETE"], - "/dashboard/subscriptions": ["GET", "POST", "PUT", "DELETE"], - "/dashboard/plans": ["GET", "POST", "PUT", "DELETE"], - "/home": ["GET"], - "/account": ["GET", "PUT", "POST", "DELETE"], - "/account/two-factor": ["GET", "PUT", "POST"], - "/profile": ["GET", "PUT"], - "/user": ["GET", "PUT", "POST"], - "/auth/authorize": ["POST"], - "/auth/login": ["GET"], - "/logout": ["GET"], - "/login": ["GET", "POST"], - r"/account/\w+": ["GET", "PUT", "POST", "DELETE"], - "/update-api-key": ["POST"] - } -} - - -async def check_authorization(uuid: str | None, path: str, method: str) -> bool: - """ - Function to check if user is authorized to access a specific route based on their role. - :param uuid: The user's UUID. - :param path: The path being accessed. - :param method: The HTTP method being used. - :return: True if the user is authorized, False otherwise. - """ - # Load the allowed routes for the user's role - if uuid is None or path is None or method is None: - return False - - auth_logger.info(f"Authorizing Path : {path} and Method: {method}, for UUID : {uuid}") - # Retrieve the user data based on the UUID - with next(sessions) as session: - user: Account | None = await Account.get_by_uuid(uuid=uuid, session=session) - - if user is None: - return False - - auth_logger.info(f"User is found for authorization: user: {user.uuid}") - - routes: dict[str, list[str]] = ALLOWED_ROUTES["admin"] if user.is_admin else ALLOWED_ROUTES["regular"] - method: str = method.upper() - # Check if the requested path matches any of the regular expressions in the dictionary - for route, methods in routes.items(): - if re.match(route, path) and method in methods: - return True - - auth_logger.info(f"User : {uuid} is Not Authorized to access path : {path}") - return False diff --git a/src/management_api/routers/contact/__init__.py b/src/management_api/routers/contact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/routers/contact/contact_route.py b/src/management_api/routers/contact/contact_route.py deleted file mode 100644 index 0ce92aa..0000000 --- a/src/management_api/routers/contact/contact_route.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import APIRouter, Request -from starlette.responses import JSONResponse - -from src.database.contact import Contacts -from src.database.database_sessions import sessions -from src.management_api.admin.authentication import get_headers, authenticate_app -from src.management_api.models.contact import ContactModel -from src.utils.my_logger import init_logger - -contact_router = APIRouter() - -contact_logger = init_logger('contact-logger') - - -# noinspection PyUnusedLocal -@contact_router.api_route('/contacts', methods=['POST']) -@authenticate_app -async def create_contact(request: Request, contact_data: ContactModel): - """ - will create a new contact record on - :param request: - :param contact_data: - :return: - """ - with next(sessions) as session: - Contacts.create_if_not_exists() - contact_instance: Contacts = Contacts(**contact_data.dict()) - session.add(contact_instance) - session.commit() - - _payload = dict(status=True, message="message sent successfully") - # creating the header without payload to avoid a problem when a message is too big - headers = await get_headers(user_data=_payload) - _payload.update(payload=contact_instance.to_dict()) - - return JSONResponse(content=_payload, status_code=200, headers=headers) diff --git a/src/management_api/routers/logs/__init__.py b/src/management_api/routers/logs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/routers/logs/logs.py b/src/management_api/routers/logs/logs.py deleted file mode 100644 index 8e3053c..0000000 --- a/src/management_api/routers/logs/logs.py +++ /dev/null @@ -1,39 +0,0 @@ -import os - -from fastapi import APIRouter, Request -from starlette.responses import JSONResponse - -from src.config import config_instance -from src.management_api.admin.authentication import authenticate_app, get_headers -from src.utils.my_logger import EncryptedFormatter - -log_router = APIRouter() - - -# noinspection PyUnusedLocal -@log_router.get("/_admin/logs/{num_logs}") -@authenticate_app -async def get_logs(request: Request, num_logs: int = 50): - """will retrieve encrypted logs and display on the admin app""" - log_filename = config_instance().LOGGING.filename - log_file_path = os.path.join("logs", log_filename) - - # Create a log formatter with the encryption key from the config - encrypted_logs = EncryptedFormatter() - - # Open the log file and read the last `num_logs` lines (or all lines if `num_logs` is not specified) - with open(log_file_path, "r") as f: - if num_logs: - lines = f.readlines()[-num_logs:] - else: - lines = f.readlines() - - # Decrypt each line and return the decrypted log messages - decrypted_logs = [] - for line in lines: - decrypted_log = encrypted_logs.decrypt_formatted_log(line) - decrypted_logs.append(decrypted_log) - - payload = dict(status=True, logs=decrypted_logs, message="logs found") - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=200, headers=_headers) diff --git a/src/management_api/routers/paypal/__init__.py b/src/management_api/routers/paypal/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/routers/paypal/event_notes.txt b/src/management_api/routers/paypal/event_notes.txt deleted file mode 100644 index a13ca30..0000000 --- a/src/management_api/routers/paypal/event_notes.txt +++ /dev/null @@ -1,130 +0,0 @@ - -"Event Activated" - -{ - "id": "WH-55TG7562XN2588878-8YH955435R661687G", - "create_time": "2018-19-12T22:20:32.000Z", - "resource_type": "plan", - "event_type": "BILLING.PLAN.ACTIVATED", - "summary": "A billing plan was activated.", - "resource": { - "update_time": "2018-12-10T21:20:49Z", - "create_time": "2018-12-10T21:20:49Z", - "usage_type": "LICENSED", - "payment_preferences": { - "service_type": "PREPAID", - "auto_bill_outstanding": true, - "setup_fee": { - "value": "10", - "currency_code": "USD" - }, - "setup_fee_failure_action": "CONTINUE", - "payment_failure_threshold": 3 - }, - "product_id": "PROD-XXCD1234QWER65782", - "name": "Zoho Marketing Campaign Plan", - "billing_cycles": [ - { - "frequency": { - "interval_unit": "MONTH", - "interval_count": 1 - }, - "tenure_type": "TRIAL", - "sequence": 1, - "total_cycles": 1, - "pricing_scheme": { - "fixed_price": { - "value": "50", - "currency_code": "USD" - }, - "tier_mode": "VOLUME", - "tiers": [ - { - "starting_quantity": "1", - "ending_quantity": "1000", - "amount": { - "value": "100", - "currency_code": "USD" - } - }, - { - "starting_quantity": "1001", - "amount": { - "value": "200", - "currency_code": "USD" - } - } - ] - } - }, - { - "frequency": { - "interval_unit": "MONTH", - "interval_count": 1 - }, - "tenure_type": "REGULAR", - "sequence": 2, - "total_cycles": 12, - "pricing_scheme": { - "fixed_price": { - "value": "100", - "currency_code": "USD" - }, - "tier_mode": "VOLUME", - "tiers": [ - { - "starting_quantity": "1", - "ending_quantity": "1000", - "amount": { - "value": "300", - "currency_code": "USD" - } - }, - { - "starting_quantity": "1001", - "amount": { - "value": "1000", - "currency_code": "USD" - } - } - ] - } - } - ], - "description": "Zoho Marketing Campaign Plan", - "taxes": { - "percentage": "10", - "inclusive": false - }, - "links": [ - { - "href": "https://api.paypal.com/v1/billing/plans/P-5ML4271244454362WXNWU5NQ", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.paypal.com/v1/billing/plans/P-5ML4271244454362WXNWU5NQ", - "rel": "edit", - "method": "PATCH" - } - ], - "id": "P-7GL4271244454362WXNWU5NQ", - "status": "ACTIVE" - }, - "links": [ - { - "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-55TG7562XN2588878-8YH955435R661687G", - "rel": "self", - "method": "GET", - "encType": "application/json" - }, - { - "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-55TG7562XN2588878-8YH955435R661687G/resend", - "rel": "resend", - "method": "POST", - "encType": "application/json" - } - ], - "event_version": "1.0", - "resource_version": "2.0" -} \ No newline at end of file diff --git a/src/management_api/routers/paypal/paypal.py b/src/management_api/routers/paypal/paypal.py deleted file mode 100644 index 3b76962..0000000 --- a/src/management_api/routers/paypal/paypal.py +++ /dev/null @@ -1,105 +0,0 @@ -import functools - -from fastapi import APIRouter, Request -from starlette.responses import JSONResponse - -from src import paypal_utils -from src.authorize.authorize import NotAuthorized -from src.config import config_instance -from src.database.database_sessions import sessions -from src.database.plans.plans import Subscriptions -from src.management_api.admin.authentication import authenticate_app, get_headers -from src.management_api.models.paypal import PayPalIPN -from src.paypal_utils.paypal_plans import paypal_service -from src.utils.my_logger import init_logger - -paypal_router = APIRouter() -paypal_logger = init_logger("paypal_router") - - -async def verify_paypal_ipn(ipn: PayPalIPN): - """will check if PayPal ipn is verified if not throws an error""" - paypal_logger.info(f"ipn initial data : {ipn}") - verify_data = ipn.dict() - paypal_logger.info(f"verifying ipn : {verify_data}") - response_text = await paypal_utils.verify_ipn(ipn_data=verify_data) - # NOTE Verify if the request comes from PayPal if not Raise Error and exit - if response_text.casefold() != 'VERIFIED'.casefold(): - raise NotAuthorized(message="Invalid IPN Request") - return True - - -@paypal_router.api_route(path="/paypal/subscriptions", methods=["GET"], include_in_schema=True) -# @authenticate_app -async def create_paypal_subscriptions(request: Request): - """ - TODO - create a PayPal Subscriptions Model - :param request: - :return: - """ - response = await paypal_service.create_paypal_billing_plans() - paypal_logger.info(f"Paypal Subscriptions : {response}") - return JSONResponse(content=response, status_code=201) - - -@paypal_router.api_route(path="/_ipn/paypal/{path}", methods=["GET", "POST"], include_in_schema=True) -async def paypal_ipn(request: Request, path: str, ipn: PayPalIPN): - """ - **paypal_ipn** - - this IPN will handle the following events from PayPal and then take the - required actions on the user account related to the PayPal Action / Event - https://gateway.eod-stock-api.site/_admin/_ipn/paypal/activated - https://gateway.eod-stock-api.site/_admin/_ipn/paypal/cancelled - https://gateway.eod-stock-api.site/_admin/_ipn/paypal/created - https://gateway.eod-stock-api.site/_admin/_ipn/paypal/expired-suspended - https://gateway.eod-stock-api.site/_admin/_ipn/paypal/payment-failed - https://gateway.eod-stock-api.site/_admin/_ipn/paypal/reactivated - - :param path: - :param request: - :param ipn: - :return: - """ - paypal_logger.info(f"entry to paypal ipn : {path} with ipn: {ipn}") - paypal_logger.info(F'request data : {await request.body()}') - # NOTE Authenticates the IPN Message - await verify_paypal_ipn(ipn=ipn) - - # Changes Subscription State depending on IPN Message - async def change_subscription_state(_subscription_id: str, state: bool): - """change subscription state depending on the request state""" - with next(sessions) as _session: - # TODO verify that the subscription ID here is the same as the one we had when subscribing - _subscription_instance: Subscriptions = await Subscriptions.get_by_subscription_id( - subscription_id=_subscription_id, session=_session) - _subscription_instance._is_active = state - _session.merge(_subscription_instance) - _session.commit() - return JSONResponse(content={'status': 'OK'}, status_code=200) - - # NOTE: select appropriate state depending on the ipn - _ipn_state_selector = {'cancelled': functools.partial(change_subscription_state, state=False), - 'activated': functools.partial(change_subscription_state, state=True), - 'expired': functools.partial(change_subscription_state, state=False), - 'suspended': functools.partial(change_subscription_state, state=False), - 'payment-failed': functools.partial(change_subscription_state, state=False), - 'reactivated': functools.partial(change_subscription_state, state=False)} - - # TODO consider sending notification Emails triggered by events here - return await _ipn_state_selector.get(path.casefold())(_subscription_id=ipn.custom) - - -@paypal_router.api_route(path="/paypal/settings/{uuid}", methods=["GET"]) -@authenticate_app -async def paypal_settings(request: Request, uuid: str): - """ - **paypal_settings** - This will return the settings for PayPal - :param request: - :param uuid: - :return: - """ - paypal_settings_dict: dict[str, str] = config_instance().PAYPAL_SETTINGS.dict() - _headers = await get_headers(user_data=paypal_settings_dict) - return JSONResponse(content=paypal_settings_dict, status_code=200, headers=_headers) diff --git a/src/management_api/routers/subscriptions/__init__.py b/src/management_api/routers/subscriptions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/routers/subscriptions/subscriptions.py b/src/management_api/routers/subscriptions/subscriptions.py deleted file mode 100644 index 25d1ebb..0000000 --- a/src/management_api/routers/subscriptions/subscriptions.py +++ /dev/null @@ -1,292 +0,0 @@ -from datetime import datetime - -from fastapi import APIRouter, Request -from starlette.responses import JSONResponse - -from src.config import config_instance -from src.const import UUID_LEN -from src.database.account.account import Account -from src.database.apikeys.keys import ApiKeyModel -from src.database.database_sessions import sessions -from src.database.plans.plans import Plans, Subscriptions, Invoices -from src.event_queues.invoice_queue import add_invoice_to_send -from src.management_api.admin.authentication import authenticate_app, get_headers -from src.management_api.email.email import email_process -from src.management_api.models.subscriptions import SubscriptionCreate, SubscriptionUpdate -from src.utils.my_logger import init_logger -from src.utils.utils import create_id, calculate_invoice_date_range, create_api_key - -subscriptions_router = APIRouter() -sub_logger = init_logger("subscriptions_router") - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/subscriptions", methods=["POST"], include_in_schema=True) -@authenticate_app -async def create_subscription(subscription_data: SubscriptionCreate, request: Request): - """ - create and update subscriptions - :param request: - :param subscription_data: - :return: - """ - sub_logger.info("Subscriptions") - - # TODO Refactor this method to include request authorization headers - with next(sessions) as session: - plan_id = subscription_data.plan_id - plan = await Plans.get_plan_by_plan_id(plan_id=plan_id, session=session) - subscribe_dict = subscription_data.dict() - subscribe_dict.update({ - 'subscription_id': create_id(UUID_LEN), - 'api_requests_balance': plan.plan_limit, - 'time_subscribed': datetime.now().timestamp()} - ) - - subscription_instance: Subscriptions = await Subscriptions.create_subscription(_data=subscribe_dict, session=session) - from_date, to_date = calculate_invoice_date_range(today=datetime.now().timestamp()) - today = datetime.now().timestamp() - - invoice_data = { - 'subscription_id': subscription_instance.subscription_id, - 'invoice_id': create_id(UUID_LEN), - 'invoiced_amount': plan.charge_amount, - 'invoice_from_date': from_date, - 'invoice_to_date': to_date, - 'time_issued': today - } - # TODO invoices must indicate that they are paid invoices - # TODO in future i must create invoices for clients which did not yet pay and send them to their inbox - invoice: Invoices = await Invoices.create_invoice(_data=invoice_data, session=session) - - session.add(subscription_instance) - session.commit() - session.add(invoice) - account = await Account.get_by_uuid(uuid=subscription_data.uuid, session=session) - await add_invoice_to_send(invoice=invoice.to_dict(), account=account.to_dict()) - - session.commit() - session.flush() - # this last step creates a billing in paypal the client app must redirect the user to the url for verifying - # the billing - # subscription_instance = await paypal_service.create_paypal_billing(plan=plan, - # subscription=subscription_instance) - ADMIN = config_instance().EMAIL_SETTINGS.ADMIN - sub_dict = dict(sender_email=ADMIN, recipient_email=account.email, client_name=account.name, - plan_name=plan.name) - await email_process.send_subscription_welcome_email(**sub_dict) - payload = dict(status=True, - payload=subscription_instance.to_dict(), - message="successfully created a new subscription") - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=201, headers=_headers) - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/subscriptions", methods=["PUT"], include_in_schema=True) -@authenticate_app -async def update_subscription(subscription_data: SubscriptionUpdate, request: Request): - """ - Upgrade or Downgrade Plan, thi only affect the net invoice - """ - with next(sessions) as session: - plan_id: str = subscription_data.plan_id - plan: Plans = await Plans.get_plan_by_plan_id(plan_id=plan_id, session=session) - - subscription_id = subscription_data.subscription_id - subscription_instance: Subscriptions = await Subscriptions.get_by_subscription_id( - subscription_id=subscription_id, session=session) - - if subscription_instance.plan_id != plan_id: - # create a method for upgrading or downgrading plan - subscription_instance.api_requests_balance = plan.plan_limit - subscription_instance.plan_id = plan_id - subscription_instance.time_subscribed = datetime.now().timestamp() - session.merge(subscription_instance) - session.commit() - else: - # TODO update only changed fields - subscription_data_dict = subscription_data.dict(exclude_unset=True) - for field, value in subscription_data_dict.items(): - setattr(subscription_instance, field, value) - session.merge(subscription_instance) - session.commit() - - payload = dict(status=True, payload=subscription_instance.to_dict(), message="subscription updated") - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=201, headers=_headers) - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/subscription/{subscription_id}", methods=["DELETE"], include_in_schema=True) -@authenticate_app -async def de_activate_subscriptions(subscription_id: str, request: Request): - """ - de activate subscriptions - the delete action may usually mark records as deleted - :param request: - :param subscription_id: - - :return: - """ - with next(sessions) as session: - subscription_instance: Subscriptions = await Subscriptions.get_by_subscription_id( - subscription_id=subscription_id, - session=session) - - if subscription_instance: - subscription_instance.set_is_active(is_active=False) - session.merge(subscription_instance) - session.commit() - payload = dict(status=True, payload=subscription_instance.to_dict(), - message='successfully deactivated subscription') - - _headers = await get_headers(user_data=payload) - - else: - payload = dict(status=True, payload={}, - message='Successfully retrieved subscription') - _headers = await get_headers(user_data=payload) - - return JSONResponse(content=payload, status_code=200, headers=_headers) - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/subscription/{subscription_id}", methods=["DELETE"], include_in_schema=True) -@authenticate_app -async def re_activate_subscriptions(subscription_id: str, request: Request): - """ - **re_activate_subscription** - re-activate previously de-activated subscription - - :param request: - :param subscription_id: id of the subscription to activate - - :return: - """ - with next(sessions) as session: - subscription_instance: Subscriptions = await Subscriptions.get_by_subscription_id( - subscription_id=subscription_id, - session=session) - - if subscription_instance: - subscription_instance.set_is_active(is_active=True) - session.merge(subscription_instance) - session.commit() - payload = dict(status=True, payload=subscription_instance.to_dict(), - message='successfully activated subscription') - - _headers = await get_headers(user_data=payload) - - else: - payload = dict(status=True, payload={}, - message='Successfully retrieved subscription') - _headers = await get_headers(user_data=payload) - - return JSONResponse(content=payload, status_code=200, headers=_headers) - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/subscription/{subscription_id}", methods=["DELETE"], include_in_schema=True) -@authenticate_app -async def get_subscription(subscription_id: str, request: Request): - """ - **get_subscription** - - retrieve or delete subscriptions - the delete action may usually mark records as deleted - - :param request: - :param subscription_id: id of the subscription to fetch - - :return: - """ - with next(sessions) as session: - subscription_instance = await Subscriptions.get_by_subscription_id(subscription_id=subscription_id, - session=session) - if subscription_instance: - payload = dict(status=True, payload=subscription_instance.to_dict(), - message='Successfully retrieved subscription') - _headers = await get_headers(user_data=payload) - else: - payload = dict(status=True, payload={}, - message='Successfully retrieved subscription') - _headers = await get_headers(user_data=payload) - - return JSONResponse(content=payload, status_code=200, headers=_headers) - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/plans/{plan_id}", methods=["GET"], include_in_schema=True) -@authenticate_app -async def get_plan(plan_id: str, request: Request): - """ - - :param plan_id: - :param request: - :return: - """ - with next(sessions) as session: - plan_instance = await Plans.get_plan_by_plan_id(plan_id=plan_id, session=session) - if plan_instance: - payload = dict(status=True, payload=plan_instance.to_dict(), message='Successfully retrieved plan') - _headers = await get_headers(user_data=payload) - else: - payload = dict(status=True, payload={}, - message='Successfully retrieved subscription') - _headers = await get_headers(user_data=payload) - - return JSONResponse(content=payload, status_code=200, headers=_headers) - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/plans", methods=["GET"], include_in_schema=True) -@authenticate_app -async def get_all_plans(request: Request): - """ - returns all plans - :param request: - :return: - """ - with next(sessions) as session: - plan_instance_list = await Plans.get_all_plans(session=session) - _payload = [plan.to_dict() for plan in plan_instance_list] if plan_instance_list else [] - sub_logger.info(f"GET ALL PLANS : {_payload}") - - payload = dict(status=True, message='Successfully retrieved plan') - _headers = await get_headers(user_data=payload) - payload.update(payload=_payload) - - return JSONResponse(content=payload, status_code=200, headers=_headers) - - -# noinspection PyUnusedLocal -@subscriptions_router.api_route(path="/apikey/{apikey}", methods=["POST"], include_in_schema=True) -@authenticate_app -async def update_apikey(request: Request, apikey: str): - """ - - :param apikey: - :param request: - :return: - """ - with next(sessions) as session: - apikey_instance: ApiKeyModel = await ApiKeyModel.get_by_apikey(api_key=apikey, session=session) - if apikey_instance is None: - message: str = f"Could not find ApiKey : {apikey}" - sub_logger.info(message) - payload = dict(status=False, message=message) - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=401, headers=_headers) - apikey_instance.api_key = create_api_key() - - session.merge(apikey_instance) - session.commit() - session.flush() - - message: str = f"Updated ApiKey : {apikey} to : {apikey_instance.api_key}" - sub_logger.info(message) - payload = dict(status=True, payload=apikey_instance.to_dict(), message=message) - - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=201, headers=_headers) diff --git a/src/management_api/routers/users/__init__.py b/src/management_api/routers/users/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/management_api/routers/users/users.py b/src/management_api/routers/users/users.py deleted file mode 100644 index 86b4844..0000000 --- a/src/management_api/routers/users/users.py +++ /dev/null @@ -1,217 +0,0 @@ -import re - -import pymysql.err -from fastapi import APIRouter, Request -from starlette.responses import JSONResponse - -from src.config import config_instance -from src.database.account.account import Account -from src.database.database_sessions import sessions -from src.management_api.admin.authentication import authenticate_app, get_headers -from src.management_api.email.email import email_process -from src.management_api.models.users import AccountUpdate, AccountCreate, UserResponseSchema, DeleteResponseSchema, \ - UsersResponseSchema -from src.utils.my_logger import init_logger - -users_router = APIRouter() -users_logger = init_logger("users_router") - - -# noinspection PyUnusedLocal -@users_router.api_route(path="/user", methods=["POST"], include_in_schema=True) -@authenticate_app -async def create_user(new_user: AccountCreate, request: Request) -> UserResponseSchema: - """ - **create user** - used to create new user record - - :param request: - :param new_user: data containing user information - - :return: status payload, message -> see Responses - """ - users_logger.info(f"Creating user : {new_user.dict()}") - with next(sessions) as session: - email: str = new_user.email - user_instance: Account | None = await Account.get_by_email(email=email, session=session) - - if isinstance(user_instance, Account) and bool(user_instance): - users_logger.info(f'User Found: ') - payload = dict(status=False, payload={}, message="Error User Already Exists - Please login to proceed") - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=201, headers=_headers) - - new_user_instance: Account = Account(**new_user.dict()) - if not bool(new_user_instance): - users_logger.error(f'User Not Created: ') - payload = dict(status=False, payload={}, message="Error Unable to create User - Please try again later") - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=401, headers=_headers) - - users_logger.info(f'Saving User To DATABASE: ') - try: - session.add(new_user_instance) - session.commit() - except pymysql.err.IntegrityError as e: - duplicate_data = re.findall(r"\+(\d+)", e)[0] - message: str = f"Cannot Accept {duplicate_data} as it is already used, by another user" - payload = dict(status=False, payload={}, message=message) - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=401, headers=_headers) - - session.flush() - - users_logger.info(f'Converting to dict: ') - _payload: dict[str, str | dict[str, str]] = new_user_instance.to_dict() - users_logger.info(f"Created NEW USER : {_payload}") - - # this will schedule an account confirmation email to be sent - verification_link: str = f"https://gateway.eod-stock-api.site/_admin/account/confirm/{new_user_instance.uuid}" - - sender_email: str = config_instance().EMAIL_SETTINGS.ADMIN - recipient_email: str = new_user_instance.email - client_name: str = new_user_instance.names - message_dict: dict[str, str] = dict(verification_link=verification_link, sender_email=sender_email, - recipient_email=recipient_email, client_name=client_name) - try: - users_logger.info(f'Sending confirmation email : {message_dict}') - await email_process.send_account_confirmation_email(**message_dict) - except Exception as e: - users_logger.info(f'Failed to send confirmation email : {message_dict}') - - payload: dict[str, str] = dict(status=True, payload=_payload, message="Successfully Created Account - Please Login to Proceed") - _headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=201, headers=_headers) - - -# noinspection PyUnusedLocal -@users_router.api_route(path="/user", methods=["PUT"], include_in_schema=True) -@authenticate_app -async def update_user(user_data: AccountUpdate, request: Request) -> UserResponseSchema: - """ - **update_user** - given some fields on user_data update the user instance with those fields - - param: user_data: {dict} - - return: status: {boolean}, payload: {dict}, message: {string} - """ - with next(sessions) as session: - uuid = user_data.uuid - user_instance: Account = await Account.get_by_uuid(uuid=uuid, session=session) - if not bool(user_instance): - payload = dict(status=True, payload={}, message="Unable to update account") - headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=401, headers=headers) - - updated_dict = user_data.dict(exclude_unset=True) - for field, value in updated_dict.items(): - setattr(user_instance, field, value) - - session.merge(user_instance) - session.commit() - session.flush() - - payload = dict(status=True, payload=user_instance.to_dict(), message="account updated successfully") - - headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=201, headers=headers) - - -# noinspection PyUnusedLocal -@users_router.api_route(path="/user/{uuid}", methods=["GET"], include_in_schema=True) -@authenticate_app -async def get_user(uuid: str, request: Request) -> UserResponseSchema: - """ - **get_user** - will retrieve user data - :param request: - :param uuid: - :return: UserResponseSchema - """ - with next(sessions) as session: - user_instance: Account = await Account.get_by_uuid(uuid=uuid, session=session) - - if not bool(user_instance): - payload = dict(status=True, payload={}, message="Unable to get account") - headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=401, headers=headers) - - user_dict = user_instance.to_dict() - users_logger.info(f"user found : {user_dict}") - - payload = dict(status=True, payload=user_dict, message="user found") - headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=200, headers=headers) - - -# noinspection PyUnusedLocal -@users_router.api_route(path="/user/{uuid}", methods=["DELETE"], include_in_schema=True) -@authenticate_app -async def delete_user(uuid: str, request: Request) -> DeleteResponseSchema: - """ - **delete_user** - used to mark a user as deleted - :return: DeleteResponseSchema - """ - with next(sessions) as session: - user_instance: Account = await Account.get_by_uuid(uuid=uuid, session=session) - if not bool(user_instance): - payload = dict(status=True, payload={}, message="Unable to find account") - headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, status_code=401, headers=headers) - - user_instance.is_deleted = True - session.merge(user_instance) - session.commit() - session.flush() - # TODO send a Goodbye Email - message = {'message': 'successfully deleted user'} - headers = await get_headers(user_data=message) - return JSONResponse(content=message, - status_code=201, - headers=headers) - - -# noinspection PyUnusedLocal -@users_router.api_route(path="/users", methods=["GET"], include_in_schema=True) -@authenticate_app -async def get_all_users(request: Request) -> UsersResponseSchema: - """ - **delete_user** - will return a complete list of all users of the api - - :return: dict of status: boolean, list of users, and message indicating how the operation went - """ - with next(sessions) as session: - users_list: Account = await Account.fetch_all(session=session) - users = [AccountUpdate.from_orm(user) for user in users_list] - payload = dict(status=True, payload=[user.dict() for user in users], message='successfully fetched all user') - - headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, - status_code=201, - headers=headers) - - -# noinspection PyUnusedLocal -@users_router.api_route(path="/users/subscription/{is_active}", methods=["GET"], include_in_schema=True) -@authenticate_app -async def get_users_by_subscription_status(is_active: bool, request: Request) -> UsersResponseSchema: - """ - **get users by subscription status** - accepts status to filter subscriptions by - - :return: dict of status: boolean, list of users, and message indicating how the operation went - """ - status = bool(is_active) - with next(sessions) as session: - users_list: Account = await Account.fetch_all(session=session) - users_list = [user for user in users_list if user.apikey.is_active == status] - payload = dict(status=True, payload=[user.dict() for user in users_list], - message=f'successfully fetched all user by status = {status}') - - headers = await get_headers(user_data=payload) - return JSONResponse(content=payload, - status_code=201, - headers=headers) diff --git a/src/management_api/routes.py b/src/management_api/routes.py deleted file mode 100644 index f3988cc..0000000 --- a/src/management_api/routes.py +++ /dev/null @@ -1,135 +0,0 @@ -from fastapi import Request, FastAPI -from fastapi.responses import JSONResponse - -from src.authentication import authenticate_app, authenticate_cloudflare_workers -from src.authorize.authorize import NotAuthorized -from src.database.apikeys.keys import ApiKeyModel -from src.database.database_sessions import sessions -from src.database.plans.plans import Plans -from src.management_api.admin.authentication import get_headers -from src.management_api.routers.authorization.authorization import auth_router -from src.management_api.routers.contact.contact_route import contact_router -from src.management_api.routers.logs.logs import log_router -from src.management_api.routers.paypal.paypal import paypal_router -from src.management_api.routers.subscriptions.subscriptions import subscriptions_router -from src.management_api.routers.users.users import users_router -from src.utils.my_logger import init_logger - -management_logger = init_logger("management_api") -admin_app = FastAPI( - title="EOD-STOCK-API - CLIENT ADMINISTRATOR API", - description="Client Administration API for EOD Stock API", - version="0.0.12", - terms_of_service="https://www.eod-stock-api.site/terms", - contact={ - "name": "MJ API Development", - "url": "https://www.eod-stock-api.site/contact", - "email": "info@eod-stock-api.site" - }, - license_info={ - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html", - }, - docs_url="/docs", - redoc_url="/redoc", - openapi_url="/open-api" -) - - -@admin_app.middleware(middleware_type="http") -async def check_if_valid_request(request: Request, call_next): - """ - **check_if_valid_request** - Admin API Validations - :param request: - :param call_next: - :return: - """ - path = request.url - management_logger.info(f"on entry into Management API : {path} method: {request.method}") - response = await call_next(request) - management_logger.info(f"on exit from Management API : {response.status_code} {str(response)}") - return response - - -admin_app.include_router(users_router) -admin_app.include_router(auth_router) -admin_app.include_router(log_router) -admin_app.include_router(paypal_router) -admin_app.include_router(subscriptions_router) -admin_app.include_router(contact_router) - - -@admin_app.api_route(path="/plans", methods=["GET"], include_in_schema=True) -@authenticate_app -async def get_client_plans(): - """ - :return: - """ - with next(sessions) as session: - plan_list = await Plans.get_all_plans(session=session) - payload = [plan.to_dict() for plan in plan_list] - - return JSONResponse(content=payload, status_code=200) - - -@admin_app.on_event("startup") -async def admin_startup(): - """ - **admin_startup** - ADMIN API startup procedures - :return: - """ - # Needs more processes here - print("admin app started") - # asyncio.create_task(process_invoice_queues()) - - -@admin_app.api_route(path="/cloudflare/init-gateway", methods=["GET", "POST"], include_in_schema=False) -@authenticate_cloudflare_workers -async def init_cloudflare_gateway(): - """ - **init_cloudflare_gateway** - at the gateway - initialize cloudflare by allowing the retrieval of known api keys - so they can be tested for validity from the edge server - - :return: - """ - with next(sessions) as session: - api_keys = await ApiKeyModel.get_all_active(session=session) - payload = [api_key.to_dict()['api_key'] for api_key in api_keys] - - return JSONResponse(content=dict(status=True, api_keys=payload), status_code=200) - - -# noinspection PyUnusedLocal -@admin_app.exception_handler(NotAuthorized) -async def admin_not_authorized(request: Request, exc: NotAuthorized): - """ - **admin_not_authorized** - This exception will be thrown whenever Authorization has not passed. - :param request: - :param exc: - :return: - """ - user_data = {"message": exc.message} - management_logger.info(f"Error occurred: {str(exc)}") - return JSONResponse( - status_code=exc.status_code, - content=user_data, headers=await get_headers(user_data)) - - -# noinspection PyUnusedLocal -@admin_app.exception_handler(Exception) -async def handle_all_exceptions(request: Request, exc: Exception): - management_logger.info(f"Error processing request : {str(exc)}") - error_data = {'message': 'error processing request'} - return JSONResponse(content=error_data, - status_code=500, - headers=await get_headers(error_data)) - - -# noinspection PyUnusedLocal -@admin_app.get("/_ah/warmup", include_in_schema=False) -async def status_check(request: Request): - return JSONResponse(content={'status': 'OK'}, status_code=200, headers={"Content-Type": "application/json"}) diff --git a/src/paypal_utils/__init__.py b/src/paypal_utils/__init__.py deleted file mode 100644 index 04da2a8..0000000 --- a/src/paypal_utils/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -import aiohttp - -from src.config import config_instance - - -async def verify_ipn(ipn_data): - """ - Verify that an IPN is valid by sending it back to PayPal. - - :param ipn_data: The IPN data received from PayPal. - :return: The response from PayPal ("VERIFIED" or "INVALID"). - """ - PAYPAL_VERIFY_URL = "https://ipnpb.paypal.com/cgi-bin/webscr" - PAYPAL_TOKEN = f"Bearer {config_instance().PAYPAL_SETTINGS.BEARER_TOKEN}" - # TODO verify if token does not need refreshing after sometime - # Add 'cmd=_notify-validate' parameter for verification - ipn_data["cmd"] = "_notify-validate" - header = {'Authentication': PAYPAL_TOKEN} - # TODO there are some additional information i need to include on the IPN verification - async with aiohttp.ClientSession() as session: - async with session.post(url=PAYPAL_VERIFY_URL, json=ipn_data, headers=header) as resp: - return await resp.text() diff --git a/src/paypal_utils/paypal_plans.py b/src/paypal_utils/paypal_plans.py deleted file mode 100644 index 566ab12..0000000 --- a/src/paypal_utils/paypal_plans.py +++ /dev/null @@ -1,115 +0,0 @@ -import paypalrestsdk -from paypalrestsdk import BillingPlan, BillingAgreement -from src.config import config_instance -from src.database.database_sessions import sessions -from src.database.plans.plans import Plans, Subscriptions - -paypal_config = dict(client_id=config_instance().PAYPAL_SETTINGS.CLIENT_ID, - client_secret=config_instance().PAYPAL_SETTINGS.CLIENT_SECRET, - mode=config_instance().PAYPAL_SETTINGS.MODE) - - -class PayPalService: - """ - PayPal Service - """ - def __init__(self): - self.paypal_api = paypalrestsdk.configure(paypal_config) - self.client_plans = self.load_plans() - - @staticmethod - def load_plans() -> list[Plans]: - with next(sessions) as session: - plans_list = Plans.fetch_all(session=session) - return plans_list - - @staticmethod - async def create_paypal_billing_plans(): - # Run Once to create PayPal Service - Billing Plans - with next(sessions) as session: - plans_list = Plans.fetch_all(session=session) - for plan in plans_list: - plan_attr = { - "name": plan.plan_name, - "description": plan.description, - "type": "INFINITE", - "payment_definitions": [ - { - "name": "Monthly Payment", - "type": "REGULAR", - "frequency": "MONTH", - "frequency_interval": "1", - "amount": { - "currency": "USD", - "value": f"{plan.charge_amount}" - }, - "cycles": "0", - "charge_models": [ - { - "type": "SHIPPING", - "amount": { - "currency": "USD", - "value": "0.00" - } - } - ] - } - ], - "merchant_preferences": { - "setup_fee": { - "currency": "USD", - "value": "1.00" - }, - "cancel_url": "https://gateway.eod-stock-api.site/_ipn/paypal/cancel", - "return_url": "https://gateway.eod-stock-api.site/_ipn/paypal/success", - "auto_bill_amount": "YES", - "initial_fail_amount_action": "CONTINUE", - "max_fail_attempts": "3" - } - } - _billing_plan = BillingPlan(plan_attr) - if _billing_plan.create(): - plan.paypal_id = _billing_plan.id - _billing_plan.activate() - # TODO you may need to save billing plans on own database - session.merge(plan) - session.commit() - session.flush() - - @staticmethod - async def create_paypal_billing(plan: Plans, subscription: Subscriptions) -> Subscriptions: - """ - create user subscription upon user selection on the client website and then send - the client to the approval url in order to approve the billing - :param subscription: - :param plan: - :return: - """ - sub_attrs = { - "name": f"{plan.name} Subscription", - "description": f"{plan.description}", - "start_date": f"{subscription.start_date}", - "payer": { - "payment_method": "paypal" - }, - "plan": { - "id": f"{plan.paypal_id}" - } - } - with next(sessions) as session: - _subscription = BillingAgreement(sub_attrs) - if _subscription.create(): - subscription.paypal_id = _subscription.id - for link in _subscription.links: - if link.rel == "approval_url": - approval_url = str(link.href) - subscription.approval_link = approval_url - session.merge(subscription) - session.commit() - session.flush() - return subscription - - # TODO Redirect client to approval url - - -paypal_service = PayPalService() diff --git a/src/prefetch/__init__.py b/src/prefetch/__init__.py deleted file mode 100644 index 2064842..0000000 --- a/src/prefetch/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -import asyncio - -from src.cache.cache import redis_cache -from src.prefetch.dynamic_urls import build_dynamic_urls -from src.requests import requester - - -async def prefetch_endpoints() -> int: - """will fetch the endpoints causing the endpoints to be cached""" - urls = await build_dynamic_urls() - # will wait for a maximum of 30 seconds for a response - i = 0 - for url in urls: - response = await requester(api_url=url, timeout=60*5) - if response and response.get("status", False): - await redis_cache.set(key=url, value=response) - # this enables the gateway to process other requests while still prefetching urls - await asyncio.sleep(delay=3) - i += 1 - return i diff --git a/src/prefetch/dynamic_urls.py b/src/prefetch/dynamic_urls.py deleted file mode 100644 index 43ee5f6..0000000 --- a/src/prefetch/dynamic_urls.py +++ /dev/null @@ -1,85 +0,0 @@ -# Prefetch endpoints -import random - -from httpx import HTTPError -from src.config import config_instance -from src.prefetch.exchange_lists import cached_exchange_lists -from src.requests import requester -from src.cache.cache import redis_cached_ttl -from src.utils.my_logger import init_logger - -prefetch_logger = init_logger("prefetching") -ONE_DAY = 60 * 60 * 24 - -api_server_urls = [config_instance().API_SERVERS.MASTER_API_SERVER, config_instance().API_SERVERS.SLAVE_API_SERVER] - -PREFETCH_ENDPOINTS = [ - '/api/v1/exchanges', - '/api/v1/stocks', - '/api/v1/fundamental/general'] - - -@redis_cached_ttl(60*60*24) -async def get_exchange_lists(): - try: - server_url = random.choice(api_server_urls) - api_url = f"{server_url}/api/v1/exchanges" - data = await requester(api_url=api_url) - if data: - payload = data.get("payload") - status = data.get("status") - if status and payload: - return payload - return cached_exchange_lists - except HTTPError: - return cached_exchange_lists - - -async def get_exchange_codes(): - return [exchange.get("code") for exchange in await get_exchange_lists()] - - -async def get_exchange_ids(): - return [exchange.get("exchange_id") for exchange in await get_exchange_lists()] - - -async def get_currencies(): - return [exchange.get("currency_symbol") for exchange in await get_exchange_lists()] - - -async def get_countries(): - return [exchange.get("country") for exchange in await get_exchange_lists()] - - -@redis_cached_ttl(ttl=60*60*24) -async def build_dynamic_urls() -> list[str]: - """ - dynamic urls are cached for 24 hours - dynamic urls to prefetch - :return: - """ - # TODO - interesting that i could use this list for some other things such as request validation - codes_urls = ['/api/v1/exchange/listed-stocks/', '/api/v1/stocks/exchange/code/'] - exchange_by_id_url = "/api/v1/stocks/exchange/id/" - stocks_by_currency_url = "/api/v1/stocks/currency/" - stocks_by_country_url = "/api/v1/stocks/country/" - - expanded_urls = [] - for server_url in api_server_urls: - for endpoint in codes_urls: - for code in await get_exchange_codes(): - if code and code != "null": - expanded_urls.append(f"{server_url}{endpoint}{code}") - for _id in await get_exchange_ids(): - if _id and _id != "null": - expanded_urls.append(f"{server_url}{exchange_by_id_url}{_id}") - for _currency in await get_currencies(): - if _currency and _currency != "null": - expanded_urls.append(f"{server_url}{stocks_by_currency_url}{_currency}") - for _country in await get_countries(): - if _country and _country != "null": - expanded_urls.append(f"{server_url}{stocks_by_country_url}{_country}") - for _url in PREFETCH_ENDPOINTS: - expanded_urls.append(f"{server_url}{_url}") - - return expanded_urls diff --git a/src/prefetch/exchange_lists.py b/src/prefetch/exchange_lists.py deleted file mode 100644 index 5bbe21d..0000000 --- a/src/prefetch/exchange_lists.py +++ /dev/null @@ -1,594 +0,0 @@ -cached_exchange_lists = [ - { - "code": "BA", - "country": "Argentina", - "currency_symbol": "ARS", - "exchange_id": "RP5zfbmYel63f0Wc", - "name": "Buenos Aires Exchange", - "operating_mic": "XBUE" - }, - { - "code": "AU", - "country": "Australia", - "currency_symbol": "AUD", - "exchange_id": "Tl2hlbpejIQflm94", - "name": "Australia Exchange", - "operating_mic": "XASX" - }, - { - "code": "VI", - "country": "Austria", - "currency_symbol": "EUR", - "exchange_id": "LmSa3cscWaSPXc5D", - "name": "Vienna Exchange", - "operating_mic": "XWBO" - }, - { - "code": "EUBOND", - "country": "Belgium", - "currency_symbol": "EUR", - "exchange_id": "xKY5VqCL2dK1wXZL", - "name": "EU Bond Virtual Exchange", - "operating_mic": None - }, - { - "code": "BR", - "country": "Belgium", - "currency_symbol": "EUR", - "exchange_id": "66ksM2wHta7mRLns", - "name": "Euronext Brussels", - "operating_mic": "XBRU" - }, - { - "code": "SA", - "country": "Brazil", - "currency_symbol": "BRL", - "exchange_id": "nlRDVmQ7YjRROqjO", - "name": "Sao Paolo Exchange", - "operating_mic": "BVMF" - }, - { - "code": "NEO", - "country": "Canada", - "currency_symbol": "CAD", - "exchange_id": "lA9HtGfHm3RxKWV0", - "name": "NEO Exchange", - "operating_mic": "NEOE" - }, - { - "code": "TO", - "country": "Canada", - "currency_symbol": "CAD", - "exchange_id": "WPqMzJHtpJnPmB0E", - "name": "Toronto Exchange", - "operating_mic": "XTSE" - }, - { - "code": "V", - "country": "Canada", - "currency_symbol": "CAD", - "exchange_id": "vyHhUtDiKF2TL0vh", - "name": "TSX Venture Exchange", - "operating_mic": "XTSX" - }, - { - "code": "SN", - "country": "Chile", - "currency_symbol": "CLP", - "exchange_id": "90pZEGoHenFJlJvr", - "name": "Chilean Stock Exchange", - "operating_mic": "XSGO" - }, - { - "code": "SHG", - "country": "China", - "currency_symbol": "CNY", - "exchange_id": "4xeDYPg8WTJ8q5cp", - "name": "Shanghai Exchange", - "operating_mic": "XSHG" - }, - { - "code": "SHE", - "country": "China", - "currency_symbol": "CNY", - "exchange_id": "PTXIlhGNdSIAJH8A", - "name": "Shenzhen Exchange", - "operating_mic": "XSHE" - }, - { - "code": "ZSE", - "country": "Croatia", - "currency_symbol": "EUR", - "exchange_id": "icBmpIzojb2ZESPe", - "name": "Zagreb Stock Exchange", - "operating_mic": "XZAG" - }, - { - "code": "PR", - "country": "Czech Republic", - "currency_symbol": "CZK", - "exchange_id": "NCQ4KhNZBGViIHjY", - "name": "Prague Stock Exchange ", - "operating_mic": "XPRA" - }, - { - "code": "CO", - "country": "Denmark", - "currency_symbol": "DKK", - "exchange_id": "wSQc1tUJUeTA3rFd", - "name": "Copenhagen Exchange", - "operating_mic": "XCSE" - }, - { - "code": "HE", - "country": "Finland", - "currency_symbol": "EUR", - "exchange_id": "qk5mADFnav3OQje6", - "name": "Helsinki Exchange", - "operating_mic": "XHEL" - }, - { - "code": "PA", - "country": "France", - "currency_symbol": "EUR", - "exchange_id": "0j77SEouifcXFUj9", - "name": "Euronext Paris", - "operating_mic": "XPAR" - }, - { - "code": "BE", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "Io4WbG2POC03xoou", - "name": "Berlin Exchange", - "operating_mic": "XBER" - }, - { - "code": "DU", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "Gl8XvD7jzjpvrOiz", - "name": "Dusseldorf Exchange", - "operating_mic": "XDUS" - }, - { - "code": "F", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "5flEgKq6zF3egBJ4", - "name": "Frankfurt Exchange", - "operating_mic": "XFRA" - }, - { - "code": "HM", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "DIr6XmO7Ekgf3xZY", - "name": "Hamburg Exchange", - "operating_mic": "XHAM" - }, - { - "code": "HA", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "QbUfxddkuAf92BpV", - "name": "Hanover Exchange", - "operating_mic": "XHAN" - }, - { - "code": "MU", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "RMO9rrinzSNfGoTh", - "name": "Munich Exchange", - "operating_mic": "XMUN" - }, - { - "code": "STU", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "R1KaRomYKNDZw7kF", - "name": "Stuttgart Exchange", - "operating_mic": "XSTU" - }, - { - "code": "XETRA", - "country": "Germany", - "currency_symbol": "EUR", - "exchange_id": "5mVxUUxl17lXkbxb", - "name": "XETRA Exchange", - "operating_mic": "XETR" - }, - { - "code": "AT", - "country": "Greece", - "currency_symbol": "EUR", - "exchange_id": "7ilTcz0lXtMSqIxV", - "name": "Athens Exchange", - "operating_mic": "ASEX" - }, - { - "code": "HK", - "country": "Hong Kong", - "currency_symbol": "HKD", - "exchange_id": "ecUnBkCMNlCHNFBV", - "name": "Hong Kong Exchange", - "operating_mic": "XHKG" - }, - { - "code": "BUD", - "country": "Hungary", - "currency_symbol": "HUF", - "exchange_id": "myhJmJd4X9k8PIz1", - "name": "Budapest Stock Exchange", - "operating_mic": "XBUD" - }, - { - "code": "IC", - "country": "Iceland", - "currency_symbol": "ISK", - "exchange_id": "4l69uLTntejXBANf", - "name": "Iceland Exchange", - "operating_mic": "XICE" - }, - { - "code": "BSE", - "country": "India", - "currency_symbol": "INR", - "exchange_id": "GrGvKxrQJcEb4NJE", - "name": "Bombay Exchange", - "operating_mic": "XBOM" - }, - { - "code": "NSE", - "country": "India", - "currency_symbol": "INR", - "exchange_id": "Nndl7maA17tghYFN", - "name": "NSE (India)", - "operating_mic": "XNSE" - }, - { - "code": "JK", - "country": "Indonesia", - "currency_symbol": "IDR", - "exchange_id": "CB9M04p16gieLuh9", - "name": "Jakarta Exchange", - "operating_mic": "XIDX" - }, - { - "code": "IR", - "country": "Ireland", - "currency_symbol": "EUR", - "exchange_id": "nOO6TIsc9iuepmTE", - "name": "Irish Exchange", - "operating_mic": "XDUB" - }, - { - "code": "TA", - "country": "Israel", - "currency_symbol": "ILS", - "exchange_id": "wKuqcEkBZXHbhzQC", - "name": "Tel Aviv Exchange", - "operating_mic": "XTAE" - }, - { - "code": "MI", - "country": "Italy", - "currency_symbol": "EUR", - "exchange_id": "b0Ryno5rQcjNgi1U", - "name": "Borsa Italiana", - "operating_mic": "XMIL" - }, - { - "code": "TSE", - "country": "Japan", - "currency_symbol": "JPY", - "exchange_id": "joY1CTw7wNpZqJZA", - "name": "Tokyo Stock Exchange", - "operating_mic": "XJPX" - }, - { - "code": "KO", - "country": "Korea", - "currency_symbol": "KRW", - "exchange_id": "S1Uq7006jpHHEulx", - "name": "Korea Stock Exchange", - "operating_mic": "XKRX" - }, - { - "code": "KQ", - "country": "Korea", - "currency_symbol": "KRW", - "exchange_id": "VUlcbNCCPBKJvSDd", - "name": "KOSDAQ", - "operating_mic": "XKOS" - }, - { - "code": "LU", - "country": "Luxembourg", - "currency_symbol": "EUR", - "exchange_id": "qbBwqsXbfdZWlYcJ", - "name": "Luxembourg Stock Exchange", - "operating_mic": "XLUX" - }, - { - "code": "KLSE", - "country": "Malaysia", - "currency_symbol": "MYR", - "exchange_id": "cysTFUbV4eVChZ5n", - "name": "Kuala Lumpur Exchange", - "operating_mic": "XKLS" - }, - { - "code": "MX", - "country": "Mexico", - "currency_symbol": "MXN", - "exchange_id": "nM5SCa8VZCoB7cYE", - "name": "Mexican Exchange", - "operating_mic": "XMEX" - }, - { - "code": "AS", - "country": "Netherlands", - "currency_symbol": "EUR", - "exchange_id": "6UfIHcQ6EhE4NuPj", - "name": "Euronext Amsterdam", - "operating_mic": "XAMS" - }, - { - "code": "OL", - "country": "Norway", - "currency_symbol": "NOK", - "exchange_id": "x6wOQHrT58ohmibr", - "name": "Oslo Stock Exchange", - "operating_mic": "XOSL" - }, - { - "code": "KAR", - "country": "Pakistan", - "currency_symbol": "PKR", - "exchange_id": "LMFO5J8uTfRt1VLI", - "name": "Karachi Stock Exchange", - "operating_mic": "XKAR" - }, - { - "code": "LIM", - "country": "Peru", - "currency_symbol": "PEN", - "exchange_id": "FB1n7jvAFHAJ2RG0", - "name": "Bolsa de Valores de Lima", - "operating_mic": "XLIM" - }, - { - "code": "PSE", - "country": "Philippines", - "currency_symbol": "PHP", - "exchange_id": "URSgoEqNq9pswChJ", - "name": "Philippine Stock Exchange", - "operating_mic": "XPHS" - }, - { - "code": "WAR", - "country": "Poland", - "currency_symbol": "PLN", - "exchange_id": "EXIuJG0xGd8GpbfQ", - "name": "Warsaw Stock Exchange", - "operating_mic": "XWAR" - }, - { - "code": "LS", - "country": "Portugal", - "currency_symbol": "EUR", - "exchange_id": "gid79gYd6paOENqr", - "name": "Euronext Lisbon", - "operating_mic": "XLIS" - }, - { - "code": "RO", - "country": "Romania", - "currency_symbol": "RON", - "exchange_id": "DJCNqK9C7wB7RFjK", - "name": "Bucharest Stock Exchange", - "operating_mic": "XBSE" - }, - { - "code": "MCX", - "country": "Russia", - "currency_symbol": "RUB", - "exchange_id": "ru26tlEiQsp0S14D", - "name": "MICEX Moscow Russia", - "operating_mic": None - }, - { - "code": "SR", - "country": "Saudi Arabia", - "currency_symbol": "SAR", - "exchange_id": "AkPcNdmJ40JKV6Rn", - "name": "Saudi Arabia Exchange", - "operating_mic": "XSAU" - }, - { - "code": "SG", - "country": "Singapore", - "currency_symbol": "SGD", - "exchange_id": "CfG57zw9lZDogBY2", - "name": "Singapore Exchange", - "operating_mic": "XSES" - }, - { - "code": "JSE", - "country": "South Africa", - "currency_symbol": "ZAR", - "exchange_id": "zhwLLl0wvrrJCfyY", - "name": "Johannesburg Exchange", - "operating_mic": "XJSE" - }, - { - "code": "MC", - "country": "Spain", - "currency_symbol": "EUR", - "exchange_id": "hneT6zETUilAivfj", - "name": "Madrid Exchange", - "operating_mic": "BMEX" - }, - { - "code": "CM", - "country": "Sri Lanka", - "currency_symbol": "LKR", - "exchange_id": "CeTCDanBWfB16GjZ", - "name": "Colombo Stock Exchange", - "operating_mic": "XCOL" - }, - { - "code": "ST", - "country": "Sweden", - "currency_symbol": "SEK", - "exchange_id": "veJjGjclr0a3r0OE", - "name": "Stockholm Exchange", - "operating_mic": "XSTO" - }, - { - "code": "SW", - "country": "Switzerland", - "currency_symbol": "CHF", - "exchange_id": "8rCpRT5zSL7UIR66", - "name": "SIX Swiss Exchange", - "operating_mic": "XSWX" - }, - { - "code": "VX", - "country": "Switzerland", - "currency_symbol": "CHF", - "exchange_id": "61sW1hTld40S0aQA", - "name": "Swiss Exchange", - "operating_mic": "XSWX" - }, - { - "code": "TW", - "country": "Taiwan", - "currency_symbol": "TWD", - "exchange_id": "au36tlsrRl8p0XNM", - "name": "Taiwan Exchange", - "operating_mic": "XTAI" - }, - { - "code": "TWO", - "country": "Taiwan", - "currency_symbol": "TWD", - "exchange_id": "NmiJeicMEkXqFByT", - "name": "Taiwan OTC Exchange", - "operating_mic": "ROCO" - }, - { - "code": "BK", - "country": "Thailand", - "currency_symbol": "THB", - "exchange_id": "wvc1PRdacTkBLlL9", - "name": "Thailand Exchange", - "operating_mic": "XBKK" - }, - { - "code": "IS", - "country": "Turkey", - "currency_symbol": "TRY", - "exchange_id": "YopMWgK4084lOAY4", - "name": "Istanbul Stock Exchange", - "operating_mic": "XIST" - }, - { - "code": "LSE", - "country": "UK", - "currency_symbol": "GBP", - "exchange_id": "fl4Ov6jInhoqqlO7", - "name": "London Exchange", - "operating_mic": "XLON" - }, - { - "code": "IL", - "country": "UK", - "currency_symbol": "USD", - "exchange_id": "9BF3jsJXJwE3FZ2f", - "name": "London IL", - "operating_mic": "XLON" - }, - { - "code": "COMM", - "country": "Unknown", - "currency_symbol": None, - "exchange_id": "KGqDXitsl6U0QL7N", - "name": "Commodities", - "operating_mic": None - }, - { - "code": "CC", - "country": "Unknown", - "currency_symbol": None, - "exchange_id": "k3Ao1615tIBsZdru", - "name": "Cryptocurrencies", - "operating_mic": "CRYP" - }, - { - "code": "EUFUND", - "country": "Unknown", - "currency_symbol": "EUR", - "exchange_id": "fQmY5qXs5cSXDX3D", - "name": "Europe Fund Virtual Exchange", - "operating_mic": None - }, - { - "code": "FOREX", - "country": "Unknown", - "currency_symbol": None, - "exchange_id": "17tcGWD3PrJ8BmWm", - "name": "FOREX", - "operating_mic": "CDSL" - }, - { - "code": "GBOND", - "country": "Unknown", - "currency_symbol": None, - "exchange_id": "EIzkSIMhaRgg50c2", - "name": "Government Bonds", - "operating_mic": None - }, - { - "code": "INDX", - "country": "Unknown", - "currency_symbol": None, - "exchange_id": "VHzYP0hiIKYzGNLC", - "name": "Indices", - "operating_mic": None - }, - { - "code": "MONEY", - "country": "Unknown", - "currency_symbol": None, - "exchange_id": "qtDFxhoJlJ7hNL3L", - "name": "Money Market Virtual Exchange", - "operating_mic": None - }, - { - "code": "BOND", - "country": "USA", - "currency_symbol": "USD", - "exchange_id": "wKA5GAyiDmSPpAWF", - "name": "Bond Virtual Exchange", - "operating_mic": None - }, - { - "code": "US", - "country": "USA", - "currency_symbol": "USD", - "exchange_id": "SsnkrK09oZtKfwFk", - "name": "USA Stocks", - "operating_mic": "XNAS, XNYS" - }, - { - "code": "VN", - "country": "Vietnam", - "currency_symbol": "VND", - "exchange_id": "1Ib7UatNwoCoTv8t", - "name": "Vietnam Stocks", - "operating_mic": "HSTC" - } - ] diff --git a/src/ratelimit/__init__.py b/src/ratelimit/__init__.py deleted file mode 100644 index abdc448..0000000 --- a/src/ratelimit/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -import asyncio -import time - -from fastapi.requests import Request -from src.utils.my_logger import init_logger - - -class RateLimit: - """ - **RateLimit** - Used to Throttle My API into just over 100 requests per second - This works because the API is being used over cloudflare so throttling - requests to less than 100 per second for each edge server in cloudflare - makes sense. should leave room enough to service other regions - """ - def __init__(self, max_requests: int = 1000, duration: int = 1): - self.max_requests = max_requests - self.duration_seconds = duration - self.requests = [] - self.throttle_duration: int = 5 - self._logger = init_logger("global_throttle") - - async def is_limit_exceeded(self) -> bool: - now = time.monotonic() - # remove old requests from the list - self.requests = [r for r in self.requests if r > now - self.duration_seconds] - # check if limit is exceeded - if len(self.requests) >= self.max_requests: - return True - self.requests.append(now) - return False - - async def ip_throttle(self, edge_ip: str, request: Request): - mess = f""" - Throttling Requests - request from = {edge_ip} - resource_path = {request.url.path} - request_headers = {request.headers} - """ - """sleeps for 5 seconds""" - self._logger.warning(mess) - await asyncio.sleep(self.throttle_duration) - return - - -ip_rate_limits: dict[str, RateLimit] = {} diff --git a/src/requests/__init__.py b/src/requests/__init__.py deleted file mode 100644 index d90f34b..0000000 --- a/src/requests/__init__.py +++ /dev/null @@ -1,118 +0,0 @@ -import asyncio -import time - -import httpx -from src.config import config_instance -from src.utils.my_logger import init_logger - -# Use the connection pool limits in the AsyncClient -request_logger = init_logger("requester_logger") -_headers = { - 'X-API-KEY': config_instance().API_SERVERS.X_API_KEY, - 'X-SECRET-TOKEN': config_instance().API_SERVERS.X_SECRET_TOKEN, - 'X-RapidAPI-Proxy-Secret': config_instance().API_SERVERS.X_RAPID_SECRET, - 'Content-Type': "application/json", - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36' -} - -async_client = httpx.AsyncClient(http2=True, - limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), - headers=_headers) - - -async def requester(api_url: str, timeout: int = 30): - """ - 30 seconds is the maximum amount of time a request will ever wait - :param api_url: - :param timeout: - :return: - """ - try: - response = await async_client.get(url=api_url, timeout=timeout) - - _response = f""" - BACKEND SERVERS ACTUAL RESPONSES - - response_headers: {response.headers} - - response_code: {response.status_code} - - response_text: {response.text} - """ - if response.status_code not in [200, 201]: - # only print log messages if request is not successfully - request_logger.info(_response) - - response.raise_for_status() - - except httpx.HTTPError as http_err: - raise http_err - except Exception as err: - raise err - - return response.json() if response.headers.get('Content-Type') == "application/json" else None - - -class ServerMonitor: - - def __init__(self): - # 900 milliseconds - self.response_time_thresh_hold: int = 900 - self.healthy_server_urls: list[str] = config_instance().API_SERVERS.SERVERS_LIST.split(",") - # Define the health check endpoint for each server - self._server_monitor_endpoint = '/_ah/warmup' - - # Define a function to check the health of each server - async def check_health(self, api_url: str) -> tuple[str, bool]: - # Send a GET request to the health check endpoint - health_check_url = f"{api_url}{self._server_monitor_endpoint}" - request_logger.info(f"Server Health Probe: {health_check_url}") - try: - response = await async_client.get(url=health_check_url) - if response.status_code == 200: - request_logger.info(f"server still healthy : {api_url}") - return api_url, True - else: - request_logger.info(f"server not healthy : {api_url}") - request_logger.info(f"Response : {response.text}") - return api_url, False - - except (ConnectionError, TimeoutError): - return api_url, False - except httpx.HTTPError: - return api_url, False - - # Sort the healthy servers by their response time - async def measure_response_time(self, api_url: str) -> tuple[str, float | None]: - try: - check_url: str = f"{api_url}{self._server_monitor_endpoint}" - request_logger.info(f"Server Health Probe: {check_url}") - start_time = time.perf_counter() - response = await async_client.get(url=check_url) - if response.status_code == 200: - elapsed_time = int((time.perf_counter() - start_time) * 1000) - request_logger.info(f"server : {api_url} latency : {elapsed_time}") - return api_url, elapsed_time - else: - request_logger.info(f"server : {api_url} Not healthy") - request_logger.info(f"Response : {response.text}") - return api_url, None - - except (ConnectionError, TimeoutError): - return api_url, None - except httpx.HTTPError: - return api_url, None - - async def sort_api_servers_by_health(self) -> None: - # Check the health of each server asynchronously - tasks = [self.check_health(api_url) for api_url in config_instance().API_SERVERS.SERVERS_LIST.split(",")] - health_results = await asyncio.gather(*tasks) - - # Filter out the unhealthy servers - healthy_api_urls = [api_url for api_url, is_healthy in health_results if is_healthy] - - tasks = [self.measure_response_time(api_url) for api_url in healthy_api_urls] - response_time_results = await asyncio.gather(*tasks) - sorted_response_times = sorted(response_time_results, key=lambda x: x[1]) - within_threshold = [api_url for api_url, response_time in sorted_response_times if response_time < self.response_time_thresh_hold] - self.healthy_server_urls = within_threshold if within_threshold else [config_instance().API_SERVERS.SLAVE_API_SERVER] diff --git a/src/utils/__init__.py b/src/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/my_logger.py b/src/utils/my_logger.py deleted file mode 100644 index 3790dc0..0000000 --- a/src/utils/my_logger.py +++ /dev/null @@ -1,65 +0,0 @@ -import functools -import logging -import socket -import sys - -from cryptography.fernet import Fernet - -from src.config import config_instance - - -class EncryptedFormatter(logging.Formatter): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.key: bytes = config_instance().FERNET_KEY - - def format(self, record): - # Customize the format of the log message here - return f"[{record.asctime}] {record.name} - {record.levelname}: {self.encrypt_log(record.message)}" - - @staticmethod - def create_256_key() -> bytes: - # Convert the secret key to bytes - return Fernet.generate_key() - - def encrypt_log(self, log: str): - # encrypt the log message - return Fernet(self.key).encrypt(log.encode('utf-8')).decode('utf-8') - - def decrypt_log(self, log: bytes) -> str: - """decrypting logs""" - return Fernet(key=self.key).decrypt(token=log).decode('utf-8') - - def decrypt_formatted_log(self, formatted_log: str) -> str: - # Extract the encrypted message from the formatted log string - parts = formatted_log.split(":") - encrypted_message = parts[-1].strip() - # Decrypt the message and replace the encrypted message in the formatted string with the decrypted message - parts[-1] = self.decrypt_log(encrypted_message.encode("utf-8")).strip() - return ":".join(parts) - - -class AppLogger: - def __init__(self, name: str, is_file_logger: bool = False, log_level: int = logging.INFO): - # TODO create the logs folder in the root directory of the application - logging_file = f'logs/{config_instance().LOGGING.filename}' - logger_name = name if name else config_instance().APP_NAME - self.logger = logging.getLogger(logger_name) - self.logger.setLevel(level=log_level) - - handler = logging.FileHandler(logging_file) if is_file_logger else logging.StreamHandler(sys.stdout) - formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") - handler.setFormatter(formatter) - self.logger.addHandler(handler) - - -@functools.lru_cache -def init_logger(name: str = "eod-stock-api"): - """ - should include a future version which uses azure monitor to create log messages - :param name: - :return: - """ - is_development = socket.gethostname() == config_instance().DEVELOPMENT_SERVER_NAME - logger = AppLogger(name=name, is_file_logger=not is_development, log_level=logging.INFO) - return logger.logger diff --git a/src/utils/utils.py b/src/utils/utils.py deleted file mode 100644 index 1e4170b..0000000 --- a/src/utils/utils.py +++ /dev/null @@ -1,87 +0,0 @@ -""" - **Module Utils** - - Common Application Utilities** - - Utilities for commonly performed tasks - -""" -__developer__ = "mobius-crypt" -__email__ = "mobiusndou@gmail.com" -__twitter__ = "@blueitserver" -__github_profile__ = "https://github.com/freelancing-solutions/" - -import random -import re -import socket -import string -import time -from functools import wraps -from typing import Callable - -from numba import jit - -# NOTE set of characters to use when generating Unique ID -_char_set = string.ascii_lowercase + string.ascii_uppercase + string.digits - -# NOTE input character set -_input_character_set = string.printable - - -def is_development(config_instance: Callable) -> bool: - return config_instance().DEVELOPMENT_SERVER_NAME.lower() == socket.gethostname().lower() - - -def _retry(delay: int = 3, exception: Exception = None): - # noinspection PyBroadException - def decorator(func): - @wraps(func) - def wrapped_function(*args, **kwargs): - tries = 0 - while tries < 3: - try: - return func(*args, **kwargs) - except exception: - time.sleep(delay * 2 ** tries) - tries += 1 - raise exception - - return wrapped_function - - return decorator - - -def calculate_invoice_date_range(today: float) -> tuple[float, float]: - """ - - :param today: - :return: - """ - pass - - -# Creates an ID for use as a unique ID -# noinspection PyArgumentList -@jit(forceobj=True) -def create_id(size: int = 16, chars: str = _char_set) -> str: - """ - **create_id** - create a random unique id for use as indexes in Database Models - - :param size: size of string - leave as default if you can - :param chars: character set to create Unique identifier from leave as default - :return: uuid -> randomly generated id - """ - return ''.join(random.choices(chars, k=size)) - - -@jit(forceobj=True) -def camel_to_snake(name: str) -> str: - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() - - -@jit(forceobj=True) -def create_api_key() -> str: - return f"{create_id(6)}-{create_id(4)}-{create_id(4)}-{create_id(4)}-{create_id(12)}" - - -if __name__ == '__main__': - print(create_api_key())