231 lines
8.5 KiB
Python
231 lines
8.5 KiB
Python
from flask import Flask, render_template, request, jsonify
|
|
from tinydb import TinyDB, Query
|
|
from datetime import datetime, timedelta
|
|
|
|
app = Flask(__name__)
|
|
db = TinyDB('habits.json')
|
|
Habit = Query()
|
|
|
|
# Helper function to get the current date string
|
|
def get_current_date():
|
|
return datetime.now().strftime('%Y-%m-%d')
|
|
|
|
# Helper function to get a list of dates for the last 30 days
|
|
def get_last_30_days():
|
|
dates = []
|
|
for i in range(30):
|
|
date = datetime.now() - timedelta(days=i)
|
|
dates.append(date.strftime('%Y-%m-%d'))
|
|
return dates[::-1] # Reverse to have oldest first
|
|
|
|
# Helper function to get dates organized by weekday for more weeks to fill the width
|
|
def get_weekly_dates():
|
|
today = datetime.now()
|
|
# Find the most recent Monday (start of current week)
|
|
days_since_monday = today.weekday() # Monday = 0, Sunday = 6
|
|
current_monday = today - timedelta(days=days_since_monday)
|
|
|
|
weekly_data = {}
|
|
weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
|
|
|
# Use more weeks to fill the available width (about 15-20 weeks)
|
|
num_weeks = 18 # This should provide good coverage without being too much
|
|
|
|
for weekday_idx in range(7): # 0 = Monday, 6 = Sunday
|
|
weekly_data[weekdays[weekday_idx]] = []
|
|
for week_offset in range(num_weeks):
|
|
date = current_monday + timedelta(days=weekday_idx - (week_offset * 7))
|
|
if date <= today: # Only include dates up to today
|
|
weekly_data[weekdays[weekday_idx]].append(date.strftime('%Y-%m-%d'))
|
|
|
|
# Reverse each weekday list to show oldest first (left to right)
|
|
for weekday in weekly_data:
|
|
weekly_data[weekday] = weekly_data[weekday][::-1]
|
|
|
|
return weekly_data
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
@app.route('/habits', methods=['GET'])
|
|
def get_habits():
|
|
raw_habits = db.all()
|
|
formatted_habits = []
|
|
weekly_dates = get_weekly_dates()
|
|
|
|
for habit_doc in raw_habits:
|
|
# Handle migration from old format (list) to new format (dict)
|
|
completed_dates = habit_doc.get('completed_dates', {})
|
|
if isinstance(completed_dates, list):
|
|
# Convert old format to new format
|
|
new_completed_dates = {}
|
|
for date in completed_dates:
|
|
new_completed_dates[date] = 1
|
|
completed_dates = new_completed_dates
|
|
# Update in database
|
|
db.update({'completed_dates': completed_dates}, doc_ids=[habit_doc.doc_id])
|
|
|
|
current_habit_data = {
|
|
'id': habit_doc.doc_id,
|
|
'name': habit_doc.get('name'),
|
|
'completed_dates': completed_dates,
|
|
'color': habit_doc.get('color', '#4CAF50'),
|
|
'daily_target': habit_doc.get('daily_target', 1)
|
|
}
|
|
|
|
# Create weekly completion history
|
|
weekly_completion = {}
|
|
for weekday, dates in weekly_dates.items():
|
|
weekly_completion[weekday] = []
|
|
for date_str in dates:
|
|
completed_count = completed_dates.get(date_str, 0)
|
|
target_count = current_habit_data['daily_target']
|
|
weekly_completion[weekday].append({
|
|
'date': date_str,
|
|
'completed': completed_count,
|
|
'target': target_count,
|
|
'is_complete': completed_count >= target_count
|
|
})
|
|
|
|
current_habit_data['weekly_completion'] = weekly_completion
|
|
formatted_habits.append(current_habit_data)
|
|
|
|
return jsonify(formatted_habits)
|
|
|
|
@app.route('/habits', methods=['POST'])
|
|
def add_habit():
|
|
data = request.json
|
|
name = data.get('name')
|
|
color = data.get('color', '#4CAF50') # Default green color
|
|
daily_target = data.get('daily_target', 1) # Default 1x per day
|
|
if not name:
|
|
return jsonify({'error': 'Habit name is required'}), 400
|
|
|
|
habit_id = db.insert({'name': name, 'completed_dates': {}, 'color': color, 'daily_target': daily_target})
|
|
return jsonify({'id': habit_id, 'name': name, 'completed_dates': {}, 'color': color, 'daily_target': daily_target}), 201
|
|
|
|
@app.route('/habits/<int:habit_id>/complete', methods=['POST'])
|
|
def complete_habit(habit_id):
|
|
date_to_complete = request.json.get('date', get_current_date())
|
|
|
|
habit = db.get(doc_id=habit_id)
|
|
if not habit:
|
|
return jsonify({'error': 'Habit not found'}), 404
|
|
|
|
# Handle migration from old format (list) to new format (dict)
|
|
completed_dates = habit.get('completed_dates', {})
|
|
if isinstance(completed_dates, list):
|
|
new_completed_dates = {}
|
|
for date in completed_dates:
|
|
new_completed_dates[date] = 1
|
|
completed_dates = new_completed_dates
|
|
|
|
# Increment completion count for the date
|
|
current_count = completed_dates.get(date_to_complete, 0)
|
|
completed_dates[date_to_complete] = current_count + 1
|
|
|
|
db.update({'completed_dates': completed_dates}, doc_ids=[habit_id])
|
|
|
|
return jsonify({
|
|
'message': f'Habit {habit_id} marked as completed for {date_to_complete}',
|
|
'completed_dates': completed_dates,
|
|
'current_count': completed_dates[date_to_complete]
|
|
})
|
|
|
|
@app.route('/habits/<int:habit_id>/uncomplete', methods=['POST'])
|
|
def uncomplete_habit(habit_id):
|
|
date_to_uncomplete = request.json.get('date', get_current_date())
|
|
|
|
habit = db.get(doc_id=habit_id)
|
|
if not habit:
|
|
return jsonify({'error': 'Habit not found'}), 404
|
|
|
|
# Handle migration from old format (list) to new format (dict)
|
|
completed_dates = habit.get('completed_dates', {})
|
|
if isinstance(completed_dates, list):
|
|
new_completed_dates = {}
|
|
for date in completed_dates:
|
|
new_completed_dates[date] = 1
|
|
completed_dates = new_completed_dates
|
|
|
|
# Decrement completion count for the date
|
|
if date_to_uncomplete in completed_dates:
|
|
current_count = completed_dates[date_to_uncomplete]
|
|
if current_count > 1:
|
|
completed_dates[date_to_uncomplete] = current_count - 1
|
|
else:
|
|
del completed_dates[date_to_uncomplete]
|
|
|
|
db.update({'completed_dates': completed_dates}, doc_ids=[habit_id])
|
|
|
|
return jsonify({
|
|
'message': f'Habit {habit_id} marked as uncompleted for {date_to_uncomplete}',
|
|
'completed_dates': completed_dates,
|
|
'current_count': completed_dates.get(date_to_uncomplete, 0)
|
|
})
|
|
|
|
@app.route('/habits/<int:habit_id>', methods=['PUT'])
|
|
def update_habit(habit_id):
|
|
data = request.json
|
|
name = data.get('name')
|
|
color = data.get('color')
|
|
daily_target = data.get('daily_target')
|
|
|
|
if not name and not color and not daily_target:
|
|
return jsonify({'error': 'Habit name, color, or daily target is required'}), 400
|
|
|
|
habit = db.get(doc_id=habit_id)
|
|
if not habit:
|
|
return jsonify({'error': 'Habit not found'}), 404
|
|
|
|
update_data = {}
|
|
if name:
|
|
update_data['name'] = name
|
|
if color:
|
|
update_data['color'] = color
|
|
if daily_target:
|
|
update_data['daily_target'] = int(daily_target)
|
|
|
|
db.update(update_data, doc_ids=[habit_id])
|
|
return jsonify({'message': f'Habit {habit_id} updated', **update_data})
|
|
|
|
@app.route('/habits/<int:habit_id>/reset', methods=['POST'])
|
|
def reset_habit(habit_id):
|
|
date_to_reset = request.json.get('date', get_current_date())
|
|
|
|
habit = db.get(doc_id=habit_id)
|
|
if not habit:
|
|
return jsonify({'error': 'Habit not found'}), 404
|
|
|
|
# Handle migration from old format (list) to new format (dict)
|
|
completed_dates = habit.get('completed_dates', {})
|
|
if isinstance(completed_dates, list):
|
|
new_completed_dates = {}
|
|
for date in completed_dates:
|
|
new_completed_dates[date] = 1
|
|
completed_dates = new_completed_dates
|
|
|
|
# Reset completion count for the date to 0
|
|
if date_to_reset in completed_dates:
|
|
del completed_dates[date_to_reset]
|
|
|
|
db.update({'completed_dates': completed_dates}, doc_ids=[habit_id])
|
|
|
|
return jsonify({
|
|
'message': f'Habit {habit_id} reset for {date_to_reset}',
|
|
'completed_dates': completed_dates,
|
|
'current_count': 0
|
|
})
|
|
|
|
@app.route('/habits/<int:habit_id>', methods=['DELETE'])
|
|
def delete_habit(habit_id):
|
|
habit = db.get(doc_id=habit_id)
|
|
if not habit:
|
|
return jsonify({'error': 'Habit not found'}), 404
|
|
|
|
db.remove(doc_ids=[habit_id])
|
|
return jsonify({'message': f'Habit {habit_id} deleted'})
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=True) |