add: webhook
This commit is contained in:
@@ -41,20 +41,19 @@ def get_jwks():
|
||||
|
||||
def get_public_key_for_kid(kid):
|
||||
global _public_key_cache
|
||||
if kid in _public_key_cache:
|
||||
return _public_key_cache[kid]
|
||||
jwks = get_jwks()
|
||||
res = []
|
||||
for key_data in jwks["keys"]:
|
||||
if key_data["kid"] == kid and key_data["use"] == "sig" and key_data["alg"] == "RS256" and key_data["kty"] == "RSA":
|
||||
x5c = key_data["x5c"][0]
|
||||
pem_public_key = x5c_to_public_key(x5c)
|
||||
_public_key_cache[kid] = pem_public_key
|
||||
res.append(pem_public_key)
|
||||
if len(res) > 0:
|
||||
print(len(res))
|
||||
return res[0]
|
||||
|
||||
with _lock:
|
||||
if kid in _public_key_cache:
|
||||
return _public_key_cache[kid]
|
||||
jwks = get_jwks()
|
||||
res = []
|
||||
for key_data in jwks["keys"]:
|
||||
if key_data["kid"] == kid and key_data["use"] == "sig" and key_data["alg"] == "RS256" and key_data["kty"] == "RSA":
|
||||
x5c = key_data["x5c"][0]
|
||||
pem_public_key = x5c_to_public_key(x5c)
|
||||
_public_key_cache[kid] = pem_public_key
|
||||
res.append(pem_public_key)
|
||||
if len(res) > 0:
|
||||
return res[0]
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -41,3 +41,4 @@ def create_log():
|
||||
extra = data.get('extra', None)
|
||||
log_entry = Log(level=level, message=message, application=application, extra=extra)
|
||||
insert_log(log_entry)
|
||||
return jsonify({"message": "log created"}), 201
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from sqlalchemy import or_
|
||||
import api
|
||||
from api import limiter
|
||||
from api import require_auth, etag_response
|
||||
from contexts.RequestContext import RequestContext
|
||||
from db import get_db
|
||||
from db.models.Markdown import Markdown
|
||||
from events import markdown_created, markdown_updated, markdown_deleted
|
||||
import api
|
||||
import logging
|
||||
from api import limiter
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
markdown_bp = Blueprint('markdown', __name__, url_prefix='/api/markdown')
|
||||
@@ -79,6 +80,7 @@ def create_markdown():
|
||||
return jsonify({"error": "duplicate shortcut"}), 400
|
||||
session.add(new_markdown)
|
||||
session.commit()
|
||||
markdown_created.send(None, new_markdown.to_dict())
|
||||
return jsonify(new_markdown.to_dict()), 201
|
||||
except Exception as e:
|
||||
logger.error(f"failed to create markdown: {e}")
|
||||
@@ -118,6 +120,7 @@ def update_markdown(markdown_id):
|
||||
if 'shortcut' in data:
|
||||
markdown.shortcut = data.get('shortcut')
|
||||
session.commit()
|
||||
markdown_updated.send(None, markdown.to_dict())
|
||||
return jsonify(markdown.to_dict()), 200
|
||||
|
||||
@markdown_bp.route('/<int:markdown_id>', methods=['DELETE'])
|
||||
@@ -130,8 +133,10 @@ def delete_markdown(markdown_id):
|
||||
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()
|
||||
session.delete(markdown)
|
||||
session.commit()
|
||||
markdown_deleted.send(None, md)
|
||||
return jsonify({"message": "deleted"}), 200
|
||||
|
||||
|
||||
@@ -150,6 +155,7 @@ def move_forward(markdown_id):
|
||||
previous_markdown = siblings[current_index - 1]
|
||||
markdown.order, previous_markdown.order = previous_markdown.order, markdown.order
|
||||
session.commit()
|
||||
markdown_updated.send(None, markdown.to_dict())
|
||||
return jsonify(markdown.to_dict()), 200
|
||||
|
||||
|
||||
@@ -167,8 +173,9 @@ def move_backward(markdown_id):
|
||||
return jsonify({"error": "already at the last position"}), 400
|
||||
|
||||
next_markdown = siblings[current_index + 1]
|
||||
markdown.order, next_markdown.order = next_markdown.order, next_markdown.order
|
||||
markdown.order, next_markdown.order = next_markdown.order, markdown.order
|
||||
session.commit()
|
||||
markdown_updated.send(None, markdown.to_dict())
|
||||
return jsonify(markdown.to_dict()), 200
|
||||
|
||||
@markdown_bp.route('/search/<string:keyword>', methods=['GET'])
|
||||
|
||||
10
api/path.py
10
api/path.py
@@ -7,6 +7,9 @@ from db.models.Markdown import Markdown
|
||||
from db.models.Path import Path
|
||||
from api import limiter
|
||||
import logging
|
||||
|
||||
from events import path_created, path_updated, path_deleted
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
path_bp = Blueprint('path', __name__, url_prefix='/api/path')
|
||||
@@ -52,6 +55,7 @@ def create_path():
|
||||
new_path = Path(name=data['name'], parent_id=data['parent_id'])
|
||||
session.add(new_path)
|
||||
session.commit()
|
||||
path_created.send(None, new_path.to_dict())
|
||||
return jsonify(new_path.to_dict()), 201
|
||||
|
||||
@path_bp.route('/<int:path_id>', methods=['PUT'])
|
||||
@@ -70,6 +74,7 @@ def update_path(path_id):
|
||||
path.name = data['name']
|
||||
path.parent_id = data['parent_id']
|
||||
session.commit()
|
||||
path_updated.send(None, path.to_dict())
|
||||
return jsonify(path.to_dict()), 200
|
||||
|
||||
@path_bp.route('/<int:path_id>', methods=['PATCH'])
|
||||
@@ -91,6 +96,7 @@ def patch_path(path_id):
|
||||
path.name = updated_name
|
||||
path.parent_id = updated_parent_id
|
||||
session.commit()
|
||||
path_updated.send(None, path.to_dict())
|
||||
return jsonify(path.to_dict()), 200
|
||||
|
||||
|
||||
@@ -106,8 +112,10 @@ def delete_path(path_id):
|
||||
return jsonify({"error": "can not delete non empty path"}), 409
|
||||
if session.query(Markdown).filter_by(path_id=path_id).first():
|
||||
return jsonify({"error": "can not delete non empty path"}), 409
|
||||
pth = path.to_dict()
|
||||
session.delete(path)
|
||||
session.commit()
|
||||
path_deleted.send(None, pth)
|
||||
return jsonify({"message": "path deleted"}), 200
|
||||
|
||||
|
||||
@@ -126,6 +134,7 @@ def move_forward(path_id):
|
||||
previous_path = siblings[current_index - 1]
|
||||
path.order, previous_path.order = previous_path.order, path.order
|
||||
session.commit()
|
||||
path_updated.send(None, path.to_dict())
|
||||
return jsonify(path.to_dict()), 200
|
||||
|
||||
|
||||
@@ -145,5 +154,6 @@ def move_backward(path_id):
|
||||
next_path = siblings[current_index + 1]
|
||||
path.order, next_path.order = next_path.order, path.order
|
||||
session.commit()
|
||||
path_updated.send(None, path.to_dict())
|
||||
return jsonify(path.to_dict()), 200
|
||||
|
||||
|
||||
152
api/webhook.py
Normal file
152
api/webhook.py
Normal file
@@ -0,0 +1,152 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from api import require_auth, limiter
|
||||
from db import get_db
|
||||
from db.models.Webhook import Webhook
|
||||
from db.models.WebhookSetting import WebhookSetting
|
||||
|
||||
webhook_bp = Blueprint('webhook', __name__, url_prefix='/api/webhook')
|
||||
|
||||
|
||||
@webhook_bp.route('/', methods=['GET'])
|
||||
@require_auth(roles=['admin'])
|
||||
def list_webhooks():
|
||||
with get_db() as session:
|
||||
hooks = session.query(Webhook).all()
|
||||
return jsonify([h.to_dict() for h in hooks]), 200
|
||||
|
||||
|
||||
@webhook_bp.route('/', methods=['POST'])
|
||||
@require_auth(['admin'])
|
||||
def create_webhook():
|
||||
data = request.json
|
||||
hook_url = data.get('hook_url')
|
||||
if not hook_url:
|
||||
return jsonify({'error': 'hook_url required'}), 400
|
||||
|
||||
with get_db() as session:
|
||||
existing = session.query(Webhook).filter_by(hook_url=hook_url).first()
|
||||
if existing:
|
||||
return jsonify({'error': 'Webhook URL already exists'}), 409
|
||||
webhook = Webhook(hook_url=hook_url)
|
||||
session.add(webhook)
|
||||
session.commit()
|
||||
return jsonify(webhook.to_dict()), 201
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@webhook_bp.route('/<int:webhook_id>', methods=['PUT', 'PATCH'])
|
||||
@require_auth(roles=['admin'])
|
||||
def update_webhook(webhook_id):
|
||||
data = request.json
|
||||
if 'hook_url' not in data:
|
||||
return jsonify({'error': 'hook_url is required'}), 400
|
||||
|
||||
with get_db() as session:
|
||||
webhook = session.query(Webhook).get(webhook_id)
|
||||
if not webhook:
|
||||
return jsonify({'error': 'Webhook not found'}), 404
|
||||
|
||||
existing = session.query(Webhook).filter_by(hook_url=data['hook_url']).filter(Webhook.id != webhook_id).first()
|
||||
if existing:
|
||||
return jsonify({'error': 'Webhook URL already exists'}), 409
|
||||
|
||||
webhook.hook_url = data['hook_url']
|
||||
session.commit()
|
||||
return jsonify(webhook.to_dict()), 200
|
||||
|
||||
|
||||
@webhook_bp.route('/<int:webhook_id>', methods=['DELETE'])
|
||||
@require_auth(roles=['admin'])
|
||||
def delete_webhook(webhook_id):
|
||||
with get_db() as session:
|
||||
webhook = session.query(Webhook).get(webhook_id)
|
||||
if not webhook:
|
||||
return jsonify({'error': 'Webhook not found'}), 404
|
||||
|
||||
session.delete(webhook)
|
||||
session.commit()
|
||||
return jsonify({'message': 'Webhook deleted'}), 200
|
||||
|
||||
|
||||
|
||||
@webhook_bp.route('/setting/', methods=['GET'])
|
||||
@require_auth(roles=['admin'])
|
||||
def list_webhook_settings():
|
||||
with get_db() as session:
|
||||
settings = session.query(WebhookSetting).all()
|
||||
return jsonify([s.to_dict() for s in settings]), 200
|
||||
|
||||
@webhook_bp.route('/setting/<int:setting_id>', methods=['GET'])
|
||||
@require_auth(roles=['admin'])
|
||||
def webhook_setting(setting_id):
|
||||
with get_db() as session:
|
||||
setting = session.query(WebhookSetting).filter(WebhookSetting.id == setting_id).first()
|
||||
if not setting:
|
||||
return jsonify({'info': 'Webhook setting not found'}), 204
|
||||
return jsonify(setting.to_dict()), 200
|
||||
|
||||
|
||||
@webhook_bp.route('/setting/path/<int:path_id>', methods=['GET'])
|
||||
@require_auth(roles=['admin'])
|
||||
def webhook_setting_by_path(path_id):
|
||||
with get_db() as session:
|
||||
setting = session.query(WebhookSetting).filter(WebhookSetting.path_id == path_id).first()
|
||||
if not setting:
|
||||
return jsonify({'info': 'Webhook setting not found'}), 204
|
||||
return jsonify(setting.to_dict()), 200
|
||||
|
||||
@webhook_bp.route('/setting/', methods=['POST'])
|
||||
@require_auth(roles=['admin'])
|
||||
def create_webhook_setting():
|
||||
data = request.json
|
||||
print(data)
|
||||
required = ['path_id', 'webhook_id']
|
||||
for key in required:
|
||||
if key not in data:
|
||||
return jsonify({'error': 'Required field missing'}), 400
|
||||
with get_db() as session:
|
||||
setting = WebhookSetting(
|
||||
path_id=data.get('path_id'),
|
||||
webhook_id=data.get('webhook_id'),
|
||||
recursive=data.get('recursive', False),
|
||||
additional_header=data.get('additional_header', ''),
|
||||
enabled=data.get('enabled', True),
|
||||
on_events=data.get('on_events', 1),
|
||||
)
|
||||
session.add(setting)
|
||||
session.commit()
|
||||
return jsonify(setting.to_dict()), 201
|
||||
@webhook_bp.route('/setting/<int:setting_id>', methods=['PUT', 'PATCH'])
|
||||
@require_auth(roles=['admin'])
|
||||
def update_webhook_setting(setting_id):
|
||||
data = request.json
|
||||
with get_db() as session:
|
||||
setting = session.query(WebhookSetting).get(setting_id)
|
||||
if not setting:
|
||||
return jsonify({'error': 'Webhook setting not found'}), 404
|
||||
|
||||
if 'recursive' in data:
|
||||
setting.recursive = data['recursive']
|
||||
if 'additional_header' in data:
|
||||
setting.additional_header = data['additional_header']
|
||||
if 'enabled' in data:
|
||||
setting.enabled = data['enabled']
|
||||
if 'on_events' in data:
|
||||
setting.on_events = data['on_events']
|
||||
|
||||
session.commit()
|
||||
return jsonify(setting.to_dict()), 200
|
||||
|
||||
@webhook_bp.route('/setting/<int:setting_id>', methods=['DELETE'])
|
||||
@require_auth(roles=['admin'])
|
||||
def delete_webhook_setting(setting_id):
|
||||
with get_db() as session:
|
||||
setting = session.query(WebhookSetting).get(setting_id)
|
||||
if not setting:
|
||||
return jsonify({'error': 'Webhook setting not found'}), 404
|
||||
|
||||
session.delete(setting)
|
||||
session.commit()
|
||||
return jsonify({'message': 'Webhook setting deleted'}), 200
|
||||
@@ -1,5 +1,3 @@
|
||||
import os
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from sqlalchemy.schema import CreateTable
|
||||
|
||||
@@ -21,15 +19,6 @@ def get_db():
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# def dump_db():
|
||||
# try:
|
||||
# os.environ['MYSQL_PWD'] = DB_PASSWORD
|
||||
# dump_cmd = f"mysqldump --no-tablespaces -h {DB_HOST} -P {DB_PORT} -u {DB_USER} {DB_NAME} > /app/dump/db_dump.sql"
|
||||
# subprocess.run(dump_cmd, shell=True, check=True)
|
||||
# except subprocess.CalledProcessError as e:
|
||||
# print(f"Failed to dump database: {e}")
|
||||
# raise e
|
||||
|
||||
def clear_db():
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("SET FOREIGN_KEY_CHECKS = 0;"))
|
||||
@@ -60,9 +49,9 @@ def init_payload():
|
||||
for model in table_models:
|
||||
print(str(CreateTable(model.__table__)))
|
||||
|
||||
print(f"MODEL -- {model}, {hasattr(model, '__pay_load__')}")
|
||||
if hasattr(model, "__pay_load__"):
|
||||
payload = model.__pay_load__[ENVIRONMENT]
|
||||
print(f"MODEL -- {model}, {hasattr(model, '__payload__')}")
|
||||
if hasattr(model, "__payload__"):
|
||||
payload = model.__payload__[ENVIRONMENT]
|
||||
print(f"- - [ - ] hasattr, {ENVIRONMENT} - {payload}")
|
||||
stmt = insert(model.__table__).values(payload).prefix_with("IGNORE")
|
||||
print(f"- - [ - ] {stmt}\n")
|
||||
@@ -73,11 +62,6 @@ def init_payload():
|
||||
|
||||
def setup_db():
|
||||
if DB_SCHEMA_UPDATED:
|
||||
# try:
|
||||
# dump_db()
|
||||
# print("[ x ] db dumped")
|
||||
# except Exception as e:
|
||||
# print(f"[ x ] Failed to dump database: {e}")
|
||||
clear_db()
|
||||
print("[ x ] db cleared")
|
||||
create_all()
|
||||
|
||||
@@ -9,7 +9,7 @@ class Markdown(Base):
|
||||
id = Column(Integer, primary_key=True)
|
||||
title = Column(String(255), nullable=False)
|
||||
content = Column(Text, nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
created_at = Column(DateTime, default=datetime.datetime.now(datetime.UTC))
|
||||
path_id = Column(Integer, ForeignKey('path.id'), nullable=False)
|
||||
order = Column(String(36), default=lambda: str(uuid.uuid4()))
|
||||
shortcut = Column(String(36), default="")
|
||||
@@ -23,7 +23,7 @@ class Markdown(Base):
|
||||
'order': self.order,
|
||||
'shortcut': self.shortcut,
|
||||
}
|
||||
__pay_load__ = {
|
||||
__payload__ = {
|
||||
'dev': [
|
||||
{'id': 1, 'title': 'index', 'content': ' ', 'created_at': datetime.datetime.utcnow, 'path_id': 1 },
|
||||
],
|
||||
|
||||
@@ -19,7 +19,7 @@ class Path(Base):
|
||||
"order": self.order,
|
||||
}
|
||||
|
||||
__pay_load__ = {
|
||||
__payload__ = {
|
||||
'dev': [
|
||||
{'id': 1, 'name': '', 'parent_id': None},
|
||||
{'id': 2, 'name': 'test', 'parent_id': 1},
|
||||
|
||||
16
db/models/Webhook.py
Normal file
16
db/models/Webhook.py
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
from sqlalchemy import Column, String, Integer, ForeignKey, UniqueConstraint, text, Boolean
|
||||
from db.models import Base
|
||||
|
||||
|
||||
class Webhook(Base):
|
||||
__tablename__ = 'webhook'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
hook_url = Column(String(100), nullable=False)
|
||||
__table_args__ = (UniqueConstraint('hook_url'),)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"hook_url": self.hook_url
|
||||
}
|
||||
26
db/models/WebhookSetting.py
Normal file
26
db/models/WebhookSetting.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from sqlalchemy import Column, String, Integer, ForeignKey, UniqueConstraint, Boolean
|
||||
from db.models import Base
|
||||
|
||||
|
||||
class WebhookSetting(Base):
|
||||
__tablename__ = 'webhook_setting'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
path_id = Column(Integer, ForeignKey('path.id'), nullable=False)
|
||||
webhook_id = Column(Integer, ForeignKey('webhook.id'), nullable=False)
|
||||
recursive = Column(Boolean, default=False)
|
||||
additional_header = Column(String(500), nullable=True)
|
||||
enabled = Column(Boolean, default=True)
|
||||
on_events = Column(Integer, default=0)
|
||||
|
||||
__table_args__ = (UniqueConstraint('webhook_id', 'path_id', name='_webhook_path_uc'),)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"path_id": self.path_id,
|
||||
"webhook_id": self.webhook_id,
|
||||
"recursive": self.recursive,
|
||||
"additional_header": self.additional_header,
|
||||
"enabled": self.enabled,
|
||||
"on_events": self.on_events,
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
#db/models/__init__.py
|
||||
|
||||
import pkgutil
|
||||
import importlib
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import declarative_base
|
||||
Base = declarative_base()
|
||||
package_name = "db.models"
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#db/utils.py
|
||||
from db import get_db
|
||||
|
||||
def insert_log(log_entry):
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from events import MARKDOWN_CREATED_EVENT, markdown_created
|
||||
from events.WebhookEventHandlers import auto_instantiate
|
||||
from events.WebhookEventHandlers.MarkdownWebhookEventHandlers import MarkdownWebhookEventHandler
|
||||
from misc import Singleton
|
||||
|
||||
|
||||
@auto_instantiate
|
||||
class MarkdownCreatedWebhookEventHandler(MarkdownWebhookEventHandler, Singleton):
|
||||
def __init__(self):
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
super().__init__(MARKDOWN_CREATED_EVENT)
|
||||
markdown_created.connect(self)
|
||||
self._initialized = True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from events import MARKDOWN_DELETED_EVENT, markdown_deleted
|
||||
from events.WebhookEventHandlers import auto_instantiate
|
||||
from events.WebhookEventHandlers.MarkdownWebhookEventHandlers import MarkdownWebhookEventHandler
|
||||
from misc import Singleton
|
||||
|
||||
|
||||
@auto_instantiate
|
||||
class MarkdownDeletedWebhookEventHandler(MarkdownWebhookEventHandler, Singleton):
|
||||
def __init__(self):
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
super().__init__(MARKDOWN_DELETED_EVENT)
|
||||
markdown_deleted.connect(self)
|
||||
self._initialized = True
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from events import MARKDOWN_UPDATED_EVENT, markdown_updated
|
||||
from events.WebhookEventHandlers import auto_instantiate
|
||||
from events.WebhookEventHandlers.MarkdownWebhookEventHandlers import MarkdownWebhookEventHandler
|
||||
from misc import Singleton
|
||||
|
||||
|
||||
@auto_instantiate
|
||||
class MarkdownUpdatedWebhookEventHandler(MarkdownWebhookEventHandler, Singleton):
|
||||
def __init__(self):
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
super().__init__(MARKDOWN_UPDATED_EVENT)
|
||||
markdown_updated.connect(self)
|
||||
self._initialized = True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from events.WebhookEventHandlers import WebhookEventHandler
|
||||
|
||||
|
||||
class MarkdownWebhookEventHandler(WebhookEventHandler):
|
||||
def __init__(self, event_type):
|
||||
super().__init__(event_type)
|
||||
|
||||
def get_path_id(self, payload):
|
||||
return payload["path_id"]
|
||||
@@ -0,0 +1,26 @@
|
||||
from events import PATH_CREATED_EVENT, path_created
|
||||
from events.WebhookEventHandlers import auto_instantiate
|
||||
from events.WebhookEventHandlers.PathWebhookEventHandlers import PathWebhookEventHandler
|
||||
from misc import Singleton
|
||||
|
||||
|
||||
@auto_instantiate
|
||||
class PathCreatedWebhookEventHandler(PathWebhookEventHandler,Singleton):
|
||||
|
||||
_instance = None
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(PathCreatedWebhookEventHandler, cls).__new__(cls)
|
||||
return cls._instance
|
||||
def __init__(self):
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
super().__init__(PATH_CREATED_EVENT)
|
||||
path_created.connect(self)
|
||||
self._initialized = True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from events import PATH_DELETED_EVENT, path_deleted
|
||||
from events.WebhookEventHandlers import auto_instantiate
|
||||
from events.WebhookEventHandlers.PathWebhookEventHandlers import PathWebhookEventHandler
|
||||
from misc import Singleton
|
||||
|
||||
|
||||
@auto_instantiate
|
||||
class PathDeletedWebhookEventHandler(PathWebhookEventHandler, Singleton):
|
||||
_instance = None
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(PathDeletedWebhookEventHandler, cls).__new__(cls)
|
||||
return cls._instance
|
||||
def __init__(self):
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
super().__init__(PATH_DELETED_EVENT)
|
||||
path_deleted.connect(self)
|
||||
self._initialized = True
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from events import PATH_UPDATED_EVENT, path_updated
|
||||
from events.WebhookEventHandlers import auto_instantiate
|
||||
from events.WebhookEventHandlers.PathWebhookEventHandlers import PathWebhookEventHandler
|
||||
from misc import Singleton
|
||||
|
||||
|
||||
@auto_instantiate
|
||||
class PathUpdatedWebhookEventHandler(PathWebhookEventHandler, Singleton):
|
||||
|
||||
_instance = None
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(PathUpdatedWebhookEventHandler, cls).__new__(cls)
|
||||
return cls._instance
|
||||
def __init__(self):
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
super().__init__(PATH_UPDATED_EVENT)
|
||||
path_updated.connect(self)
|
||||
self._initialized = True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from events.WebhookEventHandlers import WebhookEventHandler
|
||||
|
||||
|
||||
class PathWebhookEventHandler(WebhookEventHandler):
|
||||
def __init__(self, event_type):
|
||||
super().__init__(event_type)
|
||||
|
||||
def get_path_id(self, payload):
|
||||
return payload["id"]
|
||||
69
events/WebhookEventHandlers/__init__.py
Normal file
69
events/WebhookEventHandlers/__init__.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from db.models.Path import Path
|
||||
from db.models.Webhook import Webhook
|
||||
from db.models.WebhookSetting import WebhookSetting
|
||||
import abc
|
||||
import importlib
|
||||
import json
|
||||
import pkgutil
|
||||
import requests
|
||||
import db
|
||||
|
||||
class WebhookEventHandler(abc.ABC):
|
||||
def __init__(self, event_type=0):
|
||||
self.event_type = event_type
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_path_id(self, payload):
|
||||
pass
|
||||
|
||||
def __call__(self, payload):
|
||||
path_id = self.get_path_id(payload)
|
||||
with db.get_db() as session:
|
||||
setting = self.get_setting(session, path_id)
|
||||
if setting is None:
|
||||
return
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
if setting["additional_headers"] is not None:
|
||||
headers.update(json.loads(setting["additional_headers"]))
|
||||
try:
|
||||
response = requests.post(setting["webhook_url"], json=payload, headers=headers, timeout=5)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
print(e)
|
||||
|
||||
def get_setting(self, session: Session, path_id):
|
||||
|
||||
path = session.query(Path).filter(Path.id == path_id).first()
|
||||
if path is None:
|
||||
return None
|
||||
p = path.to_dict()
|
||||
webhook_setting = session.query(WebhookSetting).filter(WebhookSetting.path_id == path_id).first()
|
||||
|
||||
if webhook_setting is None and p["parent_id"] != 1:
|
||||
return self.get_setting(session, p["parent_id"])
|
||||
|
||||
setting = webhook_setting.to_dict()
|
||||
if not setting["enabled"] or setting["on_events"] & self.event_type == 0:
|
||||
return None
|
||||
webhook = session.query(Webhook).filter(Webhook.id == webhook_setting.webhook_id).first()
|
||||
if webhook is None:
|
||||
return None
|
||||
setting["webhook_url"] = webhook.to_dict()["hook_url"]
|
||||
return setting
|
||||
|
||||
_auto_instantiate_classes = set()
|
||||
|
||||
def auto_instantiate(cls):
|
||||
_auto_instantiate_classes.add(cls)
|
||||
return cls
|
||||
|
||||
def register_all_webhook_event_handlers():
|
||||
package = __name__
|
||||
package_path = __path__
|
||||
|
||||
for finder, name, ispkg in pkgutil.walk_packages(package_path, package+"."):
|
||||
importlib.import_module(name)
|
||||
|
||||
for cls in _auto_instantiate_classes:
|
||||
cls()
|
||||
17
events/__init__.py
Normal file
17
events/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from blinker import Namespace
|
||||
|
||||
signals = Namespace()
|
||||
|
||||
MARKDOWN_CREATED_EVENT = 1
|
||||
MARKDOWN_UPDATED_EVENT = 2
|
||||
MARKDOWN_DELETED_EVENT = 4
|
||||
PATH_CREATED_EVENT = 8
|
||||
PATH_UPDATED_EVENT = 16
|
||||
PATH_DELETED_EVENT = 32
|
||||
|
||||
markdown_created = signals.signal('markdown_created')
|
||||
markdown_updated = signals.signal('markdown_updated')
|
||||
markdown_deleted = signals.signal('markdown_deleted')
|
||||
path_created = signals.signal('path_created')
|
||||
path_updated = signals.signal('path_updated')
|
||||
path_deleted = signals.signal('path_deleted')
|
||||
7
misc/__init__.py
Normal file
7
misc/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
class Singleton:
|
||||
_instances = {}
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__new__(cls)
|
||||
return cls._instances[cls]
|
||||
Reference in New Issue
Block a user