From 4b9c1ba727e33bd74cb19e1f1ee74913c114bd28 Mon Sep 17 00:00:00 2001 From: hzhang Date: Thu, 5 Dec 2024 18:28:16 +0000 Subject: [PATCH] manage markdowns by path --- api/markdown.py | 13 +++++-- api/path.py | 86 +++++++++++++++++++++++++++++++++++++++++++ db/models/Markdown.py | 6 +-- db/models/Path.py | 17 +++++++++ 4 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 api/path.py create mode 100644 db/models/Path.py diff --git a/api/markdown.py b/api/markdown.py index 4e61c2f..0312b5d 100644 --- a/api/markdown.py +++ b/api/markdown.py @@ -18,6 +18,13 @@ def get_markdowns(): mds = db.query(Markdown).all() return jsonify([md.to_dict() for md in mds]), 200 +@markdown_bp.route('/by_path/', methods=['GET']) +@limiter.limit('5 per minute') +def get_markdowns_by_path(path_id): + with get_db() as db: + markdowns = db.query(Markdown).filter(Markdown.path_id == path_id).all() + return jsonify([md.to_dict() for md in markdowns]), 200 + @markdown_bp.route('/', methods=['GET']) @@ -36,10 +43,10 @@ def create_markdown(): data = request.json title = data.get('title') content = data.get('content') - path = data.get('path') + path_id = data.get('path_id') if not title or not content: return jsonify({"error": "missing required fields"}), 400 - new_markdown = Markdown(title=title, content=content, path=path) + new_markdown = Markdown(title=title, content=content, path_id=path_id) with get_db() as db: try: db.add(new_markdown) @@ -62,7 +69,7 @@ def update_markdown(markdown_id): data = request.json markdown.title = data.get('title') markdown.content = data.get('content') - markdown.path = data.get('path') + markdown.path_id = data.get('path_id') db.commit() return jsonify(markdown.to_dict()), 200 diff --git a/api/path.py b/api/path.py new file mode 100644 index 0000000..67a5f1b --- /dev/null +++ b/api/path.py @@ -0,0 +1,86 @@ +from flask import Blueprint, request, jsonify +from api import require_auth +from db import get_db +from db.models.Markdown import Markdown +from db.models.Path import Path +from api import limiter +import logging +logger = logging.getLogger(__name__) + +path_bp = Blueprint('path', __name__, url_prefix='/api/path') + +@path_bp.route('/', methods=['GET']) +@limiter.limit('5 per minute') +def get_root_paths(): + with get_db() as db: + paths = db.query(Path).filter(Path.parent_id == 0) + return jsonify([pth.to_dict() for pth in paths]), 200 + +@path_bp.route('/', methods=['GET']) +@limiter.limit('5 per minute') +def get_path(path_id): + with get_db() as db: + path = db.query(Path).get(path_id) + if path is None: + return jsonify({"error": "file not found"}), 404 + return jsonify(path.to_dict()), 200 + +@path_bp.route('/parent/', methods=['GET']) +@limiter.limit('5 per minute') +def get_path_by_parent(parent_id): + with get_db() as db: + paths = db.query(Path).filter(Path.parent_id == parent_id).all() + return jsonify([pth.to_dict() for pth in paths]), 200 + +@path_bp.route('/', methods=['POST']) +@limiter.limit('60 per minute') +@require_auth(roles=['admin', 'creator']) +def create_path(): + data = request.json + if not data or 'name' not in data or 'parent_id' not in data: + return jsonify({"error": "bad request"}), 400 + with get_db() as db: + if data['parent_id'] != 0 and not db.query(Path).get(data['parent_id']): + return jsonify({"error": "path not found"}), 404 + if db.query(Path).filter_by(name=data['name'], parent_id=data['parent_id']).first(): + return jsonify({"error": "Path already exists under the parent"}), 409 + + new_path = Path(name=data['name'], parent_id=data['parent_id']) + db.add(new_path) + db.commit() + return jsonify(new_path.to_dict()), 201 + +@path_bp.route('/', methods=['PUT']) +@limiter.limit('30 per minute') +@require_auth(roles=['admin']) +def update_path(path_id): + data = request.json + if not data or 'name' not in data or 'parent_id' not in data: + return jsonify({"error": "bad request"}), 400 + with get_db() as db: + path = db.query(Path).get(path_id) + if path is None: + return jsonify({"error": "path not found"}), 404 + if db.query(Path).filter_by(name=data['name'], parent_id=data['parent_id']).first(): + return jsonify({"error": "Path already exists under the parent"}), 409 + path.name = data['name'] + path.parent_id = data['parent_id'] + db.commit() + return jsonify(path.to_dict()), 200 + +@path_bp.route('/', methods=['DELETE']) +@limiter.limit('60 per minute') +@require_auth(roles=['admin']) +def delete_path(path_id): + with get_db() as db: + path = db.query(Path).get(path_id) + if not path: + return jsonify({"error": "path not found"}), 404 + if db.query(Path).filter_by(parent_id=path_id).first(): + return jsonify({"error": "can not delete non empty path"}), 409 + if db.query(Markdown).filter_by(path_id=path_id).first(): + return jsonify({"error": "can not delete non empty path"}), 409 + db.delete(path) + db.commit() + return jsonify({"message": "path deleted"}), 200 + diff --git a/db/models/Markdown.py b/db/models/Markdown.py index 002a8d3..f6f4cb5 100644 --- a/db/models/Markdown.py +++ b/db/models/Markdown.py @@ -1,5 +1,5 @@ #db/models/Markdown.py -from sqlalchemy import Column, Text, Integer, String, DateTime +from sqlalchemy import Column, Text, Integer, String, DateTime, ForeignKey from db.models import Base import datetime @@ -9,7 +9,7 @@ class Markdown(Base): title = Column(String(255), nullable=False) content = Column(Text, nullable=False) created_at = Column(DateTime, default=datetime.datetime.utcnow) - path = Column(String(255), nullable=False) + path_id = Column(Integer, ForeignKey('path.id'), nullable=False) def to_dict(self): return { @@ -17,5 +17,5 @@ class Markdown(Base): 'title': self.title, 'content': self.content, 'created_at': self.created_at, - 'path': self.path, + 'path_id': self.path_id, } diff --git a/db/models/Path.py b/db/models/Path.py new file mode 100644 index 0000000..3c336d1 --- /dev/null +++ b/db/models/Path.py @@ -0,0 +1,17 @@ +#db/models/Path.py +from sqlalchemy import Column, Text, LargeBinary, String, Integer, ForeignKey, UniqueConstraint +from db.models import Base + + +class Path(Base): + __tablename__ = "path" + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(50), nullable=False) + parent_id = Column(Integer, ForeignKey("path.id")) + __table_args__ = (UniqueConstraint("parent_id", "name", name="unique_parent_id_name"), ) + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "parent_id": self.parent_id, + } \ No newline at end of file