Compare commits

...

4 Commits

Author SHA1 Message Date
1f4ca52a10 add: markdown deletion 2025-06-27 12:06:28 +01:00
62c33c47c6 add: markdown deletion 2025-06-23 15:41:03 +01:00
692c0794c5 upgrade react-query to v5 2025-05-09 00:44:53 +01:00
848c4b8fd8 fix: fix endpoints 2025-05-08 12:25:47 +01:00
10 changed files with 109 additions and 61 deletions

View File

@@ -268,7 +268,8 @@ def delete_markdown(markdown_id):
"""
Delete a markdown document.
This endpoint deletes an existing markdown document.
This endpoint deletes an existing markdown document and cascades the deletion
to related settings to avoid foreign key conflicts.
It requires authentication with the 'admin' role.
Request:
@@ -280,18 +281,60 @@ def delete_markdown(markdown_id):
Response Codes:
- 200: Deleted successfully
- 404: Markdown not found
- 500: Server error during cascade deletion
"""
with get_db() as session:
markdown = session.get(Markdown, markdown_id)
if markdown is None:
logger.error(f"failed to delete markdown: {markdown_id}")
try:
markdown = session.get(Markdown, markdown_id)
if markdown is None:
logger.error(f"failed to delete markdown: {markdown_id}")
errno = RequestContext.get_error_id()
return jsonify({"error": f"file not found - {errno}"}), 404
md = markdown.to_dict()
if markdown.setting_id:
markdown_setting = session.query(MarkdownSetting).get(markdown.setting_id)
if markdown_setting:
template_setting_id = markdown_setting.template_setting_id
permission_setting_id = markdown_setting.permission_setting_id
markdown_setting.template_setting_id = None
markdown_setting.permission_setting_id = None
session.flush()
if template_setting_id:
from db.models.MarkdownTemplateSetting import MarkdownTemplateSetting
template_setting = session.query(MarkdownTemplateSetting).get(template_setting_id)
if template_setting:
session.delete(template_setting)
if permission_setting_id:
permission_setting = session.query(MarkdownPermissionSetting).get(permission_setting_id)
if permission_setting:
session.delete(permission_setting)
session.delete(markdown_setting)
# Send webhook event before committing the transaction
# This ensures webhook handlers can still access related data
markdown_deleted.send(None, payload=md)
session.delete(markdown)
session.commit()
logger.info(f"Successfully deleted markdown {markdown_id} with cascade deletion")
return jsonify(md), 200
except Exception as e:
import traceback
logger.error(f"Failed to delete markdown {markdown_id}: {e}")
logger.error(f"Exception type: {type(e).__name__}")
logger.error(f"Full traceback:\n{traceback.format_exc()}")
errno = RequestContext.get_error_id()
return jsonify({"error": f"file not found - {errno}"}), 404
md = markdown.to_dict()
session.delete(markdown)
session.commit()
markdown_deleted.send(None, payload=md)
return jsonify(md), 200
session.rollback()
return jsonify({"error": f"delete failed - {errno}"}), 500
@markdown_bp.route('/move_forward/<int:markdown_id>', methods=['PATCH'])

View File

