from flask import Flask, render_template, request, jsonify, session, redirect, url_for from datetime import datetime import database import auth import config app = Flask(__name__) app.secret_key = config.SECRET_KEY database.init_db() @app.route("/") def index(): if "user_id" in session: return redirect(url_for("goals_page")) return redirect(url_for("login_page")) @app.route("/login") def login_page(): return render_template("login.html") @app.route("/register") def register_page(): return render_template("register.html") @app.route("/goals") @auth.login_required def goals_page(): return render_template("goals.html") @app.route("/tasks") @auth.login_required def tasks_page(): return render_template("tasks.html") @app.route("/admin") @auth.login_required def admin_page(): user = auth.get_current_user() if not user or user.get("role") != "admin": return redirect(url_for("goals_page")) return render_template("admin.html") @app.route("/notes") @auth.login_required def notes_page(): return render_template("notes.html") @app.route("/api/auth/register", methods=["POST"]) def api_register(): data = request.get_json() username = data.get("username", "").strip() password = data.get("password", "") if not username or not password: return jsonify({"success": False, "message": "Username and password are required"}), 400 if len(password) < 6: return jsonify({"success": False, "message": "Password must be at least 6 characters"}), 400 if database.get_user_by_username(username): return jsonify({"success": False, "message": "Username already exists"}), 400 password_hash = auth.hash_password(password) user = database.create_user(username, password_hash) session["user_id"] = user["id"] session["username"] = username return jsonify({ "success": True, "message": "Registration successful", "user_id": user["id"] }), 201 @app.route("/api/auth/login", methods=["POST"]) def api_login(): data = request.get_json() username = data.get("username", "").strip() password = data.get("password", "") if not username or not password: return jsonify({"success": False, "message": "Username and password are required"}), 400 user = database.get_user_by_username(username) if not user or not auth.check_password(password, user["password_hash"]): return jsonify({"success": False, "message": "Invalid credentials"}), 401 session["user_id"] = user["id"] session["username"] = username return jsonify({ "success": True, "message": "Login successful", "user": { "user_id": user["id"], "username": user["username"], "role": user["role"], "max_goals": user["max_goals"], "selected_goal_id": user.get("selected_goal_id") } }) @app.route("/api/auth/logout", methods=["POST"]) @auth.login_required def api_logout(): session.clear() return jsonify({"success": True, "message": "Logged out"}) @app.route("/api/auth/me", methods=["GET"]) @auth.login_required def api_me(): user = auth.get_current_user() if not user: return jsonify({"success": False, "message": "User not found"}), 404 return jsonify({ "user_id": user["id"], "username": user["username"], "role": user["role"], "max_goals": user["max_goals"], "selected_goal_id": user.get("selected_goal_id") }) @app.route("/api/user/selected-goal", methods=["PATCH"]) @auth.login_required def api_set_selected_goal(): data = request.get_json() goal_id = data.get("goal_id") database.update_user(session["user_id"], selected_goal_id=goal_id) return jsonify({"success": True, "selected_goal_id": goal_id}) @app.route("/api/goals", methods=["GET"]) @auth.login_required def api_get_goals(): user_id = session["user_id"] goals = database.get_goals_by_user(user_id) result = [] for goal in goals: goal_dict = dict(goal) goal_dict["id"] = goal["id"] result.append(goal_dict) return jsonify(result) @app.route("/api/goals", methods=["POST"]) @auth.login_required def api_create_goal(): data = request.get_json() title = data.get("title", "").strip() if not title: return jsonify({"success": False, "message": "Title is required"}), 400 user_id = session["user_id"] user = database.get_user_by_id(user_id) if database.count_goals_by_user(user_id) >= user["max_goals"]: return jsonify({ "success": False, "message": f"Goal limit reached ({user['max_goals']})" }), 400 goal = database.create_goal(user_id, title) return jsonify({"success": True, "goal_id": goal["id"]}), 201 @app.route("/api/goals/", methods=["PUT"]) @auth.login_required def api_update_goal(goal_id): goal = database.get_goal_by_id(goal_id) if not goal or goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Goal not found"}), 404 data = request.get_json() updates = {} if "title" in data: title = data["title"].strip() if not title: return jsonify({"success": False, "message": "Title cannot be empty"}), 400 updates["title"] = title if "activated" in data: updates["activated"] = bool(data["activated"]) database.update_goal(goal_id, **updates) return jsonify({"success": True}) @app.route("/api/goals/", methods=["DELETE"]) @auth.login_required def api_delete_goal(goal_id): goal = database.get_goal_by_id(goal_id) if not goal or goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Goal not found"}), 404 database.delete_goal(goal_id) return jsonify({"success": True}) @app.route("/api/goals//toggle", methods=["PATCH"]) @auth.login_required def api_toggle_goal(goal_id): goal = database.get_goal_by_id(goal_id) if not goal or goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Goal not found"}), 404 new_status = not goal["activated"] database.update_goal(goal_id, activated=new_status) return jsonify({"success": True, "activated": new_status}) @app.route("/api/goals//selected-task", methods=["PATCH"]) @auth.login_required def api_set_selected_task(goal_id): goal = database.get_goal_by_id(goal_id) if not goal or goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Goal not found"}), 404 data = request.get_json() task_id = data.get("task_id") database.update_goal(goal_id, selected_task_id=task_id) return jsonify({"success": True, "selected_task_id": task_id}) @app.route("/api/tasks", methods=["GET"]) @auth.login_required def api_get_tasks(): goal_id = request.args.get("goal_id", type=int) if not goal_id: return jsonify([]) goal = database.get_goal_by_id(goal_id) if not goal or goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Goal not found"}), 404 tasks = database.get_tasks_sorted(goal_id) result = [] for task in tasks: task_dict = dict(task) task_dict["id"] = task["id"] result.append(task_dict) return jsonify(result) @app.route("/api/tasks", methods=["POST"]) @auth.login_required def api_create_task(): data = request.get_json() goal_id = data.get("goal_id") title = data.get("title", "").strip() desc = data.get("desc", "").strip() if not goal_id or not title: return jsonify({"success": False, "message": "Goal ID and title are required"}), 400 goal = database.get_goal_by_id(goal_id) if not goal or goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Goal not found"}), 404 if not goal["activated"]: return jsonify({"success": False, "message": "Cannot add task to deactivated goal"}), 400 task = database.create_task(goal_id, title, desc) return jsonify({"success": True, "task_id": task["id"]}), 201 @app.route("/api/tasks/", methods=["PUT"]) @auth.login_required def api_update_task(task_id): task = database.get_task_by_id(task_id) if not task: return jsonify({"success": False, "message": "Task not found"}), 404 goal = database.get_goal_by_id(task["goal_id"]) if goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Task not found"}), 404 data = request.get_json() updates = {} if "title" in data: title = data["title"].strip() if not title: return jsonify({"success": False, "message": "Title cannot be empty"}), 400 updates["title"] = title if "desc" in data: updates["desc"] = data["desc"] database.update_task(task_id, **updates) return jsonify({"success": True}) @app.route("/api/tasks/", methods=["DELETE"]) @auth.login_required def api_delete_task(task_id): task = database.get_task_by_id(task_id) if not task: return jsonify({"success": False, "message": "Task not found"}), 404 goal = database.get_goal_by_id(task["goal_id"]) if goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Task not found"}), 404 database.delete_task(task_id) return jsonify({"success": True}) @app.route("/api/tasks//status", methods=["PATCH"]) @auth.login_required def api_update_task_status(task_id): task = database.get_task_by_id(task_id) if not task: return jsonify({"success": False, "message": "Task not found"}), 404 goal = database.get_goal_by_id(task["goal_id"]) if goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Task not found"}), 404 data = request.get_json() new_status = data.get("status") if new_status not in ["todo", "doing", "pending", "done"]: return jsonify({"success": False, "message": "Invalid status"}), 400 updates = {"status": new_status} if new_status == "doing": doing_task = None all_tasks = database.get_tasks_by_goal(task["goal_id"]) for t in all_tasks: if t.get("status") == "doing" and t["id"] != task["id"]: doing_task = t break if doing_task: database.update_task(doing_task["id"], status="todo") updates["start_time"] = datetime.now().isoformat() else: updates["start_time"] = None if new_status == "done": updates["finished_time"] = datetime.now().isoformat() else: updates["finished_time"] = None database.update_task(task_id, **updates) return jsonify({"success": True}) @app.route("/api/tasks//order", methods=["PATCH"]) @auth.login_required def api_update_task_order(task_id): task = database.get_task_by_id(task_id) if not task: return jsonify({"success": False, "message": "Task not found"}), 404 goal = database.get_goal_by_id(task["goal_id"]) if goal["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Task not found"}), 404 data = request.get_json() new_order = data.get("order") if new_order is None: return jsonify({"success": False, "message": "Order value required"}), 400 database.update_task(task_id, order=float(new_order)) return jsonify({"success": True}) @app.route("/api/notes", methods=["GET"]) @auth.login_required def api_get_notes(): goal_id = request.args.get("goal_id", type=int) search = request.args.get("search", "").strip() or None notes = database.get_notes(session["user_id"], goal_id, search) return jsonify(notes) @app.route("/api/notes", methods=["POST"]) @auth.login_required def api_create_note(): data = request.get_json() title = data.get("title", "").strip() if not title: return jsonify({"success": False, "message": "Title is required"}), 400 goal_id = data.get("goal_id") task_id = data.get("task_id") content = data.get("content", "") note = database.create_note(session["user_id"], goal_id, task_id, title, content) return jsonify(note), 201 @app.route("/api/notes/", methods=["GET"]) @auth.login_required def api_get_note(note_id): note = database.get_note_by_id(note_id) if not note or note["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Note not found"}), 404 return jsonify(note) @app.route("/api/notes/", methods=["PUT"]) @auth.login_required def api_update_note(note_id): note = database.get_note_by_id(note_id) if not note or note["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Note not found"}), 404 data = request.get_json() title = data.get("title", "").strip() if not title: return jsonify({"success": False, "message": "Title is required"}), 400 updated = database.update_note(note_id, title, data.get("content", "")) return jsonify(updated) @app.route("/api/notes/", methods=["DELETE"]) @auth.login_required def api_delete_note(note_id): note = database.get_note_by_id(note_id) if not note or note["user_id"] != session["user_id"]: return jsonify({"success": False, "message": "Note not found"}), 404 database.delete_note(note_id) return jsonify({"success": True}) @app.route("/api/admin/users", methods=["GET"]) @auth.admin_required def api_get_users(): users = database.get_all_users() result = [] for user in users: result.append({ "user_id": user["id"], "username": user["username"], "role": user["role"], "max_goals": user["max_goals"] }) return jsonify(result) @app.route("/api/admin/users/", methods=["PUT"]) @auth.admin_required def api_update_user(user_id): user = database.get_user_by_id(user_id) if not user: return jsonify({"success": False, "message": "User not found"}), 404 data = request.get_json() updates = {} if "max_goals" in data: max_goals = data["max_goals"] if not isinstance(max_goals, int) or max_goals < 1: return jsonify({"success": False, "message": "Invalid max_goals value"}), 400 updates["max_goals"] = max_goals if "role" in data: role = data["role"] if role not in ["user", "admin"]: return jsonify({"success": False, "message": "Invalid role"}), 400 updates["role"] = role database.update_user(user_id, **updates) return jsonify({"success": True}) if __name__ == "__main__": app.run(host=config.HOST, port=config.PORT, debug=config.DEBUG)