diff --git a/ingest/admin.py b/ingest/admin.py index 8c38f3f..02eb883 100644 --- a/ingest/admin.py +++ b/ingest/admin.py @@ -1,3 +1,8 @@ from django.contrib import admin -# Register your models here. +from .models import DecompressedEvent + + +@admin.register(DecompressedEvent) +class DecompressedEventAdmin(admin.ModelAdmin): + list_display = ["timestamp", "project"] diff --git a/ingest/migrations/0001_initial.py b/ingest/migrations/0001_initial.py new file mode 100644 index 0000000..21e0645 --- /dev/null +++ b/ingest/migrations/0001_initial.py @@ -0,0 +1,21 @@ +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='DecompressedEvent', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('data', models.TextField()), + ('timestamp', models.DateTimeField(auto_now_add=True, help_text='Server-side timestamp')), + ], + ), + ] diff --git a/ingest/migrations/0002_decompressedevent_project.py b/ingest/migrations/0002_decompressedevent_project.py new file mode 100644 index 0000000..fa8ea5e --- /dev/null +++ b/ingest/migrations/0002_decompressedevent_project.py @@ -0,0 +1,18 @@ +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0001_initial'), + ('ingest', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='decompressedevent', + name='project', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='projects.project'), + ), + ] diff --git a/ingest/models.py b/ingest/models.py index 71a8362..69b52bc 100644 --- a/ingest/models.py +++ b/ingest/models.py @@ -1,3 +1,13 @@ +import uuid + from django.db import models -# Create your models here. +from projects.models import Project + + +class DecompressedEvent(models.Model): + """Ingested Event, no processing""" + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + project = models.ForeignKey(Project, blank=False, null=True, on_delete=models.SET_NULL) # SET_NULL: cleanup 'later' + data = models.TextField(blank=False, null=False) + timestamp = models.DateTimeField(null=False, auto_now_add=True, help_text="Server-side timestamp") diff --git a/ingest/negotiation.py b/ingest/negotiation.py index 5a4f798..9b52343 100644 --- a/ingest/negotiation.py +++ b/ingest/negotiation.py @@ -9,10 +9,12 @@ class IgnoreClientContentNegotiation(BaseContentNegotiation): """ Select the first parser in the `.parser_classes` list. """ + # TODO double-check and write down why this is the correct thing return parsers[0] def select_renderer(self, request, renderers, format_suffix): """ Select the first renderer in the `.renderer_classes` list. """ + # TODO double-check and write down why this is the correct thing return (renderers[0], renderers[0].media_type) diff --git a/ingest/views.py b/ingest/views.py index f46df94..7ec0c30 100644 --- a/ingest/views.py +++ b/ingest/views.py @@ -1,12 +1,15 @@ -from rest_framework import permissions +from rest_framework import permissions, status from rest_framework.response import Response from rest_framework.views import APIView # from projects.models import Project # from sentry.utils.auth import parse_auth_header +from projects.models import Project + from .negotiation import IgnoreClientContentNegotiation from .parsers import EnvelopeParser +from .models import DecompressedEvent class BaseIngestAPIView(APIView): @@ -15,15 +18,31 @@ class BaseIngestAPIView(APIView): content_negotiation_class = IgnoreClientContentNegotiation http_method_names = ["post"] - def post(self, request, *args, **kwargs): - import pdb; pdb.set_trace() - # return self.process_event(request.data, request, project) - return Response() + def process_event(self, event_data, request, project): + DecompressedEvent.objects.create(project=project, data=event_data) class IngestEventAPIView(BaseIngestAPIView): - pass + + def post(self, request, *args, **kwargs): + project = Project.objects.first() # TODO actually parse project header + self.process_event(request.data, request, project) + return Response() class IngestEnvelopeAPIView(BaseIngestAPIView): parser_classes = [EnvelopeParser] + + def post(self, request, *args, **kwargs): + project = Project.objects.first() # TODO actually parse project header + + if len(request.data) != 3: + # multi-part envelopes trigger an error too + return Response({"message": "Missing headers / unsupported type"}, status=status.HTTP_501_NOT_IMPLEMENTED) + + if request.data[1].get("type") != "event": + return Response({"message": "Only events are supported"}, status=status.HTTP_501_NOT_IMPLEMENTED) + + event = request.data[2] + self.process_event(event, request, project) + return Response() diff --git a/project/settings.py b/project/settings.py index 78ce408..315f88e 100644 --- a/project/settings.py +++ b/project/settings.py @@ -15,7 +15,7 @@ SECRET_KEY = 'django-insecure-$@clhhieazwnxnha-_zah&(bieq%yux7#^07&xsvhn58t)8@xw # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ["*"] # SECURITY WARNING: also make production-worthy # Application definition @@ -28,6 +28,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', + 'projects', 'ingest', ] @@ -113,7 +114,7 @@ STATIC_URL = 'static/' # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field - +# no support for uuid in this setting yet (https://code.djangoproject.com/ticket/32577) so we leave it as-is DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/projects/__init__.py b/projects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/admin.py b/projects/admin.py new file mode 100644 index 0000000..eaee937 --- /dev/null +++ b/projects/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import Project + + +@admin.register(Project) +class ProjectAdmin(admin.ModelAdmin): + pass diff --git a/projects/apps.py b/projects/apps.py new file mode 100644 index 0000000..afae498 --- /dev/null +++ b/projects/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ProjectsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'projects' diff --git a/projects/migrations/0001_initial.py b/projects/migrations/0001_initial.py new file mode 100644 index 0000000..018f22c --- /dev/null +++ b/projects/migrations/0001_initial.py @@ -0,0 +1,19 @@ +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ], + ), + ] diff --git a/projects/migrations/__init__.py b/projects/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/models.py b/projects/models.py new file mode 100644 index 0000000..a90963d --- /dev/null +++ b/projects/models.py @@ -0,0 +1,6 @@ +import uuid +from django.db import models + + +class Project(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) diff --git a/projects/tests.py b/projects/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/projects/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/projects/views.py b/projects/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/projects/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.