diff --git a/blueprints/blog.py b/blueprints/blog.py index 1f913f4..a3103d0 100644 --- a/blueprints/blog.py +++ b/blueprints/blog.py @@ -3,7 +3,7 @@ from flask import Blueprint, render_template, request, jsonify import markdown from bs4 import BeautifulSoup import re -from tools import isCurl, getClientIP +from tools import isCurl, getClientIP, getHandshakeScript blog_bp = Blueprint('blog', __name__) @@ -83,7 +83,7 @@ def fix_numbered_lists(html): return str(soup) -def render_home(handshake_scripts=None): +def render_home(handshake_scripts: str | None = None): # Get a list of pages blog_pages = list_page_files() # Create a html list of pages @@ -107,26 +107,15 @@ def render_home(handshake_scripts=None): @blog_bp.route("/") def index(): if not isCurl(request): - global handshake_scripts + return render_home(handshake_scripts=getHandshakeScript(request.host)) - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - handshake_scripts = "" - return render_home(handshake_scripts) - # Get a list of pages blog_pages = list_page_files() # Create a html list of pages blog_pages = [ - {"name":page.replace("_", " "),"url":f"/blog/{page}", "download": f"/blog/{page}.md"} for page in blog_pages + {"name": page.replace("_", " "), "url": f"/blog/{page}", "download": f"/blog/{page}.md"} for page in blog_pages ] - # Render the template return jsonify({ "status": 200, @@ -136,22 +125,11 @@ def index(): }), 200 - @blog_bp.route("/") def path(path): if not isCurl(request): - global handshake_scripts - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - handshake_scripts = "" + return render_page(path, handshake_scripts=getHandshakeScript(request.host)) - return render_page(path, handshake_scripts) - # Convert md to html if not os.path.exists(f"data/blog/{path}.md"): return render_template("404.html"), 404 @@ -169,6 +147,7 @@ def path(path): "download": f"/blog/{path}.md" }), 200 + @blog_bp.route("/.md") def path_md(path): if not os.path.exists(f"data/blog/{path}.md"): @@ -176,6 +155,6 @@ def path_md(path): with open(f"data/blog/{path}.md", "r") as f: content = f.read() - + # Return the raw markdown file return content, 200, {'Content-Type': 'text/plain; charset=utf-8'} diff --git a/blueprints/now.py b/blueprints/now.py index 25d7e91..913626c 100644 --- a/blueprints/now.py +++ b/blueprints/now.py @@ -1,10 +1,12 @@ from flask import Blueprint, render_template, make_response, request, jsonify import datetime import os +from tools import getHandshakeScript # Create blueprint now_bp = Blueprint('now', __name__) + def list_page_files(): now_pages = os.listdir("templates/now") now_pages = [ @@ -13,24 +15,28 @@ def list_page_files(): now_pages.sort(reverse=True) return now_pages + def list_dates(): now_pages = list_page_files() now_dates = [page.split(".")[0] for page in now_pages] return now_dates + def get_latest_date(formatted=False): if formatted: - date=list_dates()[0] + date = list_dates()[0] date = datetime.datetime.strptime(date, "%y_%m_%d") date = date.strftime("%A, %B %d, %Y") return date return list_dates()[0] + def render_latest(handshake_scripts=None): now_page = list_dates()[0] - return render(now_page,handshake_scripts=handshake_scripts) + return render(now_page, handshake_scripts=handshake_scripts) -def render(date,handshake_scripts=None): + +def render(date, handshake_scripts=None): # If the date is not available, render the latest page if date is None: return render_latest(handshake_scripts=handshake_scripts) @@ -40,60 +46,24 @@ def render(date,handshake_scripts=None): if date not in list_dates(): return render_template("404.html"), 404 - date_formatted = datetime.datetime.strptime(date, "%y_%m_%d") date_formatted = date_formatted.strftime("%A, %B %d, %Y") - return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts) + return render_template(f"now/{date}.html", DATE=date_formatted, handshake_scripts=handshake_scripts) + @now_bp.route("/") def index(): - handshake_scripts = '' - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - handshake_scripts = "" - else: - handshake_scripts = '' - - return render_latest(handshake_scripts) + return render_latest(handshake_scripts=getHandshakeScript(request.host)) @now_bp.route("/") def path(path): - handshake_scripts = '' - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - handshake_scripts = "" - else: - handshake_scripts = '' - - return render(path, handshake_scripts) + return render(path, handshake_scripts=getHandshakeScript(request.host)) @now_bp.route("/old") @now_bp.route("/old/") def old(): - handshake_scripts = '' - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - handshake_scripts = "" - else: - handshake_scripts = '' - now_dates = list_dates()[1:] html = '" return render_template( - "now/old.html", handshake_scripts=handshake_scripts, now_pages=html + "now/old.html", handshake_scripts=getHandshakeScript(request.host), now_pages=html ) diff --git a/server.py b/server.py index 861562b..3fd1e57 100644 --- a/server.py +++ b/server.py @@ -25,7 +25,7 @@ from blueprints.wellknown import wk_bp from blueprints.api import api_bp from blueprints.podcast import podcast_bp from blueprints.acme import acme_bp -from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getGitCommit +from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getGitCommit, isDev, getHandshakeScript app = Flask(__name__) CORS(app) @@ -49,8 +49,6 @@ EMAIL_RATE_LIMIT = 3 # Max 3 requests per email per hour IP_RATE_LIMIT = 5 # Max 5 requests per IP per hour RATE_LIMIT_WINDOW = 3600 # 1 hour in seconds -HANDSHAKE_SCRIPTS = '' - RESTRICTED_ROUTES = ["ascii"] REDIRECT_ROUTES = { "contact": "/#contact" @@ -225,7 +223,6 @@ def sol_actions(): @app.route("/") def index(): - global HANDSHAKE_SCRIPTS global PROJECTS global PROJECTS_UPDATED @@ -249,7 +246,7 @@ def index(): { "message": "Welcome to Nathan.Woodburn/! This is a personal website. For more information, visit https://nathan.woodburn.au", "ip": getClientIP(request), - "dev": HANDSHAKE_SCRIPTS == "", + "dev": isDev(request.host), "version": getGitCommit() } ) @@ -269,7 +266,7 @@ def index(): try: git = requests.get( "https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1", - headers={"Authorization": os.getenv("git_token")}, + headers={"Authorization": os.getenv("GIT_AUTH") if os.getenv("GIT_AUTH") else os.getenv("git_token")}, ) git = git.json() git = git[0] @@ -344,15 +341,7 @@ def index(): repo_name = "Nathan.Woodburn/" html_url = git["repo"]["html_url"] - repo = '' + repo_name + "" - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - HANDSHAKE_SCRIPTS = "" + repo = '' + repo_name + "" # Get time timezone_offset = datetime.timedelta(hours=NC_CONFIG["time-zone"]) @@ -389,7 +378,7 @@ def index(): resp = make_response( render_template( "index.html", - handshake_scripts=HANDSHAKE_SCRIPTS, + handshake_scripts=getHandshakeScript(request.host), HNS=HNSaddress, SOL=SOLaddress, BTC=BTCaddress, @@ -414,16 +403,6 @@ def index(): @app.route("/donate") def donate(): - global HANDSHAKE_SCRIPTS - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - HANDSHAKE_SCRIPTS = "" - coinList = os.listdir(".well-known/wallets") coinList = [file for file in coinList if file[0] != "."] coinList.sort() @@ -461,7 +440,7 @@ def donate(): ) return render_template( "donate.html", - handshake_scripts=HANDSHAKE_SCRIPTS, + handshake_scripts=getHandshakeScript(request.host), coins=coins, default_coins=default_coins, crypto=instructions, @@ -531,7 +510,7 @@ def donate(): return render_template( "donate.html", - handshake_scripts=HANDSHAKE_SCRIPTS, + handshake_scripts=getHandshakeScript(request.host), crypto=cryptoHTML, coins=coins, default_coins=default_coins, @@ -718,16 +697,7 @@ def resume_pdf(): @app.route("/") def catch_all(path: str): - global HANDSHAKE_SCRIPTS - # If localhost, don't load handshake - if ( - request.host == "localhost:5000" - or request.host == "127.0.0.1:5000" - or os.getenv("dev") == "true" - or request.host == "test.nathan.woodburn.au" - ): - HANDSHAKE_SCRIPTS = "" - + if path.lower().replace(".html", "") in RESTRICTED_ROUTES: return error_response(request, message="Restricted route", code=403) @@ -736,17 +706,17 @@ def catch_all(path: str): # If file exists, load it if os.path.isfile("templates/" + path): - return render_template(path, handshake_scripts=HANDSHAKE_SCRIPTS, sites=SITES) + return render_template(path, handshake_scripts=getHandshakeScript(request.host), sites=SITES) # Try with .html if os.path.isfile("templates/" + path + ".html"): return render_template( - path + ".html", handshake_scripts=HANDSHAKE_SCRIPTS, sites=SITES + path + ".html", handshake_scripts=getHandshakeScript(request.host), sites=SITES ) if os.path.isfile("templates/" + path.strip("/") + ".html"): return render_template( - path.strip("/") + ".html", handshake_scripts=HANDSHAKE_SCRIPTS, sites=SITES + path.strip("/") + ".html", handshake_scripts=getHandshakeScript(request.host), sites=SITES ) # Try to find a file matching diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..4300e5c --- /dev/null +++ b/tests/README.md @@ -0,0 +1,3 @@ +# Tests + +These tests use hurl. Note that the SOL tests are slow as they create transactions diff --git a/tests/sol.hurl b/tests/sol.slow_hurl similarity index 100% rename from tests/sol.hurl rename to tests/sol.slow_hurl diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..dd87ba5 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +hurl --test *.hurl diff --git a/tools.py b/tools.py index 7fa251e..56b355d 100644 --- a/tools.py +++ b/tools.py @@ -1,6 +1,6 @@ from flask import Request, render_template, jsonify, make_response import os -from functools import cache +from functools import lru_cache as cache import datetime from typing import Optional, Dict, Union, Tuple import re @@ -15,10 +15,10 @@ HTTP_NOT_FOUND = 404 def getClientIP(request: Request) -> str: """ Get the client's IP address from the request. - + Args: request (Request): The Flask request object - + Returns: str: The client's IP address """ @@ -31,11 +31,11 @@ def getClientIP(request: Request) -> str: ip = "unknown" return ip - +@cache def getGitCommit() -> str: """ Get the current git commit hash. - + Returns: str: The current git commit hash or a failure message """ @@ -65,7 +65,7 @@ def isCurl(request: Request) -> bool: Args: request (Request): The Flask request object - + Returns: bool: True if the request is from curl or hurl, False otherwise """ @@ -78,10 +78,10 @@ def isCurl(request: Request) -> bool: def isCrawler(request: Request) -> bool: """ Check if the request is from a web crawler (e.g., Googlebot, Bingbot). - + Args: request (Request): The Flask request object - + Returns: bool: True if the request is from a web crawler, False otherwise """ @@ -90,15 +90,49 @@ def isCrawler(request: Request) -> bool: return "Googlebot" in user_agent or "Bingbot" in user_agent return False +@cache +def isDev(host: str) -> bool: + """ + Check if the host indicates a development environment. + + Args: + host (str): The host string from the request + + Returns: + bool: True if in development environment, False otherwise + """ + if ( + host == "localhost:5000" + or host == "127.0.0.1:5000" + or os.getenv("DEV") == "true" + or host == "test.nathan.woodburn.au" + ): + return True + return False + +@cache +def getHandshakeScript(host: str) -> str: + """ + Get the handshake script HTML snippet. + + Args: + domain (str): The domain to use in the handshake script + + Returns: + str: The handshake script HTML snippet + """ + if isDev(host): + return "" + return '' @cache def getAddress(coin: str) -> str: """ Get the wallet address for a cryptocurrency. - + Args: coin (str): The cryptocurrency code - + Returns: str: The wallet address or empty string if not found """ @@ -110,14 +144,15 @@ def getAddress(coin: str) -> str: return address +@cache def getFilePath(name: str, path: str) -> Optional[str]: """ Find a file in a directory tree. - + Args: name (str): The filename to find path (str): The root directory to search - + Returns: Optional[str]: The full path to the file or None if not found """ @@ -130,12 +165,12 @@ def getFilePath(name: str, path: str) -> Optional[str]: def json_response(request: Request, message: Union[str, Dict] = "404 Not Found", code: int = 404): """ Create a JSON response with standard formatting. - + Args: request (Request): The Flask request object message (Union[str, Dict]): The response message or data code (int): The HTTP status code - + Returns: Tuple[Dict, int]: The JSON response and HTTP status code """ @@ -144,7 +179,7 @@ def json_response(request: Request, message: Union[str, Dict] = "404 Not Found", message["status"] = code message["ip"] = getClientIP(request) return jsonify(message), code - + return jsonify({ "status": code, "message": message, @@ -153,20 +188,20 @@ def json_response(request: Request, message: Union[str, Dict] = "404 Not Found", def error_response( - request: Request, - message: str = "404 Not Found", - code: int = 404, + request: Request, + message: str = "404 Not Found", + code: int = 404, force_json: bool = False ) -> Union[Tuple[Dict, int], object]: """ Create an error response in JSON or HTML format. - + Args: request (Request): The Flask request object message (str): The error message code (int): The HTTP status code force_json (bool): Whether to force JSON response regardless of client - + Returns: Union[Tuple[Dict, int], object]: The JSON or HTML response """ @@ -174,7 +209,8 @@ def error_response( return json_response(request, message, code) # Check if .html exists in templates - template_name = f"{code}.html" if os.path.isfile(f"templates/{code}.html") else "404.html" + template_name = f"{code}.html" if os.path.isfile( + f"templates/{code}.html") else "404.html" response = make_response(render_template( template_name, code=code, message=message), code) @@ -200,7 +236,8 @@ def parse_date(date_groups: list[str]) -> str | None: date_str = " ".join(date_groups).strip() # Remove ordinal suffixes - date_str = re.sub(r'(\d+)(st|nd|rd|th)', r'\1', date_str, flags=re.IGNORECASE) + date_str = re.sub(r'(\d+)(st|nd|rd|th)', r'\1', + date_str, flags=re.IGNORECASE) # Parse with dateutil, default day=1 if missing dt = parse(date_str, default=datetime.datetime(1900, 1, 1)) @@ -213,4 +250,3 @@ def parse_date(date_groups: list[str]) -> str | None: except (ValueError, TypeError): return None -