From ab0a8f383db64653b35c2254e809c3238fddea2f Mon Sep 17 00:00:00 2001 From: hmo Date: Thu, 23 Apr 2026 20:18:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E7=AE=A1=E7=90=86CRUD=20API=E5=92=8C=E5=85=B3=E8=81=94?= =?UTF-8?q?=E8=93=9D=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__init__.py | 2 + app/routes/__init__.py | 2 +- app/routes/goals.py | 114 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 app/routes/goals.py diff --git a/app/__init__.py b/app/__init__.py index fd92061..ffeea74 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -34,9 +34,11 @@ def create_app(): # 注册蓝图 from app.routes import main_bp from app.routes.templates import templates_bp + from app.routes.goals import goals_bp app.register_blueprint(main_bp) app.register_blueprint(templates_bp) + app.register_blueprint(goals_bp) # 创建数据库和目录 with app.app_context(): diff --git a/app/routes/__init__.py b/app/routes/__init__.py index a3be51b..3d933f0 100644 --- a/app/routes/__init__.py +++ b/app/routes/__init__.py @@ -5,4 +5,4 @@ from flask import Blueprint main_bp = Blueprint("main", __name__) # 导入各路由模块 -from app.routes import students, plans, problems, settings, auth, classes, users, backup +from app.routes import students, plans, problems, settings, auth, classes, users, backup, goals diff --git a/app/routes/goals.py b/app/routes/goals.py new file mode 100644 index 0000000..1863e68 --- /dev/null +++ b/app/routes/goals.py @@ -0,0 +1,114 @@ +from flask import Blueprint, request, jsonify +from app.models import db, Goal +from app.routes.auth import login_required_json + +goals_bp = Blueprint("goals", __name__) + +@goals_bp.route("/api/goals", methods=["GET"]) +@login_required_json +def get_goals(): + goals = Goal.query.order_by(Goal.created_at.desc()).all() + return jsonify([g.to_dict() for g in goals]) + +@goals_bp.route("/api/goals", methods=["POST"]) +@login_required_json +def create_goal(): + data = request.get_json() + goal = Goal(name=data["name"], content=data.get("content", "")) + db.session.add(goal) + db.session.commit() + return jsonify(goal.to_dict()), 201 + +@goals_bp.route("/api/goals/", methods=["GET"]) +@login_required_json +def get_goal(goal_id): + goal = Goal.query.get_or_404(goal_id) + return jsonify(goal.to_dict()) + +@goals_bp.route("/api/goals/", methods=["PUT"]) +@login_required_json +def update_goal(goal_id): + goal = Goal.query.get_or_404(goal_id) + data = request.get_json() + if "name" in data: + goal.name = data["name"] + if "content" in data: + goal.content = data["content"] + db.session.commit() + return jsonify(goal.to_dict()) + +@goals_bp.route("/api/goals/", methods=["DELETE"]) +@login_required_json +def delete_goal(goal_id): + goal = Goal.query.get_or_404(goal_id) + db.session.delete(goal) + db.session.commit() + return jsonify({"message": "删除成功"}) + +# ===== 以下是 Task 3 的循环检测和关联 API ===== + +def _has_cycle(parent_id, child_id): + """检测添加 child_id 作为 parent_id 的子目标是否会形成循环""" + visited = set() + stack = [child_id] + while stack: + current = stack.pop() + if current == parent_id: + return True + if current in visited: + continue + visited.add(current) + from app.models import GoalRelation + for rel in GoalRelation.query.filter_by(parent_goal_id=current).all(): + stack.append(rel.child_goal_id) + return False + +@goals_bp.route("/api/goals//children", methods=["GET"]) +@login_required_json +def get_goal_children(goal_id): + from app.models import GoalRelation + relations = GoalRelation.query.filter_by(parent_goal_id=goal_id).all() + child_ids = [r.child_goal_id for r in relations] + children = Goal.query.filter(Goal.id.in_(child_ids)).all() if child_ids else [] + return jsonify([c.to_dict() for c in children]) + +@goals_bp.route("/api/goals//parents", methods=["GET"]) +@login_required_json +def get_goal_parents(goal_id): + from app.models import GoalRelation + relations = GoalRelation.query.filter_by(child_goal_id=goal_id).all() + parent_ids = [r.parent_goal_id for r in relations] + parents = Goal.query.filter(Goal.id.in_(parent_ids)).all() if parent_ids else [] + return jsonify([p.to_dict() for p in parents]) + +@goals_bp.route("/api/goals//children", methods=["POST"]) +@login_required_json +def add_goal_child(goal_id): + from app.models import GoalRelation + data = request.get_json() + child_id = data["child_goal_id"] + + if goal_id == child_id: + return jsonify({"error": "不能将目标关联到自身"}), 400 + + if _has_cycle(goal_id, child_id): + return jsonify({"error": "添加此关联会形成循环引用"}), 400 + + existing = GoalRelation.query.filter_by(parent_goal_id=goal_id, child_goal_id=child_id).first() + if existing: + return jsonify({"error": "关联已存在"}), 400 + + relation = GoalRelation(parent_goal_id=goal_id, child_goal_id=child_id) + db.session.add(relation) + db.session.commit() + return jsonify({"message": "添加成功"}) + +@goals_bp.route("/api/goals//children/", methods=["DELETE"]) +@login_required_json +def remove_goal_child(goal_id, child_id): + from app.models import GoalRelation + relation = GoalRelation.query.filter_by(parent_goal_id=goal_id, child_goal_id=child_id).first() + if relation: + db.session.delete(relation) + db.session.commit() + return jsonify({"message": "移除成功"}) \ No newline at end of file