✨ Sentry in Backend, Image-Import, Settings-function improvement and reload detection
This commit is contained in:
+24
-1
@@ -1,14 +1,32 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
import sentry_sdk
|
||||
from socketio import ASGIApp
|
||||
|
||||
from classquiz.db import database
|
||||
from classquiz.config import settings
|
||||
from classquiz.routers import users, quiz, utils, stats
|
||||
from classquiz.socket_server import sio
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
|
||||
settings = settings()
|
||||
if settings.sentry_dsn:
|
||||
sentry_sdk.init(dsn=settings.sentry_dsn, integrations=[RedisIntegration()])
|
||||
app = FastAPI(redoc_url="", docs_url="/api/docs")
|
||||
app.state.database = database
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def sentry_exception(request: Request, call_next):
|
||||
try:
|
||||
response = await call_next(request)
|
||||
return response
|
||||
except Exception as e:
|
||||
with sentry_sdk.push_scope() as scope:
|
||||
scope.set_context("request", request)
|
||||
sentry_sdk.capture_exception(e)
|
||||
raise e
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup() -> None:
|
||||
database_ = app.state.database
|
||||
@@ -23,6 +41,11 @@ async def shutdown() -> None:
|
||||
await database_.disconnect()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": sio}
|
||||
|
||||
|
||||
app.include_router(users.router, tags=["users"], prefix="/api/v1/users")
|
||||
app.include_router(quiz.router, tags=["quiz"], prefix="/api/v1/quiz")
|
||||
app.include_router(utils.router, tags=["utils"], prefix="/api/v1/utils")
|
||||
|
||||
@@ -16,6 +16,7 @@ from passlib.hash import argon2
|
||||
from classquiz.cache import get_cache
|
||||
from classquiz.config import settings
|
||||
from classquiz.db.models import *
|
||||
settings = settings()
|
||||
|
||||
# pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
pwd_context = argon2
|
||||
|
||||
@@ -6,6 +6,7 @@ import ormar
|
||||
|
||||
from classquiz.config import redis, settings
|
||||
from classquiz.db.models import User
|
||||
settings = settings()
|
||||
|
||||
|
||||
async def cache_account(criteria: str, content: str) -> Union[User, None]:
|
||||
|
||||
+9
-2
@@ -1,4 +1,5 @@
|
||||
import redis.asyncio as redis_lib
|
||||
from functools import lru_cache
|
||||
from pydantic import BaseSettings, RedisDsn, PostgresDsn
|
||||
|
||||
|
||||
@@ -17,13 +18,19 @@ class Settings(BaseSettings):
|
||||
mail_server: str
|
||||
mail_port: int
|
||||
secret_key: str
|
||||
imgur_client_id: str = "b13fefc8bb1db87"
|
||||
access_token_expire_minutes: int = 30
|
||||
cache_expiry: int = 86400
|
||||
sentry_dsn: str | None = "https://4981de1f72f24fd7b5e21b8913b93a02@o661934.ingest.sentry.io/6254641"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = 'utf-8'
|
||||
|
||||
|
||||
settings = Settings()
|
||||
redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings.redis)
|
||||
@lru_cache()
|
||||
def settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings().redis)
|
||||
|
||||
@@ -3,5 +3,7 @@ import sqlalchemy
|
||||
|
||||
from classquiz.config import settings
|
||||
|
||||
settings = settings()
|
||||
|
||||
database = databases.Database(settings.db_url)
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
@@ -8,6 +8,8 @@ from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
from classquiz.config import settings
|
||||
from classquiz.db.models import User
|
||||
|
||||
settings = settings()
|
||||
|
||||
jinja = Environment(
|
||||
loader=PackageLoader('classquiz.emails', 'templates'),
|
||||
autoescape=select_autoescape(['html', 'xml']),
|
||||
|
||||
@@ -2,10 +2,48 @@ import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from aiohttp import ClientSession
|
||||
import pydantic
|
||||
from aiohttp import ClientSession, FormData
|
||||
import html
|
||||
from classquiz.db.models import Quiz, QuizAnswer, QuizQuestion, User
|
||||
from classquiz.kahoot_importer.get import get as get_quiz
|
||||
from classquiz.config import settings
|
||||
settings = settings()
|
||||
|
||||
async def _download_image(url: str) -> bytes:
|
||||
async with ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
return await resp.read()
|
||||
|
||||
|
||||
async def _upload_to_imgur(image: bytes, album_id: str | None) -> str:
|
||||
"""
|
||||
Uploads an image to Imgur.
|
||||
:param image: The image to upload.
|
||||
:return: The URL of the image on Imgur.
|
||||
"""
|
||||
|
||||
formdata = FormData()
|
||||
formdata.add_field('image', image, filename='image.png')
|
||||
if album_id:
|
||||
formdata.add_field('album', album_id)
|
||||
async with ClientSession(headers={"Authorization": f"Client-ID {settings.imgur_client_id}"}) as session:
|
||||
async with session.post("https://api.imgur.com/3/image", data=image) as resp:
|
||||
data = await resp.json()
|
||||
return data["data"]["link"]
|
||||
|
||||
|
||||
async def _generate_imgur_album(quiz_id: str) -> str:
|
||||
"""
|
||||
Generates an album on Imgur.
|
||||
:param quiz_id: The ID of the quiz.
|
||||
:return: The URL of the album on Imgur.
|
||||
"""
|
||||
formdata = FormData()
|
||||
formdata.add_field('title', quiz_id)
|
||||
async with ClientSession(headers={"Authorization": f"Client-ID {settings.imgur_client_id}"}) as session:
|
||||
async with session.post("https://api.imgur.com/3/album", data={"title": "Quiz: " + quiz_id}, ) as resp:
|
||||
data = await resp.json()
|
||||
return data["data"]["id"]
|
||||
|
||||
|
||||
async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
||||
@@ -19,12 +57,19 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
||||
if quiz is None:
|
||||
return "quiz not found"
|
||||
quiz_questions: list[dict] = []
|
||||
imgur_album_id = await _generate_imgur_album(quiz_id)
|
||||
|
||||
for q in quiz.kahoot.questions:
|
||||
answers: list[QuizAnswer] = []
|
||||
image = None
|
||||
if q.image is not None and q.image != "":
|
||||
image_bytes = await _download_image(q.image)
|
||||
image = await _upload_to_imgur(image_bytes, imgur_album_id)
|
||||
# image = q.image
|
||||
for a in q.choices:
|
||||
answers.append((QuizAnswer(right=a.correct, answer=a.answer)))
|
||||
quiz_questions.append(QuizQuestion(question=q.question, answers=answers, time=str(q.time / 1000)).dict())
|
||||
answers.append((QuizAnswer(right=a.correct, answer=html.unescape(a.answer))))
|
||||
quiz_questions.append(
|
||||
QuizQuestion(question=q.question, answers=answers, time=str(q.time / 1000), image=image).dict())
|
||||
quiz_data = Quiz(id=uuid.uuid4(), public=False, title=quiz.kahoot.title, description=quiz.kahoot.description,
|
||||
created_at=datetime.now(), updated_at=datetime.now(), user_id=user.id,
|
||||
questions=json.dumps(quiz_questions))
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi import APIRouter
|
||||
from fastapi.responses import Response
|
||||
|
||||
from classquiz.config import settings
|
||||
settings = settings()
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user