From ca88c95bc2c9bb90e0211edc7d063ab21433a130 Mon Sep 17 00:00:00 2001 From: Sagar Patil Date: Thu, 23 Jan 2025 19:58:21 -0500 Subject: [PATCH] landing page + gunicorn --- Dockerfile | 2 +- app.py | 48 ++++++++++++++------------ pyproject.toml | 1 + templates/index.html | 80 ++++++++++++++++++++++++++++++++++++++++++++ uv.lock | 23 +++++++++++++ 5 files changed, 132 insertions(+), 22 deletions(-) create mode 100644 templates/index.html diff --git a/Dockerfile b/Dockerfile index 4ea1ece..0ec3949 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,4 +6,4 @@ WORKDIR /app RUN uv sync --frozen EXPOSE 5000 -CMD ["uv", "run", "flask", "run", "--host=0.0.0.0"] \ No newline at end of file +CMD ["uv", "run", "gunicorn", "app:app", "--bind", "0.0.0.0:5000"] \ No newline at end of file diff --git a/app.py b/app.py index 441b3d5..cd88a0a 100644 --- a/app.py +++ b/app.py @@ -1,58 +1,64 @@ from main import do_the_thing -from flask import Flask, request, send_file +from flask import Flask, request, send_file, render_template, make_response import json import base64 -from typing import Callable, Dict, Any +from typing import Dict, Any from io import BytesIO app = Flask(__name__) -@app.route('/gradescope.ics') + +@app.route("/") +def index(): + response = make_response(render_template("index.html")) + response.headers["Cache-Control"] = "public, max-age=3600" + + return response + + +@app.route("/gradescope.ics") def gradescope_calendar(): try: # Get the base64 encoded query parameter - encoded_data = request.args.get('data') + encoded_data = request.args.get("data") if not encoded_data: - return 'Missing data parameter', 400 + return "Missing data parameter", 400 # Decode base64 to JSON string try: - json_str = base64.b64decode(encoded_data).decode('utf-8') + json_str = base64.b64decode(encoded_data).decode("utf-8") except Exception as e: - return f'Invalid base64 encoding: {str(e)}', 400 + return f"Invalid base64 encoding: {str(e)}", 400 # Parse JSON try: data: Dict[str, Any] = json.loads(json_str) except json.JSONDecodeError as e: - return f'Invalid JSON: {str(e)}', 400 + return f"Invalid JSON: {str(e)}", 400 # Validate required fields - required_fields = ['email', 'pwd', 'sem'] + required_fields = ["email", "pwd", "sem"] for field in required_fields: if field not in data: - return f'Missing required field: {field}', 400 + return f"Missing required field: {field}", 400 # Call do_the_thing with the parameters - file_data = do_the_thing( - email=data['email'], - pwd=data['pwd'], - sem=data['sem'] - ) + file_data = do_the_thing(email=data["email"], pwd=data["pwd"], sem=data["sem"]) file_str = "".join(file_data) - file_io = BytesIO(file_str.encode('utf-8')) + file_io = BytesIO(file_str.encode("utf-8")) # Send the file return send_file( file_io, - mimetype='text/calendar', + mimetype="text/calendar", as_attachment=True, - download_name='gradescope.ics' + download_name="gradescope.ics", ) except Exception as e: - return f'Server error: {str(e)}', 500 + return f"Server error: {str(e)}", 500 + -if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file +if __name__ == "__main__": + app.run(debug=True) diff --git a/pyproject.toml b/pyproject.toml index 6dd0508..5445bb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.12" dependencies = [ "beautifulsoup4>=4.12.3", "flask>=3.1.0", + "gunicorn>=23.0.0", "ics>=0.7.2", "python-dotenv>=1.0.1", "requests>=2.32.3", diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..6996fca --- /dev/null +++ b/templates/index.html @@ -0,0 +1,80 @@ + + + + Gradescope iCalendar + + + +
+

Gradescope iCalendar

+ + + + +
+ + +
+ + + + diff --git a/uv.lock b/uv.lock index a75379a..756e572 100644 --- a/uv.lock +++ b/uv.lock @@ -132,6 +132,7 @@ source = { virtual = "." } dependencies = [ { name = "beautifulsoup4" }, { name = "flask" }, + { name = "gunicorn" }, { name = "ics" }, { name = "python-dotenv" }, { name = "requests" }, @@ -141,11 +142,24 @@ dependencies = [ requires-dist = [ { name = "beautifulsoup4", specifier = ">=4.12.3" }, { name = "flask", specifier = ">=3.1.0" }, + { name = "gunicorn", specifier = ">=23.0.0" }, { name = "ics", specifier = ">=0.7.2" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "requests", specifier = ">=2.32.3" }, ] +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, +] + [[package]] name = "ics" version = "0.7.2" @@ -230,6 +244,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, ] +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0"