From 1a0a1371bcf1c1624e10bff0317e29ca180b9659 Mon Sep 17 00:00:00 2001 From: Steve Dogiakos Date: Mon, 9 Mar 2026 19:30:13 -0600 Subject: [PATCH] fix: correct marquee scroll speed and add code TODOs - Fixed scrolling marquee to use a fixed px/s speed via JS instead of a fixed duration, preventing it from speeding up as entries are added - Added inline TODO comments throughout codebase to track known issues (rate limiting, CSRF, unbounded queries, deprecated Flask decorator, PII logging, schema versioning, Docker non-root user, etc.) - Added todo-to-issue GitHub Action to auto-create Issues from TODOs on push to main - Added .claude/ to .gitignore --- .github/workflows/todo-to-issue.yml | 14 ++++++++++++++ .gitignore | 3 +++ Dockerfile | 5 +++++ app.py | 12 ++++++++++++ entrypoint.sh | 1 + scripts/guestbook_export.py | 3 ++- templates/index.html.template | 20 ++++++++++++++++++-- 7 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/todo-to-issue.yml diff --git a/.github/workflows/todo-to-issue.yml b/.github/workflows/todo-to-issue.yml new file mode 100644 index 0000000..833ae44 --- /dev/null +++ b/.github/workflows/todo-to-issue.yml @@ -0,0 +1,14 @@ +name: TODO to Issue + +on: + push: + branches: [ "main" ] + +jobs: + todo: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: alstr/todo-to-issue-action@v5 + with: + TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 8a8f590..b5e8d5c 100644 --- a/.gitignore +++ b/.gitignore @@ -184,5 +184,8 @@ cython_debug/ # VS Code .vscode/ +# Claude Code +.claude/ + .env docker-compose.yml diff --git a/Dockerfile b/Dockerfile index c99dae2..5d07de8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,5 +24,10 @@ ENV FLASK_ENV=production # Expose the port (Gunicorn will run on 8000) EXPOSE 8000 +# TODO: No USER directive — container runs as root. Add a non-root user for security. +# example.env has PID/GID=1000 vars suggesting this was intended. e.g.: +# RUN useradd -u 1000 -g 1000 appuser && chown -R appuser /app /data +# USER appuser + # Use the entrypoint script as the container's command CMD ["/entrypoint.sh"] diff --git a/app.py b/app.py index 510b8af..b3f1103 100644 --- a/app.py +++ b/app.py @@ -33,6 +33,7 @@ def load_banned_words(): BANNED_WORDS = load_banned_words() def contains_banned_words(text): + # TODO: This filter is easily bypassed (spacing, leet-speak, numbers). Consider a more robust NLP-based approach. words = text.lower().split() for word in words: word_clean = word.strip(".,!?;:\"'") @@ -41,6 +42,7 @@ def contains_banned_words(text): return False def init_db(): + # TODO: No schema versioning — adding columns in the future requires manual DB updates. Consider a migration tool (e.g. Alembic). conn = sqlite3.connect(DATABASE) c = conn.cursor() c.execute(''' @@ -60,14 +62,19 @@ def init_db(): logger.info("Database initialized.") def is_valid_email(email): + # TODO: This regex allows edge cases like consecutive dots and leading/trailing hyphens. Consider using the `email-validator` package. pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$' return re.match(pattern, email) +# TODO: @before_first_request is deprecated in Flask 2.2 and removed in Flask 3.0. +# Replace with: with app.app_context(): init_db() at module level, or use a CLI command. @app.before_first_request def initialize_database(): init_db() @app.route('/', methods=['GET', 'POST']) +# TODO: No rate limiting — form can be spammed. Add Flask-Limiter (e.g. @limiter.limit("10/minute")). +# TODO: No CSRF protection. Add Flask-WTF for CSRF tokens. def index(): error = None if request.method == 'POST': @@ -92,6 +99,8 @@ def index(): if error: conn = sqlite3.connect(DATABASE) c = conn.cursor() + # TODO: No LIMIT — returns all rows. Add LIMIT 100 or similar. + # TODO: No error handling — a locked/corrupted DB returns an unhandled 500. Wrap in try/except. c.execute('SELECT first_name, location FROM guests ORDER BY id DESC') guests = c.fetchall() conn.close() @@ -108,11 +117,14 @@ def index(): ) conn.commit() conn.close() + # TODO: Logging full name and location is PII. Consider omitting or hashing before logging. logger.info("Added guest: %s %s from %s", first_name, last_name, location) return redirect(url_for('index')) conn = sqlite3.connect(DATABASE) c = conn.cursor() + # TODO: No LIMIT — returns all rows forever. Add LIMIT 100 or similar to avoid memory growth. + # TODO: No indexes on this table — full table scan on every page load. Add index on id/timestamp. c.execute('SELECT first_name, location FROM guests ORDER BY id DESC') guests = c.fetchall() conn.close() diff --git a/entrypoint.sh b/entrypoint.sh index cc32e1a..4ac9629 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -4,4 +4,5 @@ envsubst < /app/templates/index.html.template > /app/templates/index.html # Start Gunicorn; using an environment variable for workers (default is 3) +# TODO: Variable mismatch — example.env sets GUNICORN_WORKERS but this reads WORKERS. Change to ${GUNICORN_WORKERS:-3}. exec gunicorn --bind 0.0.0.0:8000 app:app --workers ${WORKERS:-3} diff --git a/scripts/guestbook_export.py b/scripts/guestbook_export.py index 2cc11a2..310d024 100644 --- a/scripts/guestbook_export.py +++ b/scripts/guestbook_export.py @@ -1,7 +1,8 @@ import csv import sqlite3 -# Update the database file path if needed. +# TODO: Hardcoded relative path — breaks if script is run from a different directory. +# Replace with: DATABASE = os.environ.get('DATABASE_PATH', 'guestbook.db') and import os. DATABASE = 'guestbook.db' EXPORT_FILE = 'mailchimp_export.csv' diff --git a/templates/index.html.template b/templates/index.html.template index 9aa28b9..dcdbdce 100644 --- a/templates/index.html.template +++ b/templates/index.html.template @@ -23,12 +23,12 @@ .scrolling-content { display: inline-block; padding: 10px; - animation: scroll-left 20s linear infinite; + animation: scroll-left linear infinite; } @keyframes scroll-left { 0% { - transform: translateX(100%); + transform: translateX(100vw); } 100% { @@ -107,6 +107,22 @@ + + +