@@ -1,13 +1,12 @@
from flask import jsonify, request
from flask import jsonify, request, Blueprint
import api
from api import limiter, etag_response, require_auth
from api.setting import setting_bp
from db import get_db
from db.models.MarkdownSetting import MarkdownSetting
@setting_bp.route('/markdown/<int:setting_id>', methods=['GET'])
markdown_setting_bp = Blueprint('markdown_setting', __name__, url_prefix='/api/setting/markdown')
@markdown_setting_bp.route('/<int:setting_id>', methods=['GET'])
@limiter.limit(api.get_rate_limit)
@etag_response
def get_markdown_path(setting_id):
@@ -33,7 +32,7 @@ def get_markdown_path(setting_id):
return jsonify(setting.to_dict()), 200
@setting_bp.route('/markdown/', methods=['POST'])
@markdown_setting_bp.route('/', methods=['POST'])
@require_auth(roles=['admin'])
def create_markdown_setting():
"""
@@ -68,7 +67,7 @@ def create_markdown_setting():
except Exception:
return jsonify({"error": "failed to create setting"}), 500
@setting_bp.route('/markdown/<int:setting_id>', methods=['PUT', 'PATCH'])
@markdown_setting_bp.route('/<int:setting_id>', methods=['PUT', 'PATCH'])
@require_auth(roles=['admin'])
def update_markdown_setting(setting_id):
"""
@@ -106,7 +105,7 @@ def update_markdown_setting(setting_id):
return jsonify({"error": "failed to update setting"}), 500
@setting_bp.route('/markdown/<int:setting_id>', methods=['DELETE'])
@markdown_setting_bp.route('/<int:setting_id>', methods=['DELETE'])
@require_auth(roles=['admin'])
def delete_markdown_setting(setting_id):
"""

View File

@@ -1,12 +1,11 @@
from flask import jsonify, request
from flask import jsonify, request, Blueprint
import api
from api import etag_response, limiter, require_auth
from api.setting import setting_bp
from db import get_db
from db.models.MarkdownPermissionSetting import MarkdownPermissionSetting
@setting_bp.route('/markdown/permission/<int:setting_id>/', methods=['GET'])
markdown_permission_setting_bp = Blueprint('markdown_permission_setting', __name__, url_prefix='/api/setting/markdown/permission')
@markdown_permission_setting_bp.route('/<int:setting_id>', methods=['GET'])
@etag_response
@limiter.limit(api.get_rate_limit)
def get_permission_setting(setting_id):
@@ -32,7 +31,7 @@ def get_permission_setting(setting_id):
return jsonify(setting.to_dict()), 200
@setting_bp.route('/markdown/permission/', methods=['POST'])
@markdown_permission_setting_bp.route('/', methods=['POST'])
@require_auth(roles=['admin'])
def create_permission_setting():
"""
@@ -59,7 +58,7 @@ def create_permission_setting():
return jsonify(new_setting.to_dict()), 201
@setting_bp.route('/markdown/permission/<int:setting_id>', methods=['PUT', 'PATCH'])
@markdown_permission_setting_bp.route('/<int:setting_id>', methods=['PUT', 'PATCH'])
@require_auth(roles=['admin'])
def update_permission_setting(setting_id):
"""
@@ -95,7 +94,7 @@ def update_permission_setting(setting_id):
session.commit()
return jsonify(setting.to_dict()), 200
@setting_bp.route('/markdown/permission/<int:setting_id>', methods=['DELETE'])
@markdown_permission_setting_bp.route('/<int:setting_id>', methods=['DELETE'])
@require_auth(roles=['admin'])
def delete_permission_setting(setting_id):
"""

View File

@@ -1,12 +1,12 @@
from flask import jsonify, request
from flask import jsonify, request, Blueprint
import api
from api import etag_response, limiter, require_auth
from api.setting import setting_bp
from db import get_db
from db.models.MarkdownTemplateSetting import MarkdownTemplateSetting
@setting_bp.route('/markdown/template/', methods=['GET'])
markdown_template_setting_bp = Blueprint('markdown_template_setting', __name__, url_prefix='/api/setting/markdown/template')
@markdown_template_setting_bp.route('/', methods=['GET'])
@etag_response
def list_template_settings():
"""
@@ -24,7 +24,7 @@ def list_template_settings():
settings = session.query(MarkdownTemplateSetting).all()
return jsonify([s.to_dict() for s in settings]), 200
@setting_bp.route('/markdown/template/<int:setting_id>/', methods=['GET'])
@markdown_template_setting_bp.route('/<int:setting_id>', methods=['GET'])
@etag_response
@limiter.limit(api.get_rate_limit)
def get_template_setting(setting_id):
@@ -50,7 +50,7 @@ def get_template_setting(setting_id):
return jsonify(setting.to_dict()), 200
@setting_bp.route('/markdown/template/', methods=['POST'])
@markdown_template_setting_bp.route('/', methods=['POST'])
@require_auth(roles=['admin'])
def create_template_setting():
"""
@@ -77,7 +77,7 @@ def create_template_setting():
return jsonify(new_setting.to_dict()), 201
@setting_bp.route('/markdown/template/<int:setting_id>', methods=['PUT', 'PATCH'])
@markdown_template_setting_bp.route('/<int:setting_id>', methods=['PUT', 'PATCH'])
@require_auth(roles=['admin'])
def update_template_setting(setting_id):
"""
@@ -113,7 +113,7 @@ def update_template_setting(setting_id):
session.commit()
return jsonify(setting.to_dict()), 200
@setting_bp.route('/markdown/template/<int:setting_id>', methods=['DELETE'])
@markdown_template_setting_bp.route('/<int:setting_id>', methods=['DELETE'])
@require_auth(roles=['admin'])
def delete_template_setting(setting_id):
"""

View File

