Yeah reworked the entire thing so that the checked, count and streak value is stored in the Habit Table
474 lines
12 KiB
Python
474 lines
12 KiB
Python
import datetime
|
|
import hashlib
|
|
import os
|
|
from PIL import Image
|
|
|
|
from flask import Flask, render_template, redirect, url_for, request, jsonify
|
|
from flask_login import login_required, LoginManager, login_user, logout_user, current_user
|
|
|
|
from models.Habit import Habit
|
|
from models.HabitList import HabitList
|
|
from models.HabitTracking import HabitTracking
|
|
from models.User import User
|
|
from utils import anonymous_required
|
|
|
|
# Create a new Flask instance
|
|
app = Flask(__name__)
|
|
app.secret_key = 'PSSSSSHHHT!'
|
|
|
|
# Initialize the Flask-Login extension
|
|
login_manager = LoginManager()
|
|
login_manager.login_view = 'login'
|
|
login_manager.init_app(app)
|
|
|
|
|
|
@login_manager.user_loader
|
|
def load_user(user_id):
|
|
return User.get(user_id)
|
|
|
|
|
|
@app.context_processor
|
|
def inject_user():
|
|
return dict(user=current_user)
|
|
|
|
|
|
@app.route('/login')
|
|
@anonymous_required
|
|
def login():
|
|
return render_template('auth/login.html', errors={})
|
|
|
|
|
|
@app.route('/signup')
|
|
@anonymous_required
|
|
def signup():
|
|
return render_template('auth/signup.html', errors={})
|
|
|
|
|
|
@app.route('/login', methods=['POST'])
|
|
def login_post():
|
|
email = request.form.get('email')
|
|
password = request.form.get('password')
|
|
|
|
# Check for errors
|
|
errors = {}
|
|
if not email:
|
|
errors['email'] = 'Die E-Mail Adresse ist erforderlich.'
|
|
if not password:
|
|
errors['password'] = 'Das Passwort ist erforderlich.'
|
|
|
|
# Check if user exists
|
|
user = User.get_by_email(email)
|
|
|
|
if not user:
|
|
errors['email'] = 'E-Mail Adresse nicht gefunden.'
|
|
elif user.password is None or hashlib.sha256(password.encode()).hexdigest() != user.password:
|
|
errors['password'] = 'Das Passwort ist falsch.'
|
|
|
|
if errors:
|
|
return render_template(
|
|
'auth/login.html',
|
|
email=email,
|
|
password=password,
|
|
errors=errors
|
|
)
|
|
|
|
login_user(user)
|
|
|
|
# Redirect to login page
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
@app.route('/signup', methods=['POST'])
|
|
def signup_post():
|
|
email = request.form.get('email')
|
|
name = request.form.get('name')
|
|
password = request.form.get('password')
|
|
|
|
# Check for errors
|
|
errors = {}
|
|
if not email:
|
|
errors['email'] = 'Die E-Mail Adresse ist erforderlich.'
|
|
if not name:
|
|
errors['name'] = 'Der Name ist erforderlich.'
|
|
if not password:
|
|
errors['password'] = 'Das Passwort ist erforderlich.'
|
|
|
|
if errors:
|
|
return render_template(
|
|
'auth/signup.html',
|
|
email=email,
|
|
name=name,
|
|
password=password,
|
|
errors=errors
|
|
)
|
|
|
|
# Save user to database. Maybe log the user in directly.
|
|
user = User.create(name, email, password)
|
|
login_user(user)
|
|
|
|
# Redirect to login page
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
@app.route('/logout')
|
|
@login_required
|
|
def logout():
|
|
# Log out functionality
|
|
logout_user()
|
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
# Create a new route
|
|
@app.route('/')
|
|
def index():
|
|
if current_user.is_authenticated:
|
|
habit_lists = current_user.get_habitLists()
|
|
name = "Hallo " + current_user.name
|
|
heatmap_values = current_user.get_heatmap()
|
|
else:
|
|
habit_lists = []
|
|
name = "Bitte melde dich an."
|
|
heatmap_values = []
|
|
|
|
# Sort habits by whether they have been checked today and then by slot
|
|
for habit_list in habit_lists:
|
|
habit_list.habits = sorted(habit_list.get_habits(), key=lambda habit: (not habit.checked, habit.slot))
|
|
|
|
return render_template(
|
|
'index.html',
|
|
title=name,
|
|
utc_dt=datetime.datetime.now().strftime("%d.%m.%Y %H:%M %A"),
|
|
habit_lists=habit_lists,
|
|
heatmap_values=heatmap_values,
|
|
errors={},
|
|
)
|
|
|
|
|
|
@app.route('/habit')
|
|
@login_required
|
|
def habit_creation():
|
|
return render_template(
|
|
'habit.html',
|
|
title='Erstelle ein Habit',
|
|
unit="Woche",
|
|
errors={},
|
|
)
|
|
|
|
|
|
@app.route('/habit', methods=['POST'])
|
|
@login_required
|
|
def habit_create():
|
|
name = request.form.get('name')
|
|
note = request.form.get('note')
|
|
times = request.form.get('times')
|
|
unit = request.form.get('unit')
|
|
list_id = request.form.get('list_query')
|
|
|
|
# Check for errors
|
|
errors = {}
|
|
if not name:
|
|
errors['name'] = 'Der Name ist erforderlich.'
|
|
if not times:
|
|
errors['times'] = 'Die Anzahl ist erforderlich.'
|
|
if not note:
|
|
note = ''
|
|
if not unit:
|
|
errors['unit'] = 'Die Einheit ist erforderlich.'
|
|
if not list_id:
|
|
errors['list_query'] = 'Die Habitliste ist erforderlich.'
|
|
|
|
# Check if times is an integer
|
|
try:
|
|
times = int(times)
|
|
|
|
# Check that times is greater than 0
|
|
if times <= 0:
|
|
errors['times'] = 'Die Anzahl muss größer als 0 sein.'
|
|
except ValueError:
|
|
errors['times'] = 'Die Anzahl muss eine Zahl sein.'
|
|
|
|
# Check that unit is valid
|
|
if unit not in ['Tag', 'Woche', 'Monat', 'Jahr']:
|
|
errors['unit'] = 'Die Einheit ist ungültig.'
|
|
|
|
# check if list_id is an int
|
|
try:
|
|
list_id = int(list_id)
|
|
except ValueError:
|
|
errors['list_query'] = 'Die Anzahl muss eine Zahl sein.'
|
|
|
|
if errors:
|
|
return render_template(
|
|
'habit.html',
|
|
title='Erstelle ein Habit',
|
|
name=name,
|
|
note=note,
|
|
times=times,
|
|
unit=unit,
|
|
errors=errors,
|
|
list_id=list_id
|
|
)
|
|
|
|
# Map unit to integer
|
|
if unit == 'Tag':
|
|
unit = 0
|
|
elif unit == 'Woche':
|
|
unit = 1
|
|
elif unit == 'Monat':
|
|
unit = 2
|
|
elif unit == 'Jahr':
|
|
unit = 3
|
|
else:
|
|
unit = 1
|
|
|
|
# Save habit to database
|
|
habit = Habit.create(list_id, name, times, note, unit)
|
|
|
|
# Back to index
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
@app.route('/habit-list')
|
|
@login_required
|
|
def habit_list_creation():
|
|
return render_template(
|
|
'habit-list.html',
|
|
title='Erstelle eine Habitliste',
|
|
errors={},
|
|
)
|
|
|
|
|
|
@app.route('/habit-list', methods=['POST'])
|
|
@login_required
|
|
def habit_list_create():
|
|
name = request.form.get('name')
|
|
description = request.form.get('description')
|
|
|
|
# Check for errors
|
|
errors = {}
|
|
if not name:
|
|
errors['name'] = 'Der Name ist erforderlich.'
|
|
if not description:
|
|
note = ''
|
|
|
|
if errors:
|
|
return render_template(
|
|
'habit-list.html',
|
|
title='Erstelle eine Habitliste',
|
|
name=name,
|
|
description=description,
|
|
errors=errors
|
|
)
|
|
|
|
# Save habit to database
|
|
habit = HabitList.create(current_user.id, name, description)
|
|
|
|
# Back to index
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
@app.route('/profile')
|
|
@login_required
|
|
def profile():
|
|
return render_template(
|
|
"profile.html",
|
|
name=current_user.name,
|
|
email=current_user.email,
|
|
profile_image_url=current_user.profile_image,
|
|
)
|
|
|
|
|
|
@app.route('/profile', methods=['POST'])
|
|
@login_required
|
|
def profile_change():
|
|
newName = request.form.get('newName')
|
|
newEmail = request.form.get('newEmail')
|
|
|
|
# Update user
|
|
current_user.name = newName
|
|
current_user.email = newEmail
|
|
current_user.update()
|
|
|
|
# Back to profile
|
|
return render_template(
|
|
"profile.html",
|
|
name=current_user.name,
|
|
email=current_user.email,
|
|
profile_image_url=current_user.profile_image,
|
|
)
|
|
|
|
|
|
@app.route('/check_password', methods=['POST'])
|
|
@login_required
|
|
def check_password():
|
|
# Get the password sent from the client
|
|
password = request.json.get('password')
|
|
|
|
if hashlib.sha256(password.encode()).hexdigest() == current_user.password:
|
|
return jsonify({"valid": True})
|
|
else:
|
|
return jsonify({"valid": False})
|
|
|
|
|
|
@app.route('/password', methods=['POST'])
|
|
@login_required
|
|
def password_change():
|
|
newPassword = request.form.get('newPassword')
|
|
|
|
# Update user
|
|
if newPassword:
|
|
current_user.password = hashlib.sha256(newPassword.encode()).hexdigest()
|
|
current_user.update()
|
|
|
|
# Back to profile
|
|
return render_template(
|
|
"profile.html",
|
|
name=current_user.name,
|
|
email=current_user.email,
|
|
profile_image_url=current_user.profile_image,
|
|
)
|
|
|
|
|
|
def save_profile_image(image_file):
|
|
UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images
|
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
|
|
filename = image_file.filename
|
|
if '.' not in filename:
|
|
# Ensure the filename has an extension
|
|
raise ValueError("Invalid filename")
|
|
|
|
# Check if the file extension is allowed
|
|
allowed_extensions = {'jpg', 'jpeg', 'png', 'gif'}
|
|
file_extension = filename.rsplit('.', 1)[1].lower()
|
|
if file_extension not in allowed_extensions:
|
|
raise ValueError("Invalid file extension")
|
|
|
|
# Get the filename from the image path saved in the user
|
|
filename = os.path.basename(current_user.profile_image)
|
|
|
|
# Open the uploaded image
|
|
image = Image.open(image_file)
|
|
|
|
# Convert the image to RGB mode (required for JPEG)
|
|
image = image.convert('RGB')
|
|
|
|
# Determine the size of the square image
|
|
min_dimension = min(image.size)
|
|
square_size = (min_dimension, min_dimension)
|
|
|
|
# Calculate the coordinates for cropping
|
|
left = (image.width - min_dimension) / 2
|
|
top = (image.height - min_dimension) / 2
|
|
right = (image.width + min_dimension) / 2
|
|
bottom = (image.height + min_dimension) / 2
|
|
|
|
# Crop the image to a square and resize it
|
|
image = image.crop((left, top, right, bottom))
|
|
image.thumbnail(square_size)
|
|
image = image.resize((256, 256))
|
|
|
|
# Save the processed image
|
|
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
image.save(image_path, 'JPEG', quality=100)
|
|
|
|
|
|
@app.route('/upload', methods=['POST'])
|
|
def upload_profile_image():
|
|
if 'file' not in request.files:
|
|
return 'No file part'
|
|
|
|
file = request.files['file']
|
|
save_profile_image(file)
|
|
|
|
# Back to profile
|
|
return render_template(
|
|
"profile.html",
|
|
name=current_user.name,
|
|
email=current_user.email,
|
|
profile_image_url=current_user.profile_image,
|
|
errors={}
|
|
)
|
|
|
|
|
|
@app.route('/check', methods=['POST'])
|
|
@login_required
|
|
def check_habit():
|
|
habit_id = request.get_json()["habitId"]
|
|
habit = Habit.get(habit_id)
|
|
|
|
if habit is None:
|
|
return {"error": "Habit not found"}
|
|
|
|
# Check if habit belongs to user
|
|
users = habit.habit_list().get_users()
|
|
if current_user not in users:
|
|
return {"error": "Habit does not belong to user"}
|
|
|
|
trackings = habit.get_habitTrackings()
|
|
|
|
# Check if habit has been tracked today
|
|
delete_tracking = None
|
|
for tracking in trackings:
|
|
if tracking.created_at.date() == datetime.date.today():
|
|
delete_tracking = tracking
|
|
|
|
if not delete_tracking:
|
|
HabitTracking.create(habit_id)
|
|
habit.fill_statistics()
|
|
else:
|
|
delete_tracking.delete()
|
|
habit.reset_statistics()
|
|
|
|
habit.load_statistics()
|
|
|
|
|
|
return {
|
|
"habitId": habit_id,
|
|
"unchecked": not delete_tracking,
|
|
"percentage": habit.percentage,
|
|
}
|
|
|
|
|
|
@app.route('/delete', methods=['POST'])
|
|
@login_required
|
|
def delete_habit():
|
|
habit_id = request.get_json()["habitId"]
|
|
|
|
habit = Habit.get(habit_id)
|
|
|
|
if habit is None:
|
|
return {"error": "Habit not found"}
|
|
|
|
# Check if habit belongs to user
|
|
if current_user not in habit.habit_list().get_users():
|
|
return {"error": "Habit does not belong to user"}
|
|
|
|
habit.delete()
|
|
return {}
|
|
|
|
|
|
@app.route('/reorder', methods=['POST'])
|
|
@login_required
|
|
def reorder_habits():
|
|
new_index = request.get_json()["newIndex"] + 1
|
|
habit = Habit.get(request.get_json()["habitId"])
|
|
|
|
if habit is None:
|
|
return {"error": "Habit not found"}
|
|
|
|
# Check if habit belongs to user
|
|
users = habit.habit_list().get_users()
|
|
if current_user not in users:
|
|
return {"error": "Habit does not belong to user"}
|
|
|
|
habit.update_slot(new_index)
|
|
|
|
return {}
|
|
|
|
|
|
# Run the application
|
|
if __name__ == '__main__':
|
|
app.run(port=5000, debug=True)
|