eh meh working prototype i guess
This commit is contained in:
209
app.py
Normal file
209
app.py
Normal file
@@ -0,0 +1,209 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify
|
||||
from flask_bcrypt import Bcrypt
|
||||
from pymongo import MongoClient
|
||||
from bson.objectid import ObjectId
|
||||
from datetime import datetime
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
|
||||
|
||||
client = MongoClient(os.getenv('MONGO_URI'))
|
||||
db = client.get_default_database()
|
||||
bcrypt = Bcrypt(app)
|
||||
|
||||
# --- Helpers ---
|
||||
def get_user_id():
|
||||
return session.get('user_id')
|
||||
|
||||
def is_logged_in():
|
||||
return 'user_id' in session
|
||||
|
||||
# --- Routes ---
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if not is_logged_in():
|
||||
return redirect(url_for('login'))
|
||||
|
||||
user_id = get_user_id()
|
||||
|
||||
# Get all activities
|
||||
activities = list(db.activities.find({'user_id': user_id}))
|
||||
|
||||
# Check for active time entry
|
||||
current_entry = db.time_entries.find_one({
|
||||
'user_id': user_id,
|
||||
'end_time': None
|
||||
})
|
||||
|
||||
active_tasks_template = []
|
||||
if current_entry:
|
||||
# Get tasks associated with the active activity type
|
||||
active_activity = db.activities.find_one({'_id': current_entry['activity_id']})
|
||||
if active_activity:
|
||||
current_entry['activity_name'] = active_activity['name']
|
||||
current_entry['activity_color'] = active_activity.get('color', '#3498db')
|
||||
# Find template tasks for this activity
|
||||
active_tasks_template = list(db.task_templates.find({'activity_id': active_activity['_id']}))
|
||||
|
||||
return render_template('dashboard.html',
|
||||
activities=activities,
|
||||
current_entry=current_entry,
|
||||
tasks=active_tasks_template)
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
if db.users.find_one({'username': username}):
|
||||
flash('Username already exists')
|
||||
return redirect(url_for('register'))
|
||||
|
||||
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
|
||||
db.users.insert_one({'username': username, 'password': hashed_password})
|
||||
flash('Account created! Please login.')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
return render_template('register.html')
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
user = db.users.find_one({'username': request.form['username']})
|
||||
if user and bcrypt.check_password_hash(user['password'], request.form['password']):
|
||||
session['user_id'] = str(user['_id'])
|
||||
return redirect(url_for('index'))
|
||||
flash('Login Unsuccessful. Please check username and password')
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/add_activity', methods=['POST'])
|
||||
def add_activity():
|
||||
if not is_logged_in(): return redirect(url_for('login'))
|
||||
name = request.form['name']
|
||||
color = request.form.get('color', '#3498db')
|
||||
|
||||
activity_id = db.activities.insert_one({
|
||||
'user_id': get_user_id(),
|
||||
'name': name,
|
||||
'color': color
|
||||
}).inserted_id
|
||||
|
||||
# Add optional tasks
|
||||
tasks_text = request.form.get('tasks', '')
|
||||
if tasks_text:
|
||||
tasks = [t.strip() for t in tasks_text.split(',') if t.strip()]
|
||||
for t in tasks:
|
||||
db.task_templates.insert_one({
|
||||
'activity_id': activity_id,
|
||||
'user_id': get_user_id(),
|
||||
'name': t
|
||||
})
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/toggle_timer/<activity_id>', methods=['POST'])
|
||||
def toggle_timer(activity_id):
|
||||
if not is_logged_in(): return redirect(url_for('login'))
|
||||
user_id = get_user_id()
|
||||
note = request.form.get('note', '')
|
||||
|
||||
# Check if this activity is currently running
|
||||
current = db.time_entries.find_one({
|
||||
'user_id': user_id,
|
||||
'activity_id': ObjectId(activity_id),
|
||||
'end_time': None
|
||||
})
|
||||
|
||||
if current:
|
||||
# Stop it
|
||||
db.time_entries.update_one(
|
||||
{'_id': current['_id']},
|
||||
{'$set': {'end_time': datetime.now()}}
|
||||
)
|
||||
else:
|
||||
# Stop ANY other running timer first (single tasking)
|
||||
db.time_entries.update_many(
|
||||
{'user_id': user_id, 'end_time': None},
|
||||
{'$set': {'end_time': datetime.now()}}
|
||||
)
|
||||
# Start new
|
||||
db.time_entries.insert_one({
|
||||
'user_id': user_id,
|
||||
'activity_id': ObjectId(activity_id),
|
||||
'start_time': datetime.now(),
|
||||
'end_time': None,
|
||||
'note': note
|
||||
})
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/stop_timer', methods=['POST'])
|
||||
def stop_timer():
|
||||
if not is_logged_in(): return redirect(url_for('login'))
|
||||
db.time_entries.update_many(
|
||||
{'user_id': get_user_id(), 'end_time': None},
|
||||
{'$set': {'end_time': datetime.now()}}
|
||||
)
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/complete_task', methods=['POST'])
|
||||
def complete_task():
|
||||
if not is_logged_in(): return jsonify({'error': 'auth'}), 401
|
||||
|
||||
# We log that a specific task template was completed during a specific time entry
|
||||
task_name = request.form['task_name']
|
||||
entry_id = request.form['entry_id']
|
||||
|
||||
db.completed_tasks.insert_one({
|
||||
'user_id': get_user_id(),
|
||||
'task_name': task_name,
|
||||
'time_entry_id': ObjectId(entry_id),
|
||||
'completed_at': datetime.now()
|
||||
})
|
||||
return jsonify({'status': 'success'})
|
||||
|
||||
@app.route('/logbook')
|
||||
def logbook():
|
||||
if not is_logged_in(): return redirect(url_for('login'))
|
||||
|
||||
# Agrregation to join activities and tasks
|
||||
pipeline = [
|
||||
{'$match': {'user_id': get_user_id(), 'end_time': {'$ne': None}}},
|
||||
{'$sort': {'start_time': -1}},
|
||||
{'$lookup': {
|
||||
'from': 'activities',
|
||||
'localField': 'activity_id',
|
||||
'foreignField': '_id',
|
||||
'as': 'activity'
|
||||
}},
|
||||
{'$unwind': '$activity'},
|
||||
{'$lookup': {
|
||||
'from': 'completed_tasks',
|
||||
'localField': '_id',
|
||||
'foreignField': 'time_entry_id',
|
||||
'as': 'tasks'
|
||||
}}
|
||||
]
|
||||
|
||||
log = list(db.time_entries.aggregate(pipeline))
|
||||
|
||||
# Basic formatting
|
||||
for entry in log:
|
||||
duration = entry['end_time'] - entry['start_time']
|
||||
entry['duration_str'] = str(duration).split('.')[0] # HH:MM:SS
|
||||
|
||||
return render_template('logbook.html', log=log)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
Reference in New Issue
Block a user