DRY refactoring pass: shared mixins, route helpers, composables, CSS
Backend: - models/base.py: TimestampMixin + CreatedAtMixin; applied to all 10+ models - routes/utils.py: not_found() + parse_iso_date() helpers; used across all route files - routes/milestones.py: _milestone_dict() helper replaces 5 repeated to_dict + progress blocks Frontend: - 5 new composables: useAutoSave, useEditorGuards, useTagSuggestions, useFloatingAssist, useListKeyboardNavigation - ConfirmDialog.vue: reusable confirm modal replacing inline <teleport> blocks - editor-shared.css: sidebar CSS consolidated from both editor views - viewer-shared.css: context-bar/breadcrumb CSS consolidated from both viewer views - NoteEditorView + TaskEditorView: ~120 lines each replaced with composable calls; duplicate scoped sidebar CSS removed - NotesListView + TasksListView: inline keyboard-nav replaced with composable - NoteViewerView + TaskViewerView: duplicate context-bar CSS removed No behaviour changes. Net: -634 lines, +237 lines across 31 files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import logging
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from fabledassistant.auth import login_required, get_current_user_id
|
||||
from fabledassistant.routes.utils import not_found
|
||||
from fabledassistant.services.milestones import (
|
||||
create_milestone,
|
||||
delete_milestone,
|
||||
@@ -20,21 +21,22 @@ logger = logging.getLogger(__name__)
|
||||
milestones_bp = Blueprint("milestones", __name__, url_prefix="/api/projects")
|
||||
|
||||
|
||||
async def _milestone_dict(m) -> dict:
|
||||
d = m.to_dict()
|
||||
d.update(await get_milestone_progress(m.id))
|
||||
return d
|
||||
|
||||
|
||||
@milestones_bp.route("/<int:project_id>/milestones", methods=["GET"])
|
||||
@login_required
|
||||
async def list_milestones_route(project_id: int):
|
||||
uid = get_current_user_id()
|
||||
project = await get_project(uid, project_id)
|
||||
if project is None:
|
||||
return jsonify({"error": "Project not found"}), 404
|
||||
return not_found("Project")
|
||||
status = request.args.get("status")
|
||||
milestones = await list_milestones(uid, project_id, status=status)
|
||||
result = []
|
||||
for m in milestones:
|
||||
entry = m.to_dict()
|
||||
progress = await get_milestone_progress(m.id)
|
||||
entry.update(progress)
|
||||
result.append(entry)
|
||||
result = [await _milestone_dict(m) for m in milestones]
|
||||
return jsonify({"milestones": result})
|
||||
|
||||
|
||||
@@ -44,7 +46,7 @@ async def create_milestone_route(project_id: int):
|
||||
uid = get_current_user_id()
|
||||
project = await get_project(uid, project_id)
|
||||
if project is None:
|
||||
return jsonify({"error": "Project not found"}), 404
|
||||
return not_found("Project")
|
||||
data = await request.get_json()
|
||||
if not data.get("title"):
|
||||
return jsonify({"error": "title is required"}), 400
|
||||
@@ -55,9 +57,7 @@ async def create_milestone_route(project_id: int):
|
||||
description=data.get("description"),
|
||||
order_index=data.get("order_index", 0),
|
||||
)
|
||||
entry = milestone.to_dict()
|
||||
entry.update(await get_milestone_progress(milestone.id))
|
||||
return jsonify(entry), 201
|
||||
return jsonify(await _milestone_dict(milestone)), 201
|
||||
|
||||
|
||||
@milestones_bp.route("/<int:project_id>/milestones/<int:milestone_id>", methods=["GET"])
|
||||
@@ -66,10 +66,8 @@ async def get_milestone_route(project_id: int, milestone_id: int):
|
||||
uid = get_current_user_id()
|
||||
milestone = await get_milestone(uid, milestone_id)
|
||||
if milestone is None or milestone.project_id != project_id:
|
||||
return jsonify({"error": "Milestone not found"}), 404
|
||||
entry = milestone.to_dict()
|
||||
entry.update(await get_milestone_progress(milestone.id))
|
||||
return jsonify(entry)
|
||||
return not_found("Milestone")
|
||||
return jsonify(await _milestone_dict(milestone))
|
||||
|
||||
|
||||
@milestones_bp.route("/<int:project_id>/milestones/<int:milestone_id>", methods=["PATCH"])
|
||||
@@ -78,16 +76,14 @@ async def update_milestone_route(project_id: int, milestone_id: int):
|
||||
uid = get_current_user_id()
|
||||
milestone = await get_milestone(uid, milestone_id)
|
||||
if milestone is None or milestone.project_id != project_id:
|
||||
return jsonify({"error": "Milestone not found"}), 404
|
||||
return not_found("Milestone")
|
||||
data = await request.get_json()
|
||||
allowed = {"title", "description", "status", "order_index"}
|
||||
fields = {k: v for k, v in data.items() if k in allowed}
|
||||
updated = await update_milestone(uid, milestone_id, **fields)
|
||||
if updated is None:
|
||||
return jsonify({"error": "Milestone not found"}), 404
|
||||
entry = updated.to_dict()
|
||||
entry.update(await get_milestone_progress(updated.id))
|
||||
return jsonify(entry)
|
||||
return not_found("Milestone")
|
||||
return jsonify(await _milestone_dict(updated))
|
||||
|
||||
|
||||
@milestones_bp.route("/<int:project_id>/milestones/<int:milestone_id>", methods=["DELETE"])
|
||||
@@ -96,10 +92,10 @@ async def delete_milestone_route(project_id: int, milestone_id: int):
|
||||
uid = get_current_user_id()
|
||||
milestone = await get_milestone(uid, milestone_id)
|
||||
if milestone is None or milestone.project_id != project_id:
|
||||
return jsonify({"error": "Milestone not found"}), 404
|
||||
return not_found("Milestone")
|
||||
deleted = await delete_milestone(uid, milestone_id)
|
||||
if not deleted:
|
||||
return jsonify({"error": "Milestone not found"}), 404
|
||||
return not_found("Milestone")
|
||||
return "", 204
|
||||
|
||||
|
||||
@@ -109,7 +105,7 @@ async def get_milestone_tasks_route(project_id: int, milestone_id: int):
|
||||
uid = get_current_user_id()
|
||||
milestone = await get_milestone(uid, milestone_id)
|
||||
if milestone is None or milestone.project_id != project_id:
|
||||
return jsonify({"error": "Milestone not found"}), 404
|
||||
return not_found("Milestone")
|
||||
status_filter = request.args.get("status")
|
||||
limit = min(request.args.get("limit", 100, type=int), 500)
|
||||
offset = request.args.get("offset", 0, type=int)
|
||||
|
||||
Reference in New Issue
Block a user