embed_api.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import json
  2. from typing import Optional
  3. from urllib.parse import urlparse
  4. from django.conf import settings
  5. from django.db import IntegrityError
  6. from django.http import HttpRequest, HttpResponse
  7. from django.template.loader import render_to_string
  8. from django.utils.safestring import mark_safe
  9. from ninja import Field, Form, Query, Router
  10. from ninja.errors import AuthenticationError
  11. from apps.issue_events.models import IssueEvent
  12. from apps.projects.models import Project
  13. from glitchtip.schema import CamelSchema
  14. from .forms import UserReportForm
  15. async def embed_auth(request: HttpRequest):
  16. dsn = request.GET.get("dsn")
  17. urlparts = urlparse(dsn)
  18. public_key = urlparts.username
  19. path = str(urlparts.path)
  20. project_id = path.rsplit("/", 1)[-1]
  21. try:
  22. project = (
  23. await Project.objects.filter(
  24. id=project_id, projectkey__public_key=public_key
  25. )
  26. .select_related("organization")
  27. .only("id", "organization__is_accepting_events")
  28. .aget()
  29. )
  30. except ValueError:
  31. raise AuthenticationError([{"message": "Invalid DSN"}])
  32. if not project.organization.is_accepting_events:
  33. raise AuthenticationError([{"message": "Invalid DSN"}])
  34. return project
  35. class EmbedAuthHttpRequest(HttpRequest):
  36. """Django HttpRequest that is known to be authenticated by the embed api"""
  37. auth: Project
  38. router = Router(auth=embed_auth)
  39. # Copy credited to OSS Sentry sentry/web/error_page_embed.py
  40. DEFAULT_TITLE = "It looks like we're having issues."
  41. GENERIC_ERROR = (
  42. "An unknown error occurred while submitting your report. Please try again."
  43. )
  44. FORM_ERROR = "Some fields were invalid. Please correct the errors and try again."
  45. SENT_MESSAGE = "Your feedback has been sent. Thank you!"
  46. DEFAULT_SUBTITLE = "Our team has been notified."
  47. DEFAULT_SUBTITLE2 = "If you'd like to help, tell us what happened below."
  48. DEFAULT_NAME_LABEL = "Name"
  49. DEFAULT_EMAIL_LABEL = "Email"
  50. DEFAULT_COMMENTS_LABEL = "What happened?"
  51. DEFAULT_CLOSE_LABEL = "Close"
  52. DEFAULT_SUBMIT_LABEL = "Submit Crash Report"
  53. class EmbedSchema(CamelSchema):
  54. dsn: str
  55. eventId: str = Field()
  56. class EmbedGetSchema(EmbedSchema):
  57. title: str = DEFAULT_TITLE
  58. subtitle: str = DEFAULT_SUBTITLE
  59. subtitle2: str = DEFAULT_SUBTITLE2
  60. label_name: str = DEFAULT_NAME_LABEL
  61. label_email: str = DEFAULT_EMAIL_LABEL
  62. label_comments: str = DEFAULT_COMMENTS_LABEL
  63. label_close: str = DEFAULT_CLOSE_LABEL
  64. label_submit: str = DEFAULT_SUBMIT_LABEL
  65. error_generic: str = GENERIC_ERROR
  66. error_form_entry: str = FORM_ERROR
  67. success_message: str = SENT_MESSAGE
  68. name: Optional[str] = None
  69. email: Optional[str] = None
  70. class UserReportFormInput(CamelSchema):
  71. name: Optional[str]
  72. email: Optional[str]
  73. comments: Optional[str]
  74. @router.get("/error-page/")
  75. async def get_embed_error_page(request: HttpRequest, data: Query[EmbedGetSchema]):
  76. initial = {"name": data.name, "email": data.email}
  77. # Stubbed, should be configurable
  78. show_branding = True
  79. form = UserReportForm(initial=initial)
  80. template = render_to_string(
  81. "user_reports/error-page-embed.html",
  82. {
  83. "form": form,
  84. "show_branding": show_branding,
  85. "title": data.title,
  86. "subtitle": data.subtitle,
  87. "subtitle2": data.subtitle2,
  88. "name_label": data.label_name,
  89. "email_label": data.label_email,
  90. "comments_label": data.label_comments,
  91. "submit_label": data.label_submit,
  92. "close_label": data.label_close,
  93. },
  94. )
  95. url = settings.GLITCHTIP_URL.geturl() + request.get_full_path()
  96. context = {
  97. "endpoint": mark_safe("*/" + json.dumps(url) + ";/*"),
  98. "template": mark_safe("*/" + json.dumps(template) + ";/*"),
  99. "strings": mark_safe(
  100. json.dumps(
  101. {
  102. "generic_error": data.error_generic,
  103. "form_error": data.error_form_entry,
  104. "sent_message": data.success_message,
  105. }
  106. )
  107. ),
  108. }
  109. return HttpResponse(
  110. render_to_string("user_reports/error-page-embed.js", context, request),
  111. content_type="application/javascript",
  112. )
  113. @router.post("/error-page/")
  114. async def submit_embed_error_page(
  115. request: EmbedAuthHttpRequest,
  116. data: Query[EmbedGetSchema],
  117. form: Form[UserReportFormInput],
  118. ):
  119. event_id = data.eventId
  120. initial = {"name": data.name, "email": data.email}
  121. form = UserReportForm(form.dict(), initial=initial)
  122. if form.is_valid():
  123. report = form.save(commit=False)
  124. report.project_id = request.auth.id
  125. report.event_id = event_id
  126. event = await IssueEvent.objects.filter(id=event_id).afirst()
  127. if event:
  128. report.issue_id = event.issue_id
  129. try:
  130. await report.asave()
  131. except IntegrityError:
  132. pass # Duplicate, ignore
  133. return HttpResponse()
  134. return HttpResponse(
  135. json.dumps({"errors": form.errors}),
  136. status=400,
  137. content_type="application/json",
  138. )