fix error in gitignore

This commit is contained in:
2026-09-24 12:32:59 +03:30
commit 86c117ee2a
165 changed files with 5963 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# audit app
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class AuditConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.audit"
+36
View File
@@ -0,0 +1,36 @@
# Generated by Django 5.2.17 on 2026-09-15 09:29
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='AuditEvent',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('action', models.CharField(db_index=True, max_length=120)),
('object_type', models.CharField(db_index=True, max_length=120)),
('object_id', models.CharField(blank=True, db_index=True, max_length=120)),
('ip', models.GenericIPAddressField(blank=True, null=True)),
('before', models.JSONField(blank=True, default=dict)),
('after', models.JSONField(blank=True, default=dict)),
('metadata', models.JSONField(blank=True, default=dict)),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
('actor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created_at'],
},
),
]
View File
+23
View File
@@ -0,0 +1,23 @@
from django.db import models
from apps.core.models import UUIDModel
class AuditEvent(UUIDModel):
"""Immutable audit log entry. Records are never updated or deleted."""
actor = models.ForeignKey("accounts.User", null=True, blank=True, on_delete=models.SET_NULL)
action = models.CharField(max_length=120, db_index=True)
object_type = models.CharField(max_length=120, db_index=True)
object_id = models.CharField(max_length=120, blank=True, db_index=True)
ip = models.GenericIPAddressField(null=True, blank=True)
before = models.JSONField(default=dict, blank=True)
after = models.JSONField(default=dict, blank=True)
metadata = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
ordering = ["-created_at"]
def __str__(self) -> str:
return f"{self.action} {self.object_type}:{self.object_id}"
+14
View File
@@ -0,0 +1,14 @@
from rest_framework import serializers
from apps.audit.models import AuditEvent
class AuditEventSerializer(serializers.ModelSerializer):
actor_name = serializers.CharField(source="actor.username", read_only=True)
class Meta:
model = AuditEvent
fields = [
"id", "actor", "actor_name", "action", "object_type", "object_id",
"ip", "before", "after", "metadata", "created_at",
]
+22
View File
@@ -0,0 +1,22 @@
from apps.audit.models import AuditEvent
def record(actor, action, object_type, object_id="", before=None, after=None, metadata=None, request=None):
"""Write an immutable audit event. Never raises to avoid breaking business flows."""
ip = None
if request is not None:
xff = request.META.get("HTTP_X_FORWARDED_FOR")
ip = (xff.split(",")[0].strip() if xff else request.META.get("REMOTE_ADDR"))
try:
AuditEvent.objects.create(
actor=actor if actor and actor.is_authenticated else None,
action=action,
object_type=object_type,
object_id=str(object_id),
ip=ip,
before=before or {},
after=after or {},
metadata=metadata or {},
)
except Exception:
pass
+8
View File
@@ -0,0 +1,8 @@
from rest_framework.routers import DefaultRouter
from apps.audit.views import AuditEventViewSet
router = DefaultRouter()
router.register("", AuditEventViewSet, basename="audit-event")
urlpatterns = router.urls
+12
View File
@@ -0,0 +1,12 @@
from rest_framework import mixins, viewsets
from apps.audit.models import AuditEvent
from apps.audit.serializers import AuditEventSerializer
from apps.rbac.permissions import ActionPermission
class AuditEventViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = AuditEvent.objects.select_related("actor").all()
serializer_class = AuditEventSerializer
permission_classes = [ActionPermission]
code_map = {"list": "user.view", "retrieve": "user.view"}