-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #184 from dsgnr/new_api
Refactor both front-end and backend to make app more simple and portable
- Loading branch information
Showing
62 changed files
with
4,704 additions
and
8,181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM python:3.11-slim | ||
|
||
COPY requirements.txt entrypoint.sh / | ||
COPY api /src | ||
RUN chmod +x /entrypoint.sh | ||
RUN pip3 install --no-cache-dir --upgrade -r /requirements.txt | ||
WORKDIR /src | ||
EXPOSE 8000 | ||
CMD ["/entrypoint.sh"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM python:3.11-slim | ||
|
||
COPY requirements.txt entrypoint_dev.sh / | ||
COPY api /src | ||
RUN chmod +x /entrypoint_dev.sh | ||
RUN pip3 install --no-cache-dir --upgrade -r /requirements.txt | ||
WORKDIR /src | ||
EXPOSE 8000 | ||
CMD ["/entrypoint_dev.sh"] | ||
|
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Standard Library | ||
import os | ||
import socket | ||
from ipaddress import ip_address | ||
from urllib.parse import urlparse | ||
|
||
|
||
def validate_port(port: int) -> bool: | ||
return port in range(1, 65535 + 1) | ||
|
||
|
||
def is_ip_address(address: str) -> bool: | ||
try: | ||
return bool(ip_address(address)) | ||
except ValueError: | ||
return False | ||
|
||
|
||
def is_address_valid(address: str) -> bool: | ||
address_obj = ip_address(address) | ||
if address_obj.is_private and not os.environ.get("ALLOW_PRIVATE"): | ||
raise ValueError( | ||
f"IPv{address_obj.version} address '{address}' does not appear to be public" | ||
) | ||
return address_obj.version | ||
|
||
|
||
def is_valid_hostname(hostname): | ||
try: | ||
parsed = urlparse(hostname) | ||
if parsed.scheme: | ||
raise ValueError("The hostname must not have a scheme") | ||
except Exception as ex: | ||
raise Exception(str(ex)) | ||
|
||
try: | ||
socket.gethostbyname(hostname) | ||
return True | ||
except socket.gaierror: | ||
raise Exception("Hostname does not appear to resolve") | ||
|
||
|
||
def query_ipv4(address, ports): | ||
results = [] | ||
for port in ports: | ||
result = {"port": port, "status": False} | ||
sock = socket.socket() | ||
sock.settimeout(1) | ||
port_check = sock.connect_ex((address, int(port))) | ||
if port_check == 0: | ||
result["status"] = True | ||
sock.close() | ||
results.append(result) | ||
return results |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
""" | ||
The API routes for admin | ||
""" | ||
# Third Party | ||
from fastapi.routing import APIRouter | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.get("/healthz") | ||
def health() -> bool: | ||
""" | ||
Basic health check to ensure API is responding. Returns `True`. | ||
""" | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
""" | ||
The API routes for V1 | ||
""" | ||
# Third Party | ||
from app.helpers.query import ( | ||
is_address_valid, | ||
is_ip_address, | ||
is_valid_hostname, | ||
query_ipv4, | ||
validate_port, | ||
) | ||
from app.schemas.api import APIResponseSchema, APISchema | ||
from fastapi.responses import JSONResponse | ||
from fastapi.routing import APIRouter | ||
from fastapi_versioning import version | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.post("/query", response_model=APIResponseSchema) | ||
@version(1) | ||
def query_host(body: APISchema) -> APIResponseSchema: | ||
ret = {"error": False, "host": None, "check": [], "msg": None} | ||
|
||
try: | ||
ret["host"] = body.host | ||
except Exception: | ||
ret["error"] = True | ||
ret["msg"] = "A host must be defined" | ||
return JSONResponse(status_code=400, content=ret) | ||
|
||
try: | ||
ports = body.ports | ||
except Exception: | ||
ret["error"] = True | ||
ret["msg"] = "A list of ports must be defined" | ||
return JSONResponse(status_code=400, content=ret) | ||
|
||
try: | ||
for port in ports: | ||
if not validate_port(port): | ||
raise ValueError( | ||
"Only a valid port number between 1 and 65535 can be queried. " | ||
f"Port {port} is not valid" | ||
) | ||
except Exception as ex: | ||
ret["error"] = True | ||
ret["msg"] = str(ex) | ||
return JSONResponse(status_code=400, content=ret) | ||
|
||
is_ip = is_ip_address(ret["host"]) | ||
ip_version = 4 | ||
try: | ||
if is_ip: | ||
ip_version = is_address_valid(ret["host"]) | ||
else: | ||
is_valid_hostname(ret["host"]) | ||
except Exception as ex: | ||
ret["error"] = True | ||
ret["msg"] = str(ex) | ||
return JSONResponse(status_code=400, content=ret) | ||
|
||
if ip_version == 6: | ||
ret["error"] = True | ||
ret["msg"] = "IPv6 is not currently supported" | ||
return JSONResponse(status_code=400, content=ret) | ||
|
||
ret["check"] = query_ipv4(ret["host"], ports) | ||
return JSONResponse(status_code=200, content=ret) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
""" | ||
The API schema for V1 | ||
""" | ||
# Standard Library | ||
from ipaddress import IPv4Address | ||
from typing import List, Union | ||
|
||
# Third Party | ||
from pydantic import BaseModel, Field | ||
|
||
|
||
class APISchema(BaseModel): | ||
host: Union[IPv4Address, str] = Field(description="The IPv4 address of the host to query") | ||
ports: List[int] | ||
|
||
model_config = {"json_schema_extra": {"examples": [{"host": "1.1.1.1", "ports": [444]}]}} | ||
|
||
|
||
class APICheckSchema(BaseModel): | ||
port: int | ||
status: bool | ||
|
||
|
||
class APIResponseSchema(BaseModel): | ||
error: bool | ||
msg: Union[str, None] | ||
check: List[APICheckSchema] | ||
host: str | ||
|
||
model_config = { | ||
"json_schema_extra": { | ||
"examples": [ | ||
{ | ||
"error": False, | ||
"msg": None, | ||
"host": "1.1.1.1", | ||
"check": [{"status": True, "ports": 443}], | ||
} | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Standard Library | ||
import logging | ||
|
||
# Third Party | ||
from fastapi import FastAPI | ||
from fastapi.logger import logger as fastapi_logger | ||
from fastapi.middleware.cors import CORSMiddleware | ||
from fastapi_versioning import VersionedFastAPI | ||
|
||
# First Party | ||
from app.routes import admin, v1 | ||
|
||
# Init the app | ||
app = FastAPI(title="portchecker.io", version="1.0.0") | ||
|
||
# Logging | ||
gunicorn_error_logger = logging.getLogger("gunicorn.error") | ||
gunicorn_logger = logging.getLogger("gunicorn") | ||
uvicorn_access_logger = logging.getLogger("uvicorn.access") | ||
uvicorn_access_logger.handlers = gunicorn_error_logger.handlers | ||
fastapi_logger.handlers = gunicorn_error_logger.handlers | ||
fastapi_logger.setLevel(gunicorn_logger.level) | ||
|
||
# Routes | ||
app.include_router(v1.router, tags=["Routes"]) | ||
app = VersionedFastAPI(app, version_format="{major}", prefix_format="/api/v{major}") | ||
app.include_router(admin.router, tags=["Admin"]) | ||
|
||
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#! /usr/bin/env sh | ||
set -e | ||
exec gunicorn -k uvicorn.workers.UvicornWorker \ | ||
-b 0.0.0.0:8000 \ | ||
--workers 4 \ | ||
main:app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#! /usr/bin/env sh | ||
set -e | ||
exec uvicorn main:app --host 0.0.0.0 --port 8000 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[tool.black] | ||
line-length = 100 | ||
target-version = ["py39"] | ||
skip-string-normalization = false | ||
skip-numeric-underscore-normalization = false | ||
|
||
[tool.isort] | ||
combine_as_imports = true | ||
force_grid_wrap = 0 | ||
include_trailing_comma = true | ||
multi_line_output = 3 | ||
use_parentheses = true | ||
line_length = 100 | ||
wrap_length = 100 | ||
ensure_newline_before_comments = true | ||
|
||
import_heading_firstparty = "First Party" | ||
import_heading_stdlib = "Standard Library" | ||
import_heading_thirdparty = "Third Party" |
Oops, something went wrong.