This commit is contained in:
2025-08-07 18:18:36 -04:00
parent 8df728530e
commit 918fc91604
28 changed files with 1127 additions and 243 deletions

1
app/utils/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Utils package

38
app/utils/auth.py Normal file
View File

@@ -0,0 +1,38 @@
"""
Authentication utilities and decorators
"""
from functools import wraps
from flask import session, flash, redirect, url_for, current_app
def login_required(f):
"""Decorator to require login for route access"""
@wraps(f)
def decorated_function(*args, **kwargs):
if 'logged_in' not in session:
flash('Please log in to access this page.')
return redirect(url_for('auth.login'))
return f(*args, **kwargs)
return decorated_function
def check_credentials(username, password):
"""Check if provided credentials are valid"""
return (username == current_app.config['ADMIN_USERNAME'] and
password == current_app.config['ADMIN_PASSWORD'])
def login_user():
"""Log in the user by setting session"""
session['logged_in'] = True
def logout_user():
"""Log out the user by clearing session"""
session.pop('logged_in', None)
def is_authenticated():
"""Check if current user is authenticated"""
return 'logged_in' in session

View File

@@ -0,0 +1,24 @@
"""
Error handlers for the application
"""
from flask import render_template, current_app
def register_error_handlers(app):
"""Register error handlers with the app"""
@app.errorhandler(404)
def not_found_error(error):
current_app.logger.error(f'404 error: {error}')
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_error(error):
current_app.logger.error(f'500 error: {error}')
return render_template('errors/500.html'), 500
@app.errorhandler(413)
def too_large(error):
current_app.logger.error(f'413 error - File too large: {error}')
return render_template('errors/413.html'), 413

60
app/utils/helpers.py Normal file
View File

@@ -0,0 +1,60 @@
"""
Utility helper functions
"""
from flask import current_app, make_response, jsonify
from app.models.database import PetPicture
def allowed_file(filename):
"""Check if file extension is allowed"""
return ('.' in filename and
filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS'])
def get_liked_pictures_from_cookie(request):
"""Extract liked picture IDs from cookie"""
liked_pictures = request.cookies.get('liked_pictures', '')
return liked_pictures.split(',') if liked_pictures else []
def add_liked_status_to_pictures(pictures, liked_list):
"""Add user_liked status to picture records"""
pictures_with_likes = []
for picture in pictures:
picture_dict = dict(picture)
picture_dict['user_liked'] = str(picture['id']) in liked_list
pictures_with_likes.append(picture_dict)
return pictures_with_likes
def handle_like_action(picture_id, request):
"""Handle like/unlike action and return JSON response"""
# Get existing likes from cookie
liked_list = get_liked_pictures_from_cookie(request)
picture_id_str = str(picture_id)
if picture_id_str in liked_list:
# Unlike: remove from cookie and decrement count
liked_list.remove(picture_id_str)
PetPicture.update_likes(picture_id, increment=False)
liked = False
else:
# Like: add to cookie and increment count
liked_list.append(picture_id_str)
PetPicture.update_likes(picture_id, increment=True)
liked = True
# Get updated like count
like_count = PetPicture.get_like_count(picture_id)
# Create response with updated cookie
response = make_response(jsonify({
'liked': liked,
'like_count': like_count
}))
# Update cookie (expires in 1 year)
response.set_cookie('liked_pictures', ','.join(liked_list), max_age=365*24*60*60)
return response

View File

@@ -0,0 +1,32 @@
"""
Logging configuration
"""
import logging
import os
from logging.handlers import RotatingFileHandler
def setup_logging(app):
"""Setup application logging"""
if not app.debug and not app.testing:
# Production logging setup
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler(
'logs/pets_powerwashing.log',
maxBytes=10240,
backupCount=10
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Pets of Powerwashing startup')
else:
# Development logging
app.logger.setLevel(logging.DEBUG)