import json import logging import os import sqlite3 from contextlib import closing from datetime import datetime, timezone from fastapi import FastAPI, Request from fastapi.responses import JSONResponse, PlainTextResponse DB_PATH = os.getenv('WEBHOOK_DB_PATH', './servicem8_webhooks.db') APP_HOST = os.getenv('WEBHOOK_HOST', '0.0.0.0') APP_PORT = int(os.getenv('WEBHOOK_PORT', '8000')) logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') logger = logging.getLogger('servicem8-webhook-receiver') app = FastAPI(title='ServiceM8 Webhook Receiver', version='1.0.0') def get_conn(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn def init_db(): with closing(get_conn()) as conn: conn.execute( ''' CREATE TABLE IF NOT EXISTS webhook_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, received_at TEXT NOT NULL, client_host TEXT, method TEXT NOT NULL, path TEXT NOT NULL, headers_json TEXT, payload_json TEXT NOT NULL ) ''' ) conn.commit() @app.on_event('startup') async def startup_event(): init_db() logger.info('SQLite DB ready at %s', DB_PATH) @app.get('/health') async def health(): return {'ok': True} @app.post('/webhooks/servicem8-job-updated') async def servicem8_webhook(request: Request): try: payload = await request.json() except Exception: body = await request.body() payload = {'_raw_body': body.decode('utf-8', errors='replace')} headers = dict(request.headers) client_host = request.client.host if request.client else None received_at = datetime.now(timezone.utc).isoformat() with closing(get_conn()) as conn: conn.execute( ''' INSERT INTO webhook_events ( received_at, client_host, method, path, headers_json, payload_json ) VALUES (?, ?, ?, ?, ?, ?) ''', ( received_at, client_host, request.method, request.url.path, json.dumps(headers), json.dumps(payload), ), ) conn.commit() logger.info('Webhook received from %s and stored', client_host) return PlainTextResponse('OK', status_code=200) if __name__ == '__main__': import uvicorn uvicorn.run('servicem8_webhook_receiver:app', host=APP_HOST, port=APP_PORT, reload=False)