schema.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. from typing import Annotated
  2. from urllib.parse import urlparse
  3. from annotated_types import Ge, Le
  4. from django.conf import settings
  5. from django.core.exceptions import ValidationError as DjangoValidationError
  6. from django.core.validators import URLValidator
  7. from django.urls import reverse
  8. from ninja import ModelSchema
  9. from ninja.errors import ValidationError
  10. from pydantic import model_validator
  11. from glitchtip.schema import CamelSchema
  12. from .constants import HTTP_MONITOR_TYPES, MonitorType
  13. from .models import Monitor, MonitorCheck, StatusPage
  14. class MonitorCheckSchema(CamelSchema, ModelSchema):
  15. class Meta:
  16. model = MonitorCheck
  17. fields = ["is_up", "start_check", "reason"]
  18. class MonitorCheckResponseTimeSchema(MonitorCheckSchema, ModelSchema):
  19. """Monitor check with response time. Used in Monitors detail api and monitor checks list"""
  20. class Meta(MonitorCheckSchema.Meta):
  21. fields = MonitorCheckSchema.Meta.fields + ["response_time"]
  22. class MonitorIn(CamelSchema, ModelSchema):
  23. expected_body: str
  24. expected_status: int | None
  25. timeout: Annotated[int, Ge(1), Le(60)] | None
  26. @model_validator(mode="after")
  27. def validate(self):
  28. monitor_type = self.monitor_type
  29. if self.url == "" and monitor_type in HTTP_MONITOR_TYPES + (MonitorType.SSL,):
  30. raise ValidationError("URL is required for " + monitor_type)
  31. if monitor_type in HTTP_MONITOR_TYPES:
  32. try:
  33. URLValidator()(self.url)
  34. except DjangoValidationError as err:
  35. raise ValidationError("Invalid Url") from err
  36. if self.expected_status is None and monitor_type in [
  37. MonitorType.GET,
  38. MonitorType.POST,
  39. ]:
  40. raise ValidationError("Expected status is required for " + monitor_type)
  41. if monitor_type == MonitorType.PORT:
  42. url = self.url.replace("http://", "//", 1)
  43. if not url.startswith("//"):
  44. url = "//" + url
  45. parsed_url = urlparse(url)
  46. message = "Invalid Port URL, expected hostname and port"
  47. try:
  48. if not all([parsed_url.hostname, parsed_url.port]):
  49. raise ValidationError(message)
  50. except ValueError as err:
  51. raise ValidationError(message) from err
  52. self.url = f"{parsed_url.hostname}:{parsed_url.port}"
  53. return self
  54. class Meta:
  55. model = Monitor
  56. fields = [
  57. "monitor_type",
  58. "name",
  59. "url",
  60. "project",
  61. "interval",
  62. ]
  63. class MonitorSchema(CamelSchema, ModelSchema):
  64. project_id: int | None
  65. environment_id: int | None
  66. is_up: bool | None
  67. last_change: str | None
  68. heartbeat_endpoint: str | None
  69. project_name: str | None = None
  70. env_name: str | None = None
  71. checks: list[MonitorCheckSchema]
  72. organization_id: int
  73. monitor_type: MonitorType
  74. class Meta:
  75. model = Monitor
  76. fields = [
  77. "id",
  78. "monitor_type",
  79. "endpoint_id",
  80. "created",
  81. "name",
  82. "url",
  83. "expected_status",
  84. "expected_body",
  85. "interval",
  86. "timeout",
  87. ]
  88. @staticmethod
  89. def resolve_is_up(obj):
  90. return obj.latest_is_up
  91. @staticmethod
  92. def resolve_last_change(obj):
  93. if obj.last_change:
  94. return obj.last_change.isoformat().replace("+00:00", "Z")
  95. @staticmethod
  96. def resolve_heartbeat_endpoint(obj):
  97. if obj.endpoint_id:
  98. return settings.GLITCHTIP_URL.geturl() + reverse(
  99. "api:heartbeat_check",
  100. kwargs={
  101. "organization_slug": obj.organization.slug,
  102. "endpoint_id": obj.endpoint_id,
  103. },
  104. )
  105. @staticmethod
  106. def resolve_project_name(obj):
  107. if obj.project:
  108. return obj.project.name
  109. class MonitorDetailSchema(MonitorSchema):
  110. checks: list[MonitorCheckResponseTimeSchema]
  111. class StatusPageIn(CamelSchema, ModelSchema):
  112. is_public: bool = False
  113. class Meta:
  114. model = StatusPage
  115. fields = ["name", "is_public"]
  116. class StatusPageSchema(StatusPageIn, ModelSchema):
  117. monitors: list[MonitorSchema]
  118. class Meta(StatusPageIn.Meta):
  119. fields = ["name", "slug", "is_public"]