bujo_v1/app/routes.py
2026-07-22 21:48:24 +02:00

365 lines
No EOL
13 KiB
Python

from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
from flask_login import login_required, current_user
from datetime import datetime, date
from . import db
from .models import init_db_models
# Import model classes (stored on init_db_models function)
DailyLog = init_db_models.DailyLog
DailyLogItem = init_db_models.DailyLogItem
UserSettings = init_db_models.UserSettings
bp = Blueprint('main', __name__)
# ─── Step 2: Daily Log (main page) ───────────────────────────────────
@bp.route('/')
@login_required
def index():
"""Daily Log screen — main page (Step 2)."""
today = date.today()
log = DailyLog.query.filter_by(date=today, user_id=current_user.id).first()
if not log:
log = DailyLog(date=today, user_id=current_user.id)
db.session.add(log)
db.session.commit()
# Get settings
settings = UserSettings.query.filter_by(user_id=current_user.id).first()
if not settings:
settings = UserSettings(user_id=current_user.id)
db.session.add(settings)
db.session.commit()
return render_template('daily_log.html', log=log, settings=settings)
# ─── Step 2: All Logs ────────────────────────────────────────────────
@bp.route('/logs')
@login_required
def log_list():
"""All Logs screen — list of dates (Step 2)."""
logs = DailyLog.query.filter_by(user_id=current_user.id)\
.order_by(DailyLog.date.desc()).all()
return render_template('log_list.html', logs=logs)
# ─── Step 2: Daily Log Detail (by date) ──────────────────────────────
@bp.route('/logs/<int:log_id>')
@login_required
def log_detail(log_id):
"""Open the Daily Log for a specific date."""
log = DailyLog.query.get_or_404(log_id)
if log.user_id != current_user.id:
flash('Access denied.')
return redirect(url_for('main.log_list'))
return render_template('daily_log.html', log=log, settings=None)
# ─── Step 4: Create / Edit Log ───────────────────────────────────────
@bp.route('/logs/new', methods=['GET', 'POST'])
@login_required
def log_new():
"""Create a new Daily Log entry."""
if request.method == 'POST':
log_date = request.form.get('date')
try:
log_date = datetime.strptime(log_date, '%Y-%m-%d').date()
except ValueError:
flash('Invalid date format.')
return render_template('daily_log_form.html', action='New')
existing = DailyLog.query.filter_by(date=log_date, user_id=current_user.id).first()
if existing:
flash('Log for this date already exists.')
return redirect(url_for('main.log_detail', log_id=existing.id))
log = DailyLog(
date=log_date,
prompt=request.form.get('prompt', ''),
notes=request.form.get('notes', ''),
bedtime=request.form.get('bedtime', ''),
wake_time=request.form.get('wake_time', ''),
sleep_quality=request.form.get('sleep_quality'),
mood=request.form.get('mood', ''),
user_id=current_user.id,
)
db.session.add(log)
db.session.commit()
# Handle gratitude (3 fields)
gratitude = [request.form.get(f'gratitude_{i}', '') for i in range(3)]
log.gratitude = gratitude
# Handle bullets / items
item_types = request.form.getlist('item_type[]')
item_symbols = request.form.getlist('item_symbol[]')
item_texts = request.form.getlist('item_text[]')
item_statuses = request.form.getlist('item_status[]')
for i in range(len(item_types)):
if item_types[i] and item_texts[i].strip():
item = DailyLogItem(
log_id=log.id,
type=item_types[i],
symbol=item_symbols[i] if item_symbols[i] else '',
text=item_texts[i].strip(),
status=item_statuses[i] if item_statuses[i] in ['todo', 'doing', 'done', 'cancelled'] else 'todo',
)
db.session.add(item)
db.session.commit()
flash('Log saved!')
return redirect(url_for('main.log_detail', log_id=log.id))
return render_template('daily_log_form.html', action='New')
@bp.route('/logs/<int:log_id>/edit', methods=['GET', 'POST'])
@login_required
def log_edit(log_id):
"""Edit an existing Daily Log entry."""
log = DailyLog.query.get_or_404(log_id)
if log.user_id != current_user.id:
flash('Access denied.')
return redirect(url_for('main.log_list'))
if request.method == 'POST':
log.prompt = request.form.get('prompt', '')
log.notes = request.form.get('notes', '')
log.bedtime = request.form.get('bedtime', '')
log.wake_time = request.form.get('wake_time', '')
log.sleep_quality = request.form.get('sleep_quality')
log.mood = request.form.get('mood', '')
# Handle gratitude
gratitude = [request.form.get(f'gratitude_{i}', '') for i in range(3)]
log.gratitude = gratitude
# Handle existing items and new items
item_ids = request.form.getlist('item_id[]')
item_types = request.form.getlist('item_type[]')
item_symbols = request.form.getlist('item_symbol[]')
item_texts = request.form.getlist('item_text[]')
item_statuses = request.form.getlist('item_status[]')
# Remove items not in item_ids
existing_ids = [int(i) for i in item_ids if i.isdigit()]
for item in log.items:
if item.id not in existing_ids:
db.session.delete(item)
for i in range(len(item_types)):
if item_ids[i] and item_ids[i].isdigit():
item = DailyLogItem.query.get(int(item_ids[i]))
if item:
item.type = item_types[i]
item.symbol = item_symbols[i] if item_symbols[i] else ''
item.text = item_texts[i].strip()
item.status = item_statuses[i] if item_statuses[i] in ['todo', 'doing', 'done', 'cancelled'] else 'todo'
elif item_types[i] and item_texts[i].strip():
item = DailyLogItem(
log_id=log.id,
type=item_types[i],
symbol=item_symbols[i] if item_symbols[i] else '',
text=item_texts[i].strip(),
status=item_statuses[i] if item_statuses[i] in ['todo', 'doing', 'done', 'cancelled'] else 'todo',
)
db.session.add(item)
db.session.commit()
flash('Log updated!')
return redirect(url_for('main.log_detail', log_id=log.id))
return render_template('daily_log_form.html', log=log, action='Edit')
# ─── Step 4: Add / Update Bullet (AJAX) ──────────────────────────────
@bp.route('/logs/<int:log_id>/items', methods=['POST'])
@login_required
def add_log_item(log_id):
"""Add a bullet/item to a log (AJAX endpoint)."""
log = DailyLog.query.get_or_404(log_id)
if log.user_id != current_user.id:
return jsonify({'success': False, 'error': 'Access denied.'}), 403
data = request.get_json()
item_type = data.get('type', 'bullet')
symbol = data.get('symbol', '')
text = data.get('text', '').strip()
status = data.get('status', 'todo')
if not text:
return jsonify({'success': False, 'error': 'Text is required.'}), 400
item = DailyLogItem(
log_id=log.id,
type=item_type,
symbol=symbol,
text=text,
status=status,
)
db.session.add(item)
db.session.commit()
return jsonify({
'success': True,
'id': item.id,
'type': item.type,
'symbol': item.symbol,
'text': item.text,
'status': item.status,
})
@bp.route('/logs/items/<int:item_id>', methods=['PUT'])
@login_required
def update_log_item(item_id):
"""Update a bullet/item status or text (AJAX endpoint)."""
item = DailyLogItem.query.get_or_404(item_id)
if item.log.user_id != current_user.id:
return jsonify({'success': False, 'error': 'Access denied.'}), 403
data = request.get_json()
if 'status' in data:
item.status = data['status']
if 'text' in data:
item.text = data['text'].strip()
db.session.commit()
return jsonify({'success': True, 'status': item.status, 'text': item.text})
@bp.route('/logs/items/<int:item_id>', methods=['DELETE'])
@login_required
def delete_log_item(item_id):
"""Delete a bullet/item (AJAX endpoint)."""
item = DailyLogItem.query.get_or_404(item_id)
if item.log.user_id != current_user.id:
return jsonify({'success': False, 'error': 'Access denied.'}), 403
db.session.delete(item)
db.session.commit()
return jsonify({'success': True})
# ─── Step 4: Export / Import ─────────────────────────────────────────
@bp.route('/export')
@login_required
def export_logs():
"""Export all logs as JSON (Step 4)."""
logs = DailyLog.query.filter_by(user_id=current_user.id).all()
data = []
for log in logs:
log_data = {
'date': log.date.isoformat(),
'prompt': log.prompt,
'notes': log.notes,
'gratitude': log.gratitude or [],
'bedtime': log.bedtime,
'wake_time': log.wake_time,
'sleep_quality': log.sleep_quality,
'mood': log.mood,
'items': [
{
'type': item.type.value,
'symbol': item.symbol,
'text': item.text,
'status': item.status.value,
}
for item in log.items
],
}
data.append(log_data)
return jsonify(data), 200, {'Content-Type': 'application/json', 'Content-Disposition': 'attachment; filename="logs.json"'}
@bp.route('/import', methods=['POST'])
@login_required
def import_logs():
"""Import logs from JSON file (Step 4)."""
if 'file' not in request.files:
flash('No file uploaded.')
return redirect(url_for('main.settings'))
file = request.files['file']
if not file or not file.filename.endswith('.json'):
flash('Invalid file. Please upload a JSON file.')
return redirect(url_for('main.settings'))
import json as json_module
try:
data = file.read().decode('utf-8')
logs_data = json_module.loads(data)
imported = 0
for log_data in logs_data:
log_date = datetime.strptime(log_data['date'], '%Y-%m-%d').date()
existing = DailyLog.query.filter_by(date=log_date, user_id=current_user.id).first()
if existing:
continue # Skip existing
log = DailyLog(
date=log_date,
prompt=log_data.get('prompt', ''),
notes=log_data.get('notes', ''),
gratitude=log_data.get('gratitude', []),
bedtime=log_data.get('bedtime', ''),
wake_time=log_data.get('wake_time', ''),
sleep_quality=log_data.get('sleep_quality'),
mood=log_data.get('mood', ''),
user_id=current_user.id,
)
db.session.add(log)
db.session.flush()
for item_data in log_data.get('items', []):
item = DailyLogItem(
log_id=log.id,
type=item_data.get('type', 'bullet'),
symbol=item_data.get('symbol', ''),
text=item_data.get('text', ''),
status=item_data.get('status', 'todo'),
)
db.session.add(item)
imported += 1
db.session.commit()
flash(f'{imported} logs imported!')
except Exception as e:
db.session.rollback()
flash(f'Import failed: {str(e)}')
return redirect(url_for('main.settings'))
# ─── Step 4: Settings ────────────────────────────────────────────────
@bp.route('/settings')
@login_required
def settings():
"""Settings screen (Step 2)."""
s = UserSettings.query.filter_by(user_id=current_user.id).first()
if not s:
s = UserSettings(user_id=current_user.id)
db.session.add(s)
db.session.commit()
return render_template('settings.html', settings=s)
@bp.route('/settings/update', methods=['POST'])
@login_required
def update_settings():
"""Update user settings."""
s = UserSettings.query.filter_by(user_id=current_user.id).first()
if not s:
s = UserSettings(user_id=current_user.id)
db.session.add(s)
s.theme = request.form.get('theme', 'light')
s.font_size = int(request.form.get('font_size', 18))
db.session.commit()
flash('Settings saved!')
return redirect(url_for('main.settings'))