Files

123 lines
5.7 KiB
Python
Raw Permalink Normal View History

2026-08-31 15:11:52 +03:30
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
# مانیتورینگ اپ: هر فراخوانیِ API خودکار (سمت سرور، بدون نیاز به تغییر اپ
# فلاتر) لاگ می‌شود؛ رویدادهای سمت کلاینت (صفحه‌بازدید/کلیک/خطا) از یک
# اندپوینتِ دسته‌ای ثبت می‌شوند که اپ فلاتر دوره‌ای می‌فرستد.
class ApiCallLog(models.Model):
"""هر درخواستِ API — خودکار توسط ApiCallLoggingMiddleware ثبت می‌شود."""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="api_calls",
verbose_name=_("کاربر"),
)
method = models.CharField(max_length=10, verbose_name=_("متد"))
path = models.CharField(max_length=255, verbose_name=_("مسیر"))
status_code = models.PositiveSmallIntegerField(verbose_name=_("کد وضعیت"))
duration_ms = models.FloatField(verbose_name=_("زمان پاسخ (میلی‌ثانیه)"))
ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name=_("آی‌پی"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("زمان"))
class Meta:
verbose_name = _("لاگ فراخوانی API")
verbose_name_plural = _("لاگ‌های فراخوانی API")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["user", "created_at"]),
models.Index(fields=["path", "created_at"]),
]
def __str__(self):
return f"{self.method} {self.path} ({self.status_code})"
class AppSession(models.Model):
"""یک دوره‌ی «باز بودنِ اپ» — از سمت کلاینت (اپ فلاتر) با یک UUID شروع
می‌شود، با رویدادِ session_end یا انقضای بی‌فعالیت بسته می‌شود."""
id = models.CharField(max_length=36, primary_key=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="app_sessions", verbose_name=_("کاربر")
)
platform = models.CharField(max_length=30, blank=True, verbose_name=_("پلتفرم"))
app_version = models.CharField(max_length=30, blank=True, verbose_name=_("نسخه‌ی اپ"))
ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name=_("آی‌پی"))
started_at = models.DateTimeField(verbose_name=_("شروع"))
ended_at = models.DateTimeField(null=True, blank=True, verbose_name=_("پایان"))
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = _("جلسه‌ی استفاده از اپ")
verbose_name_plural = _("جلسه‌های استفاده از اپ")
ordering = ["-started_at"]
indexes = [models.Index(fields=["user", "started_at"])]
def __str__(self):
return f"{self.user} — {self.started_at:%Y-%m-%d %H:%M}"
@property
def duration_seconds(self) -> float | None:
if not self.ended_at:
return None
return (self.ended_at - self.started_at).total_seconds()
class PageView(models.Model):
"""یک بازدید از یک صفحه/مسیر توی اپ — با ورود و مدت‌زمانِ ماندن."""
session = models.ForeignKey(AppSession, on_delete=models.CASCADE, related_name="page_views")
screen_name = models.CharField(max_length=150, verbose_name=_("نام صفحه"))
entered_at = models.DateTimeField(verbose_name=_("زمان ورود"))
duration_seconds = models.FloatField(null=True, blank=True, verbose_name=_("مدت ماندن (ثانیه)"))
class Meta:
verbose_name = _("بازدید صفحه")
verbose_name_plural = _("بازدیدهای صفحه")
ordering = ["entered_at"]
indexes = [models.Index(fields=["screen_name"])]
def __str__(self):
return self.screen_name
class ClickEvent(models.Model):
"""یک لمس/کلیک — مختصات نسبی (۰ تا ۱) نسبت به عرض/ارتفاع صفحه، برای
این‌که مستقل از سایز گوشی قابل‌مقایسه باشد."""
session = models.ForeignKey(AppSession, on_delete=models.CASCADE, related_name="clicks")
screen_name = models.CharField(max_length=150, blank=True, verbose_name=_("نام صفحه"))
element = models.CharField(max_length=150, blank=True, verbose_name=_("برچسبِ عنصر (اختیاری)"))
x = models.FloatField(null=True, blank=True)
y = models.FloatField(null=True, blank=True)
created_at = models.DateTimeField(verbose_name=_("زمان"))
class Meta:
verbose_name = _("رویداد کلیک")
verbose_name_plural = _("رویدادهای کلیک")
ordering = ["-created_at"]
indexes = [models.Index(fields=["screen_name"])]
class ClientErrorLog(models.Model):
"""خطای سمت کلاینت (uncaught exception توی اپ فلاتر) — «کجا مشکل خورده»."""
session = models.ForeignKey(
AppSession, on_delete=models.SET_NULL, null=True, blank=True, related_name="errors"
)
screen_name = models.CharField(max_length=150, blank=True, verbose_name=_("نام صفحه"))
message = models.TextField(verbose_name=_("پیام خطا"))
stack_trace = models.TextField(blank=True, verbose_name=_("Stack trace"))
created_at = models.DateTimeField(verbose_name=_("زمان"))
class Meta:
verbose_name = _("خطای سمت کاربر")
verbose_name_plural = _("خطاهای سمت کاربر")
ordering = ["-created_at"]