From de883403ef06ba38e8fc3026a9ba3964a7b1c462 Mon Sep 17 00:00:00 2001 From: Joshua Burman Date: Fri, 3 May 2024 15:24:54 -0400 Subject: [PATCH 1/9] added toast and validations --- climbdex/api.py | 31 ++++++++++++++++++++-- climbdex/static/js/common.js | 3 +++ climbdex/static/js/results.js | 20 +++++++++++--- climbdex/templates/beta.html.j2 | 4 +++ climbdex/templates/boardSelection.html.j2 | 5 +++- climbdex/templates/climbCreation.html.j2 | 6 ++++- climbdex/templates/filterSelection.html.j2 | 1 + climbdex/templates/results.html.j2 | 4 +++ climbdex/templates/toast.html.j2 | 8 ++++++ requirements.txt | 1 + 10 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 climbdex/templates/toast.html.j2 diff --git a/climbdex/api.py b/climbdex/api.py index 7b29399..cf485db 100644 --- a/climbdex/api.py +++ b/climbdex/api.py @@ -1,3 +1,5 @@ +from flask_parameter_validation import ValidateParameters, Query + import boardlib.api.aurora import flask import requests @@ -6,6 +8,13 @@ blueprint = flask.Blueprint("api", __name__) +def param_error_handler(err): + return { + "error": True, + "details": str(err), + "message": f"There was an issue getting results. If the issue persists please report it" + }, 400 + @blueprint.route("/api/v1//layouts") def layouts(board_name): @@ -29,12 +38,30 @@ def sets(board_name, layout_id, size_id): @blueprint.route("/api/v1/search/count") -def resultsCount(): +@ValidateParameters(param_error_handler) +def resultsCount( + gradeAccuracy: float = Query(), + layout: int = Query(), + maxGrade: int = Query(), + minAscents: int = Query(), + minGrade: int = Query(), + minRating: float = Query(), + size: int = Query(), +): return flask.jsonify(climbdex.db.get_search_count(flask.request.args)) @blueprint.route("/api/v1/search") -def search(): +@ValidateParameters(param_error_handler) +def search( + gradeAccuracy: float = Query(), + layout: int = Query(), + maxGrade: int = Query(), + minAscents: int = Query(), + minGrade: int = Query(), + minRating: float = Query(), + size: int = Query(), +): return flask.jsonify(climbdex.db.get_search_results(flask.request.args)) diff --git a/climbdex/static/js/common.js b/climbdex/static/js/common.js index d9fe01b..82897d3 100644 --- a/climbdex/static/js/common.js +++ b/climbdex/static/js/common.js @@ -1,3 +1,6 @@ +var el = document.getElementById('toast') +var toast = bootstrap.Toast.getOrCreateInstance(el) + function drawBoard( svgElementId, imagesToHolds, diff --git a/climbdex/static/js/results.js b/climbdex/static/js/results.js index 320df08..470dcb3 100644 --- a/climbdex/static/js/results.js +++ b/climbdex/static/js/results.js @@ -86,7 +86,14 @@ async function fetchResultsCount() { const urlParams = new URLSearchParams(window.location.search); const response = await fetch("/api/v1/search/count?" + urlParams); const resultsCount = await response.json(); - return resultsCount; + + if (resultsCount['error'] == true) { + el.classList.add('bg-danger') + el.querySelector('.toast-body').innerHTML = resultsCount['message'] + toast.show() + } else { + return resultsCount; + } } async function fetchResults(pageNumber, pageSize) { @@ -95,7 +102,14 @@ async function fetchResults(pageNumber, pageSize) { urlParams.append("pageSize", pageSize); const response = await fetch("/api/v1/search?" + urlParams); const results = await response.json(); - return results; + + if (results['error'] == true) { + el.classList.add('bg-danger') + el.querySelector('.toast-body').innerHTML = results['message'] + toast.show() + } else { + return results; + } } function clickClimbButton(index, pageSize, resultsCount) { @@ -230,7 +244,7 @@ function drawResultsPage(results, pageNumber, pageSize, resultsCount) { } const backAnchor = document.getElementById("anchor-back"); -backAnchor.href = location.origin + "/filter?" + location.search; +backAnchor.href = location.origin + "/filter" + location.search; if (document.referrer && new URL(document.referrer).origin == location.origin) { backAnchor.addEventListener("click", function (event) { event.preventDefault(); diff --git a/climbdex/templates/beta.html.j2 b/climbdex/templates/beta.html.j2 index fae8aca..48f5251 100644 --- a/climbdex/templates/beta.html.j2 +++ b/climbdex/templates/beta.html.j2 @@ -14,6 +14,7 @@ + {% include 'toast.html.j2' %}
{% include 'heading.html.j2' %}
@@ -52,6 +53,9 @@ {% include 'footer.html.j2'%}
+ diff --git a/climbdex/templates/boardSelection.html.j2 b/climbdex/templates/boardSelection.html.j2 index 041957c..9e25140 100644 --- a/climbdex/templates/boardSelection.html.j2 +++ b/climbdex/templates/boardSelection.html.j2 @@ -14,6 +14,7 @@ + {% include 'toast.html.j2' %}
{% include 'heading.html.j2' %}
@@ -100,7 +101,9 @@
- + diff --git a/climbdex/templates/climbCreation.html.j2 b/climbdex/templates/climbCreation.html.j2 index 6a8c0fc..66df3ca 100644 --- a/climbdex/templates/climbCreation.html.j2 +++ b/climbdex/templates/climbCreation.html.j2 @@ -12,6 +12,7 @@ + {% include 'toast.html.j2' %}
{% include 'heading.html.j2' %}
@@ -33,9 +34,12 @@
{% include 'footer.html.j2' %}
+ + - + {% include 'toast.html.j2' %}
{% include 'heading.html.j2' %}
diff --git a/climbdex/templates/results.html.j2 b/climbdex/templates/results.html.j2 index 7000c7f..9b47ca5 100644 --- a/climbdex/templates/results.html.j2 +++ b/climbdex/templates/results.html.j2 @@ -22,6 +22,7 @@ + {% include 'toast.html.j2' %}
{% include 'heading.html.j2' %}
@@ -76,6 +77,9 @@
{% include 'footer.html.j2' %}
+ - {% include 'toast.html.j2' %}
{% include 'heading.html.j2' %} + {% include 'alert.html.j2' %}
From 9474d9a483bb56c11d11a74b86747fd1a9b0e3b5 Mon Sep 17 00:00:00 2001 From: Joshua Burman Date: Sat, 4 May 2024 23:08:30 -0400 Subject: [PATCH 8/9] added flask exception handling, modification to error display, basic logging impl --- climbdex/__init__.py | 7 +++ climbdex/api.py | 81 ++++++++++++++++++----------------- climbdex/static/js/results.js | 4 +- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/climbdex/__init__.py b/climbdex/__init__.py index 8eebd2c..01e32aa 100644 --- a/climbdex/__init__.py +++ b/climbdex/__init__.py @@ -1,8 +1,15 @@ +import sys import flask +import logging import climbdex.api import climbdex.views +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler(sys.stdout)], +) def create_app(): app = flask.Flask(__name__, instance_relative_config=True) diff --git a/climbdex/api.py b/climbdex/api.py index 00fec88..62bd68c 100644 --- a/climbdex/api.py +++ b/climbdex/api.py @@ -3,56 +3,66 @@ import boardlib.api.aurora import flask import requests +import json +import logging import climbdex.db blueprint = flask.Blueprint("api", __name__) -def error_handler(err): - details = str(err) - error_type = str(type(err).__name__) - message = f"There was a problem while getting results from the api. If the issue persists, please report it" +def parameter_error(e): + code = 400 + name = str(type(e).__name__) + description = f"Parameters were missing and/or misconfigured. If the issue persists, please report it (code: {code})" - return { + response = { "error": True, - "details": details, - "type": error_type, - "message": message, - }, 400 + "code": code, + "name": name, + "description": description, + }, code + + logging.error(response) + return response + +@blueprint.errorhandler(Exception) +def handle_exception(e): + response = e.get_response() + response.data = json.dumps({ + "error": True, + "code": e.code, + "name": e.name, + "description": f"There was a problem while getting results from the server. If the issue persists, please report it (code: {e.code})", + }) + response.content_type = "application/json" + logging.error(response.data) + return response + @blueprint.route("/api/v1//layouts") def layouts(board_name): - try: - return flask.jsonify(climbdex.db.get_data(board_name, "layouts")) - except Exception as e: - return error_handler(e) + return flask.jsonify(climbdex.db.get_data(board_name, "layouts")) @blueprint.route("/api/v1//layouts//sizes") def sizes(board_name, layout_id): - try: - return flask.jsonify( - climbdex.db.get_data(board_name, "sizes", {"layout_id": int(layout_id)}) - ) - except Exception as e: - return error_handler(e) + return flask.jsonify( + climbdex.db.get_data(board_name, "sizes", {"layout_id": int(layout_id)}) + ) @blueprint.route("/api/v1//layouts//sizes//sets") def sets(board_name, layout_id, size_id): - try: - return flask.jsonify( - climbdex.db.get_data( - board_name, "sets", {"layout_id": int(layout_id), "size_id": int(size_id)} - ) + return flask.jsonify( + climbdex.db.get_data( + board_name, "sets", {"layout_id": int(layout_id), "size_id": int(size_id)} ) - except Exception as e: - return error_handler(e) + ) @blueprint.route("/api/v1/search/count") -@ValidateParameters(error_handler) +@ValidateParameters(parameter_error) def resultsCount( gradeAccuracy: float = Query(), layout: int = Query(), @@ -62,13 +72,10 @@ def resultsCount( minRating: float = Query(), size: int = Query(), ): - try: - return flask.jsonify(climbdex.db.get_search_count(flask.request.args)) - except Exception as e: - return error_handler(e) + return flask.jsonify(climbdex.db.get_search_count(flask.request.args)) @blueprint.route("/api/v1/search") -@ValidateParameters(error_handler) +@ValidateParameters(parameter_error) def search( gradeAccuracy: float = Query(), layout: int = Query(), @@ -78,18 +85,12 @@ def search( minRating: float = Query(), size: int = Query(), ): - try: - return flask.jsonify(climbdex.db.get_search_results(flask.request.args)) - except Exception as e: - return error_handler(e) + return flask.jsonify(climbdex.db.get_search_results(flask.request.args)) @blueprint.route("/api/v1//beta/") def beta(board_name, uuid): - try: - return flask.jsonify(climbdex.db.get_data(board_name, "beta", {"uuid": uuid})) - except Exception as e: - return error_handler(e) + return flask.jsonify(climbdex.db.get_data(board_name, "beta", {"uuid": uuid})) @blueprint.route("/api/v1/login/", methods=["POST"]) diff --git a/climbdex/static/js/results.js b/climbdex/static/js/results.js index 8a52312..35c000e 100644 --- a/climbdex/static/js/results.js +++ b/climbdex/static/js/results.js @@ -88,7 +88,7 @@ async function fetchResultsCount() { const resultsCount = await response.json(); if (resultsCount['error'] == true) { - alert.querySelector('.alert-content').innerHTML = resultsCount['message'] + alert.querySelector('.alert-content').innerHTML = resultsCount['description'] alert.classList.add('show-alert') } else { return resultsCount; @@ -103,7 +103,7 @@ async function fetchResults(pageNumber, pageSize) { const results = await response.json(); if (results['error'] == true) { - alert.querySelector('.alert-content').innerHTML = resultsCount['message'] + alert.querySelector('.alert-content').innerHTML = resultsCount['description'] alert.classList.add('show-alert') } else { return results; From a81878bbf919e6548735750c9f292b83c09e21fd Mon Sep 17 00:00:00 2001 From: Joshua Burman Date: Tue, 7 May 2024 15:24:10 -0400 Subject: [PATCH 9/9] add location.search back --- climbdex/static/js/results.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/climbdex/static/js/results.js b/climbdex/static/js/results.js index 35c000e..d7de350 100644 --- a/climbdex/static/js/results.js +++ b/climbdex/static/js/results.js @@ -242,7 +242,7 @@ function drawResultsPage(results, pageNumber, pageSize, resultsCount) { } const backAnchor = document.getElementById("anchor-back"); -backAnchor.href = location.origin + "/filter"; +backAnchor.href = location.origin + "/filter" + location.search; if (document.referrer && new URL(document.referrer).origin == location.origin) { backAnchor.addEventListener("click", function (event) { event.preventDefault();