api.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import logging
  2. from typing import Optional
  3. import orjson
  4. from allauth.socialaccount.models import SocialApp
  5. from allauth.socialaccount.providers.openid_connect.views import (
  6. OpenIDConnectOAuth2Adapter,
  7. )
  8. from asgiref.sync import sync_to_async
  9. from django.conf import settings
  10. from django.http import HttpRequest
  11. from ninja import Field, ModelSchema, NinjaAPI
  12. from ninja.errors import ValidationError
  13. from sentry_sdk import capture_exception, set_context, set_level
  14. from apps.api_tokens.api import router as api_tokens_router
  15. from apps.event_ingest.api import router as event_ingest_router
  16. from apps.event_ingest.embed_api import router as embed_router
  17. from apps.importer.api import router as importer_router
  18. from apps.issue_events.api import router as issue_events_router
  19. from apps.releases.api import router as releases_router
  20. from apps.teams.api import router as teams_router
  21. from apps.users.api import router as users_router
  22. from apps.users.utils import ais_user_registration_open
  23. from glitchtip.constants import SOCIAL_ADAPTER_MAP
  24. from ..schema import CamelSchema
  25. from .authentication import SessionAuth, TokenAuth
  26. from .exceptions import ThrottleException
  27. from .parsers import EnvelopeParser
  28. try:
  29. from djstripe.settings import djstripe_settings
  30. except ImportError:
  31. pass
  32. logger = logging.getLogger(__name__)
  33. api = NinjaAPI(
  34. parser=EnvelopeParser(),
  35. title="GlitchTip API",
  36. urls_namespace="api",
  37. auth=[TokenAuth(), SessionAuth()],
  38. )
  39. api.add_router("0", api_tokens_router)
  40. api.add_router("", event_ingest_router)
  41. api.add_router("0", importer_router)
  42. api.add_router("0", issue_events_router)
  43. api.add_router("0", teams_router)
  44. api.add_router("0", users_router)
  45. api.add_router("0", releases_router)
  46. api.add_router("embed", embed_router)
  47. # Would be better at the router level
  48. # https://github.com/vitalik/django-ninja/issues/442
  49. @api.exception_handler(ValidationError)
  50. def log_validation(request, exc):
  51. if request.resolver_match.route == "api/<project_id>/envelope/":
  52. set_level("warning")
  53. set_context(
  54. "incoming event", [orjson.loads(line) for line in request.body.splitlines()]
  55. )
  56. capture_exception(exc)
  57. logger.warning(f"Validation error on {request.path}", exc_info=exc)
  58. return api.create_response(request, {"detail": exc.errors}, status=422)
  59. @api.exception_handler(ThrottleException)
  60. def throttled(request: HttpRequest, exc: ThrottleException):
  61. response = api.create_response(
  62. request,
  63. {"message": "Please retry later"},
  64. status=429,
  65. )
  66. if retry_after := exc.retry_after:
  67. if isinstance(retry_after, int):
  68. response["Retry-After"] = retry_after
  69. else:
  70. response["Retry-After"] = retry_after.strftime("%a, %d %b %Y %H:%M:%S GMT")
  71. return response
  72. class SocialAppSchema(ModelSchema):
  73. scopes: list[str]
  74. authorize_url: Optional[str]
  75. class Config:
  76. model = SocialApp
  77. model_fields = ["name", "client_id", "provider"]
  78. class SettingsOut(CamelSchema):
  79. social_apps: list[SocialAppSchema]
  80. billing_enabled: bool
  81. i_paid_for_glitchtip: bool = Field(serialization_alias="iPaidForGlitchTip")
  82. enable_user_registration: bool
  83. enable_organization_creation: bool
  84. stripe_public_key: Optional[str]
  85. plausible_url: Optional[str]
  86. plausible_domain: Optional[str]
  87. chatwoot_website_token: Optional[str]
  88. sentryDSN: Optional[str]
  89. sentry_traces_sample_rate: Optional[float]
  90. environment: Optional[str]
  91. version: str
  92. server_time_zone: str
  93. @api.get("settings/", response=SettingsOut, by_alias=True, auth=None)
  94. async def get_settings(request: HttpRequest):
  95. social_apps: list[SocialApp] = []
  96. async for social_app in SocialApp.objects.order_by("name"):
  97. provider = social_app.get_provider(request)
  98. social_app.scopes = provider.get_scope()
  99. adapter_cls = SOCIAL_ADAPTER_MAP.get(social_app.provider)
  100. if adapter_cls == OpenIDConnectOAuth2Adapter:
  101. adapter = adapter_cls(request, social_app.provider_id)
  102. elif adapter_cls:
  103. adapter = adapter_cls(request)
  104. else:
  105. adapter = None
  106. if adapter:
  107. social_app.authorize_url = await sync_to_async(
  108. lambda: adapter.authorize_url
  109. )()
  110. social_app.provider = social_app.provider_id or social_app.provider
  111. social_apps.append(social_app)
  112. billing_enabled = settings.BILLING_ENABLED
  113. return {
  114. "social_apps": social_apps,
  115. "billing_enabled": billing_enabled,
  116. "i_paid_for_glitchtip": settings.I_PAID_FOR_GLITCHTIP,
  117. "enable_user_registration": await ais_user_registration_open(),
  118. "enable_organization_creation": settings.ENABLE_ORGANIZATION_CREATION,
  119. "stripe_public_key": djstripe_settings.STRIPE_PUBLIC_KEY
  120. if billing_enabled
  121. else None,
  122. "plausible_url": settings.PLAUSIBLE_URL,
  123. "plausible_domain": settings.PLAUSIBLE_DOMAIN,
  124. "chatwoot_website_token": settings.CHATWOOT_WEBSITE_TOKEN,
  125. "sentryDSN": settings.SENTRY_FRONTEND_DSN,
  126. "sentry_traces_sample_rate": settings.SENTRY_TRACES_SAMPLE_RATE,
  127. "environment": settings.ENVIRONMENT,
  128. "version": settings.GLITCHTIP_VERSION,
  129. "server_time_zone": settings.TIME_ZONE,
  130. }