@@ -1,15 +1,16 @@
from flask import jsonify, request
from flask import jsonify, request, Blueprint
import api
from api import limiter, require_auth, etag_response
from api.setting import setting_bp
from db import get_db
from db.models.PathSetting import PathSetting
import logging
logger = logging.getLogger(__name__)
@setting_bp.route('/path/<int:setting_id>', methods=['GET'])
path_setting_bp = Blueprint('path_setting', __name__, url_prefix='/api/setting/path')
@path_setting_bp.route('/<int:setting_id>', methods=['GET'])
@limiter.limit(api.get_rate_limit)
@etag_response
def get_path_setting(setting_id):
@@ -34,7 +35,7 @@ def get_path_setting(setting_id):
return jsonify({}), 204
return jsonify(setting.to_dict()), 200
@setting_bp.route('/path/', methods=['POST'])
@path_setting_bp.route('/', methods=['POST'])
@require_auth(roles=['admin'])
def create_path_setting():
"""
@@ -71,7 +72,7 @@ def create_path_setting():
return jsonify({"error": "failed to create setting"}), 500
@setting_bp.route('/path/<int:setting_id>', methods=['PUT', 'PATCH'])
@path_setting_bp.route('/<int:setting_id>', methods=['PUT', 'PATCH'])
@require_auth(roles=['admin'])
def update_path_setting(setting_id):
"""
@@ -108,7 +109,7 @@ def update_path_setting(setting_id):
except Exception:
return jsonify({"error": "failed to update path setting"}), 500
@setting_bp.route('/path/<int:setting_id>', methods=['DELETE'])
@path_setting_bp.route('/<int:setting_id>', methods=['DELETE'])
@require_auth(roles=['admin'])
def delete_path_setting(setting_id):
"""

View File

@@ -1,11 +1,10 @@
from flask import jsonify, request
from flask import jsonify, request, Blueprint
from api import require_auth
from api.setting import setting_bp
from db import get_db
from db.models.WebhookSetting import WebhookSetting
@setting_bp.route('/path/webhook/', methods=['GET'])
path_webhook_setting_bp = Blueprint('path_webhook_setting', __name__, url_prefix='/api/setting/path/webhook')
@path_webhook_setting_bp.route('/', methods=['GET'])
@require_auth(roles=['admin'])
def list_webhook_settings():
"""
@@ -25,7 +24,7 @@ def list_webhook_settings():
return jsonify([s.to_dict() for s in settings]), 200
@setting_bp.route('/path/webhook/<int:setting_id>', methods=['GET'])
@path_webhook_setting_bp.route('/<int:setting_id>', methods=['GET'])
@require_auth(roles=['admin'])
def webhook_setting(setting_id):
"""
@@ -51,7 +50,7 @@ def webhook_setting(setting_id):
return jsonify(setting.to_dict()), 200
@setting_bp.route('/path/webhook/', methods=['POST'])
@path_webhook_setting_bp.route('/', methods=['POST'])
@require_auth(roles=['admin'])
def create_webhook_setting():
"""
@@ -87,7 +86,7 @@ def create_webhook_setting():
return jsonify(setting.to_dict()), 201
@setting_bp.route('/path/webhook/<int:setting_id>', methods=['PUT', 'PATCH'])
@path_webhook_setting_bp.route('/<int:setting_id>', methods=['PUT', 'PATCH'])
@require_auth(roles=['admin'])
def update_webhook_setting(setting_id):
"""
@@ -131,7 +130,7 @@ def update_webhook_setting(setting_id):
return jsonify(setting.to_dict()), 200
@setting_bp.route('/path/webhook/<int:setting_id>', methods=['DELETE'])
@path_webhook_setting_bp.route('/<int:setting_id>', methods=['DELETE'])
@require_auth(roles=['admin'])
def delete_webhook_setting(setting_id):
"""

View File

