Add an endpoint for posting to Grafana
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, Response
|
||||
from fastapi import APIRouter, HTTPException, Response
|
||||
import sqlalchemy
|
||||
from sqlmodel import select
|
||||
from sqlmodel import select, Session
|
||||
from slixmpp.jid import JID
|
||||
|
||||
from xmpp_api.api.types.v1.bot import (
|
||||
@@ -15,15 +15,15 @@ from xmpp_api.api.types.v1.bot import (
|
||||
SendMessageRequest,
|
||||
BotConstraint,
|
||||
BotDomainConstraint,
|
||||
SendMessageGrafanaWebhook,
|
||||
)
|
||||
from xmpp_api.db import BotDep, SessionDep, UserDep
|
||||
from xmpp_api.db import BotDep, SessionDep, UserDep, AuthorizationBotDep
|
||||
from xmpp_api.db.bot import AllowedJid, Bot, JIDType
|
||||
import xmpp_api.db.bot as db_bot
|
||||
from xmpp_api.util.token import generate_token
|
||||
from xmpp_api.db import get_bot_by_id, get_jids_by_bot_id, get_jid_by_jid_token
|
||||
from xmpp_api.util.constraints import bot_constraint_to_db, bot_constraint_from_db
|
||||
from xmpp_api.xmpp.component import XmppApiComponentDep
|
||||
|
||||
from xmpp_api.xmpp.component import XmppApiComponentDep, XmppApiComponent
|
||||
|
||||
router = APIRouter(prefix="/bot")
|
||||
|
||||
@@ -157,11 +157,41 @@ def delete_bot_jid_token(
|
||||
def post_bot_message(
|
||||
jid_token: str,
|
||||
message: SendMessageRequest,
|
||||
request: Request,
|
||||
bot: BotDep,
|
||||
session: SessionDep,
|
||||
component: XmppApiComponentDep,
|
||||
):
|
||||
_post_message(jid_token, message.body, bot, session, component)
|
||||
|
||||
|
||||
@router.post("/message/{jid_token}/grafana")
|
||||
def post_bot_message_grafana_webhook(
|
||||
jid_token: str,
|
||||
message: SendMessageGrafanaWebhook,
|
||||
bot: AuthorizationBotDep,
|
||||
session: SessionDep,
|
||||
component: XmppApiComponentDep,
|
||||
):
|
||||
_post_message(jid_token, message.body, bot, session, component)
|
||||
|
||||
|
||||
def _post_message(
|
||||
jid_token: str,
|
||||
body: str,
|
||||
bot: Bot,
|
||||
session: Session,
|
||||
component: XmppApiComponent,
|
||||
):
|
||||
"""
|
||||
Performs the message sending.
|
||||
|
||||
Args
|
||||
:jid_token The recipient's JID token.
|
||||
:body The body of the message.
|
||||
:bot The bot to send the message as.
|
||||
:session The database session.
|
||||
:component The XMPP component instance.
|
||||
"""
|
||||
# Is the bot allowed to access this JID?
|
||||
jid = session.exec(
|
||||
select(AllowedJid).where(
|
||||
@@ -184,7 +214,7 @@ def post_bot_message(
|
||||
match jid.type:
|
||||
case JIDType.DIRECT:
|
||||
component.send_direct_message(
|
||||
body=message.body,
|
||||
body=body,
|
||||
localpart=bot.localpart,
|
||||
nick=bot.name,
|
||||
recipient=jid.jid,
|
||||
|
||||
@@ -109,6 +109,18 @@ class SendMessageRequest(BaseModel):
|
||||
body: str
|
||||
|
||||
|
||||
class SendMessageGrafanaWebhook(BaseModel):
|
||||
"""
|
||||
Model for the request to send a grafana webhook to a JID.
|
||||
"""
|
||||
|
||||
# The alert title
|
||||
title: str
|
||||
|
||||
# The alert message
|
||||
body: str
|
||||
|
||||
|
||||
class GetBotInformation(BotInformation):
|
||||
"""
|
||||
Response for an information request about a given bot.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from enum import auto, Enum
|
||||
from typing import Annotated, Generator, Sequence, cast
|
||||
|
||||
from fastapi import Depends, Request, HTTPException
|
||||
@@ -27,10 +28,35 @@ def get_session(engine: EngineDep) -> Generator[Session]:
|
||||
SessionDep = Annotated[Session, Depends(get_session)]
|
||||
|
||||
|
||||
class TokenType(Enum):
|
||||
"""
|
||||
Types of authentication tokens.
|
||||
"""
|
||||
|
||||
# Token is put into the X-Token header
|
||||
X_TOKEN = auto()
|
||||
|
||||
# Token is put into the "Authorization" header with type "Bearer".
|
||||
BEARER = auto()
|
||||
|
||||
|
||||
def get_by_token(
|
||||
cls: type[SQLModel], request: Request, session: SessionDep
|
||||
cls: type[SQLModel],
|
||||
request: Request,
|
||||
session: SessionDep,
|
||||
token_type: TokenType = TokenType.X_TOKEN,
|
||||
) -> SQLModel:
|
||||
token = request.headers.get("X-Token")
|
||||
token: str | None = None
|
||||
match token_type:
|
||||
case TokenType.X_TOKEN:
|
||||
token = request.headers.get("X-Token")
|
||||
case TokenType.BEARER:
|
||||
token = request.headers.get("Authorization")
|
||||
if token is not None:
|
||||
parts = token.split(" ")
|
||||
if len(parts) == 2 and parts[0] == "Bearer":
|
||||
token = parts[1]
|
||||
|
||||
if token is None:
|
||||
raise HTTPException(
|
||||
detail="No token provided",
|
||||
@@ -47,19 +73,28 @@ def get_by_token(
|
||||
|
||||
|
||||
def get_user(request: Request, session: SessionDep) -> User:
|
||||
return cast(User, get_by_token(User, request, session))
|
||||
return cast(
|
||||
User, get_by_token(User, request, session, token_type=TokenType.X_TOKEN)
|
||||
)
|
||||
|
||||
|
||||
UserDep = Annotated[User, Depends(get_user)]
|
||||
|
||||
|
||||
def get_bot(request: Request, session: SessionDep) -> Bot:
|
||||
return cast(Bot, get_by_token(Bot, request, session))
|
||||
return cast(Bot, get_by_token(Bot, request, session, token_type=TokenType.X_TOKEN))
|
||||
|
||||
|
||||
BotDep = Annotated[Bot, Depends(get_bot)]
|
||||
|
||||
|
||||
def get_authorization_bot(request: Request, session: SessionDep) -> Bot:
|
||||
return cast(Bot, get_by_token(Bot, request, session, token_type=TokenType.BEARER))
|
||||
|
||||
|
||||
AuthorizationBotDep = Annotated[Bot, Depends(get_authorization_bot)]
|
||||
|
||||
|
||||
def get_bot_by_id(bot_id: str, user_id: str, session: SessionDep) -> Bot | None:
|
||||
"""
|
||||
Fetches the specified bot from the database.
|
||||
|
||||
@@ -33,6 +33,7 @@ def startup():
|
||||
if config.sentry is not None:
|
||||
logging.getLogger("root").info("Setting up Sentry")
|
||||
import sentry_sdk
|
||||
|
||||
sentry_sdk.init(
|
||||
config.sentry.dsn,
|
||||
send_default_pii=True,
|
||||
|
||||
Reference in New Issue
Block a user