From 6b66b9b189d027f5afb927941713c7745dd79ab4 Mon Sep 17 00:00:00 2001 From: kritzl Date: Thu, 11 Jul 2024 22:04:33 +0200 Subject: [PATCH] add interface to manage webhooks; call webhook --- src/vinywaji/api/serializers.py | 15 ++++ src/vinywaji/api/urls.py | 1 + src/vinywaji/api/views.py | 20 +++++ src/vinywaji/gui/static/css/dist/styles.css | 78 +++++++++++++++++++ .../gui/templates/components/forms.html | 4 +- .../components/forms/add_webhook.html | 16 ++++ src/vinywaji/gui/templates/views/profile.html | 58 +++++++++++++- src/vinywaji/gui/views.py | 15 +++- 8 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 src/vinywaji/gui/templates/components/forms/add_webhook.html diff --git a/src/vinywaji/api/serializers.py b/src/vinywaji/api/serializers.py index 184c5ae..8252a05 100644 --- a/src/vinywaji/api/serializers.py +++ b/src/vinywaji/api/serializers.py @@ -28,3 +28,18 @@ def create(self, validated_data): validated_data["amount"] = validated_data["amount"] * -1 validated_data["amount"] = int(validated_data["amount"]) return models.Transaction.objects.create(**validated_data) + + +class WebhookConfigSerializer(serializers.ModelSerializer): + class Meta: + model = models.WebhookConfig + fields = "__all__" + + user = serializers.PrimaryKeyRelatedField( + default=serializers.CurrentUserDefault(), queryset=models.User.objects.all() + ) + amount = serializers.FloatField() + + def create(self, validated_data): + validated_data["amount"] = int(validated_data["amount"]) + return models.WebhookConfig.objects.create(**validated_data) diff --git a/src/vinywaji/api/urls.py b/src/vinywaji/api/urls.py index 0123b5a..b8bec2a 100644 --- a/src/vinywaji/api/urls.py +++ b/src/vinywaji/api/urls.py @@ -11,6 +11,7 @@ router = routers.SimpleRouter() router.register(r"users", views.UserViewSet, basename="user") router.register(r"transactions", views.TransactionViewSet, basename="transaction") +router.register(r"webhooks", views.WebhookConfigViewSet, basename="webhook") urlpatterns = [ diff --git a/src/vinywaji/api/views.py b/src/vinywaji/api/views.py index bd9c14f..1692cb1 100644 --- a/src/vinywaji/api/views.py +++ b/src/vinywaji/api/views.py @@ -72,3 +72,23 @@ def get_permissions(self): return [IsAuthenticated()] else: return [(IsAdminUser | permissions.IsRelatedToRequester)()] + + +class WebhookConfigViewSet( + viewsets.mixins.CreateModelMixin, + viewsets.mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + serializer_class = serializers.WebhookConfigSerializer + + def get_queryset(self): + if self.request.user.is_superuser: + return models.WebhookConfig.objects.all() + else: + return models.WebhookConfig.objects.filter(user=self.request.user) + + def get_permissions(self): + if self.action == "list": + return [IsAuthenticated()] + else: + return [(IsAdminUser | permissions.IsRelatedToRequester)()] diff --git a/src/vinywaji/gui/static/css/dist/styles.css b/src/vinywaji/gui/static/css/dist/styles.css index 3411ba8..23cc063 100644 --- a/src/vinywaji/gui/static/css/dist/styles.css +++ b/src/vinywaji/gui/static/css/dist/styles.css @@ -803,6 +803,10 @@ input[type=number] { grid-column: span 3 / span 3; } +.col-span-full { + grid-column: 1 / -1; +} + .mx-auto { margin-left: auto; margin-right: auto; @@ -813,6 +817,11 @@ input[type=number] { margin-bottom: 1rem; } +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + .mb-8 { margin-bottom: 2rem; } @@ -845,6 +854,14 @@ input[type=number] { width: 100%; } +.min-w-28 { + min-width: 7rem; +} + +.min-w-12 { + min-width: 3rem; +} + .grow { flex-grow: 1; } @@ -882,10 +899,18 @@ input[type=number] { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + .flex-col { flex-direction: column; } +.items-center { + align-items: center; +} + .items-stretch { align-items: stretch; } @@ -922,11 +947,40 @@ input[type=number] { border-radius: 0.5rem; } +.border-b { + border-bottom-width: 1px; +} + .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.border-gray-500 { + --tw-border-opacity: 1; + border-color: rgb(107 114 128 / var(--tw-border-opacity)); +} + +.border-gray-800 { + --tw-border-opacity: 1; + border-color: rgb(31 41 55 / var(--tw-border-opacity)); +} + +.border-gray-700 { + --tw-border-opacity: 1; + border-color: rgb(55 65 81 / var(--tw-border-opacity)); +} + +.border-b-gray-300 { + --tw-border-opacity: 1; + border-bottom-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.border-b-gray-400 { + --tw-border-opacity: 1; + border-bottom-color: rgb(156 163 175 / var(--tw-border-opacity)); +} + .bg-black\/10 { background-color: rgb(0 0 0 / 0.1); } @@ -4856,6 +4910,11 @@ input[type=number] { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } +.bg-red-400 { + --tw-bg-opacity: 1; + background-color: rgb(248 113 113 / var(--tw-bg-opacity)); +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -4897,6 +4956,10 @@ input[type=number] { text-align: center; } +.align-middle { + vertical-align: middle; +} + .text-2xl { font-size: 1.5rem; line-height: 2rem; @@ -8895,6 +8958,11 @@ input[type=number] { background-color: rgb(4 47 46 / 0.95); } +.hover\:bg-red-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); +} + .hover\:text-gray-500:hover { --tw-text-opacity: 1; color: rgb(107 114 128 / var(--tw-text-opacity)); @@ -16854,6 +16922,11 @@ input[type=number] { background-color: rgb(255 255 255 / 0.1); } + .dark\:bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); + } + .dark\:text-gray-400 { --tw-text-opacity: 1; color: rgb(156 163 175 / var(--tw-text-opacity)); @@ -20780,6 +20853,11 @@ input[type=number] { background-color: rgb(4 47 46 / 0.95); } + .dark\:hover\:bg-red-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); + } + .dark\:focus\:border-blue-500:focus { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); diff --git a/src/vinywaji/gui/templates/components/forms.html b/src/vinywaji/gui/templates/components/forms.html index ec7b7d8..2551134 100644 --- a/src/vinywaji/gui/templates/components/forms.html +++ b/src/vinywaji/gui/templates/components/forms.html @@ -6,7 +6,7 @@ {% endmacro %} -{% macro input label name value="" type="text" step="1" min="0" placeholder="" required=False class="" %} +{% macro input label name value="" type="text" step="1" min="" placeholder="" required=False class="" %}
+ {% csrf_token %} +
+ {% usemacro input "Description" "description" type="text" class="col-span-full" %} + + {% usemacro input "Transaction amount (ct)" "amount" type="number" placeholder="150" required=True step="0.1" %} + + {% usemacro input "Transaction description" "transaction_description" type="text" placeholder="fritz-kola" %} + + {% usemacro button "Add Webhook" class="col-span-full" %} +
+ diff --git a/src/vinywaji/gui/templates/views/profile.html b/src/vinywaji/gui/templates/views/profile.html index 6dc6ec8..0709bf6 100644 --- a/src/vinywaji/gui/templates/views/profile.html +++ b/src/vinywaji/gui/templates/views/profile.html @@ -13,14 +13,64 @@
Basic Information
Username:
{{ request.user }}
-
Webhooks
-
- TODO: Put webhook configuration here -
+ + {% include "components/forms/add_webhook.html" %} + +
+ + {% csrf_token %} + + + +
+ {% for webhook in webhooks %} +
+
+ {{ webhook.description }} +
+
+
+ {{ webhook.amount }} + {{ webhook.transaction_description }} +
+
+ + +
+
+
+ {% endfor %} +
{% endblock %} diff --git a/src/vinywaji/gui/views.py b/src/vinywaji/gui/views.py index a280aba..dbc691e 100644 --- a/src/vinywaji/gui/views.py +++ b/src/vinywaji/gui/views.py @@ -7,6 +7,8 @@ from django.shortcuts import render from django.views import View +from vinywaji.core.models import WebhookConfig + class DashboardView(View): def get(self, request: HttpRequest): @@ -38,14 +40,23 @@ def get(self, request: HttpRequest): "title": settings.ORG_NAME, } if not request.user.is_anonymous: - context.update({}) + context.update( + { + "webhooks": request.user.webhooks.all(), + } + ) return render(request, "views/profile.html", context) class WebhookTriggerView(View): def get(self, request: HttpRequest, trigger: str): - return HttpResponse("OK") + webhook = WebhookConfig.objects.filter(trigger_key=trigger) + if len(webhook) == 1: + webhook[0].trigger() + return HttpResponse("OK") + else: + return HttpResponse("Failed") def manifest(request):