@@ -1,10 +1,12 @@
from flask import jsonify, request
from flask import jsonify, request, Blueprint
from api import etag_response, require_auth
from api.template import template_bp
from db import get_db
from db.models.MarkdownTemplate import MarkdownTemplate
cached_templates = {}
markdown_template_bp = Blueprint('markdown_template', __name__, url_prefix='/api/template/markdown')
def inflate_template(template):
for parameter in template.get('parameters'):
if parameter.get('type', {}).get('base_type') == 'template':
@@ -19,7 +21,7 @@ def inflate_template(template):
return template
@template_bp.route('/markdown/<int:template_id>', methods=['GET'])
@markdown_template_bp.route('/<int:template_id>', methods=['GET'])
@etag_response
def get_markdown_template(template_id):
"""
@@ -42,10 +44,9 @@ def get_markdown_template(template_id):
template = session.query(MarkdownTemplate).get(template_id)
if template is None:
return jsonify({}), 204
print(inflate_template(template.to_dict()))
return jsonify(inflate_template(template.to_dict())), 200
@template_bp.route('/markdown/', methods=['GET'])
@markdown_template_bp.route('/', methods=['GET'])
@etag_response
def get_markdown_templates():
"""
@@ -62,11 +63,10 @@ def get_markdown_templates():
"""
with get_db() as session:
templates = session.query(MarkdownTemplate).all()
print(templates)
return jsonify([inflate_template(template.to_dict()) for template in templates]), 200
@template_bp.route('/markdown/', methods=['POST'])
@markdown_template_bp.route('/', methods=['POST'])
@require_auth(roles=['admin'])
def create_markdown_template():
"""
@@ -103,7 +103,7 @@ def create_markdown_template():
return jsonify({"error": "failed to create markdown template"}), 400
@template_bp.route('/markdown/<int:template_id>', methods=['PUT', 'PATCH'])
@markdown_template_bp.route('/<int:template_id>', methods=['PUT', 'PATCH'])
@require_auth(roles=['admin'])
def update_markdown_template(template_id):
"""
@@ -142,7 +142,7 @@ def update_markdown_template(template_id):
return jsonify(template.to_dict()), 200
@template_bp.route('/markdown/<int:template_id>', methods=['DELETE'])
@markdown_template_bp.route('/<int:template_id>', methods=['DELETE'])
@require_auth(roles=['admin'])
def delete_markdown_template(template_id):
"""

View File

@@ -1,12 +1,13 @@
from flask import jsonify, request
from flask.sansio.blueprints import Blueprint
from api import etag_response, require_auth
from api.template import template_bp
from db import get_db
from db.models.PathTemplate import PathTemplate
@template_bp.route('/path/<int:template_id>', methods=['GET'])
path_template_bp = Blueprint('path_template', __name__, url_prefix='/api/template/path')
@path_template_bp.route('/<int:template_id>', methods=['GET'])
@etag_response
def get_path_template(template_id):
"""
@@ -30,7 +31,7 @@ def get_path_template(template_id):
return jsonify({}), 204
return jsonify(template.to_dict()), 200
@template_bp.route('/path/', methods=['GET'])
@path_template_bp.route('/', methods=['GET'])
@etag_response
def get_path_templates():
"""
@@ -49,7 +50,7 @@ def get_path_templates():
return jsonify([template.to_dict() for template in templates]), 200
@template_bp.route('/path/', methods=['POST'])
@path_template_bp.route('/', methods=['POST'])
@require_auth(roles=['admin'])
def create_path_template():
"""
@@ -84,7 +85,7 @@ def create_path_template():
return jsonify({"error": "failed to create path template"}), 400
@template_bp.route('/path/<int:template_id>', methods=['PUT', 'PATCH'])
@path_template_bp.route('/<int:template_id>', methods=['PUT', 'PATCH'])
@require_auth(roles=['admin'])
def update_path_template(template_id):
"""
@@ -118,7 +119,7 @@ def update_path_template(template_id):
return jsonify(template.to_dict()), 200
@template_bp.route('/path/<int:template_id>', methods=['DELETE'])
@path_template_bp.route('/<int:template_id>', methods=['DELETE'])
@require_auth(roles=['admin'])
def delete_path_template(template_id):
"""

4
app.py
View File

@@ -35,7 +35,8 @@ CORS(app, resources={
r"https?://localhost:\d+",
r"https?://127\.0\.0\.1:\d+",
r"https?://localhost"
]
],
"supports_credentials": True
}
},
expose_headers=['Content-Disposition']
@@ -54,5 +55,6 @@ def log_request():
if __name__ == '__main__':
api.init_rate_limits(app)
print("env")
pprint(env_provider.summerize())
app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=True)

View File

@@ -59,6 +59,10 @@ class WebhookEventHandler(abc.ABC):
if webhook_setting is None and p["parent_id"] != 1:
return self.get_setting(session, p["parent_id"])
# Check if webhook_setting is still None (e.g., when parent_id == 1 or no parent found)
if webhook_setting is None:
return None
setting = webhook_setting.to_dict()
if not setting["enabled"] or setting["on_events"] & self.event_type == 0: