Skip to content

Commit

Permalink
Merge pull request #9 from fsinfuhh/feature/transaction_webhooks
Browse files Browse the repository at this point in the history
Feature: transaction webhooks
  • Loading branch information
Wahlord authored Jan 8, 2025
2 parents bc4fb9e + 0b82eeb commit e0ad05a
Show file tree
Hide file tree
Showing 54 changed files with 2,688 additions and 580 deletions.
2 changes: 2 additions & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ VW_DB=sqlite://./db.sqlite
VW_OPENID_CLIENT_ID=dev-client
VW_OPENID_CLIENT_SECRET=public-secret
VW_OPENID_SCOPE=openid profile
VW_ORG_NAME=Vinywaji
VW_THEME_COLOR=amber
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM docker.io/tiangolo/uvicorn-gunicorn:python3.10-slim
# add system dependencies
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update &&\
apt-get install -y --no-install-recommends git &&\
apt-get install -y --no-install-recommends git nodejs npm &&\
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pip3 install --no-cache pipenv psycopg2-binary
WORKDIR /app
Expand All @@ -18,7 +18,7 @@ COPY docker/prestart.sh /app/
RUN ln -sf /app/vinywaji/asgi.py /app/main.py

# setup recommended container config
RUN mkdir /app/data
RUN mkdir -p /app/data
ENV VW_DATABASE_URL=sqlite:///app/data/db.sqlite

# add additional metadata
Expand Down
3 changes: 3 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ environs = { version = "~=9.5", extras = ["django"] }
opentelemetry-api = "~=1.22.0"
opentelemetry-sdk = "~=1.22.0"
opentelemetry-exporter-prometheus = "*"
django-tailwind = "*"
django-templates-macros = "*"

[dev-packages]
pre-commit = "*"
Expand All @@ -22,6 +24,7 @@ black = "*"
ipython = "*"
pytest = "*"
pytest-django = "*"
django-browser-reload = "*"

[requires]
python_version = "3"
680 changes: 351 additions & 329 deletions Pipfile.lock

Large diffs are not rendered by default.

63 changes: 47 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,59 @@ To start it:

```shell
pipenv shell
./src/manage.py tailwind install
./src/manage.py check --deploy
./src/manage.py migrate
./src/manage.py tailwind build
./src/manage.py collectstatic
./src/manage.py runserver
```

## Development

Install dev dependencies:
```shell

pipenv install -d --ignore-pipfile
```

Install npm. E.g.:
```shell
sudo apt install nodejs npm
pipenv shell
./src/manage.py tailwind install
```

Run Django Dev Server
```shell
./src/manage.py check --deploy
./src/manage.py migrate
./src/manage.py runserver
```

Run tailwind server
```shell
./src/manage.py tailwind start
```

## Configuration

The application is configured at runtime via the following environment variables:

| Name | Default | Description | Notes |
|-------------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
| VW_DB | *required* | Url that specifies the complete database connection. [Documentation](https://pypi.org/project/dj-database-url/) | In container based deployments this preconfigured to point to `/app/data/db.sqlite` |
| VW_SECRET_KEY | *required* | Django secret key. **Keep this secret!** | |
| VW_ALLOWED_HOSTS | *required* | List of hostnames which may be used when accessing the application. | |
| VW_SERVED_OVER_HTTPS | `false` | Whether the application is served over HTTPS. If enabled, automatic redirects and additional security measures are activated. | |
| VW_HSTS_SECONDS | `63072000` | If larger than 0 and `BL_SERVED_OVER_HTTPS` is true, HSTS is enabled with this configured value. | |
| VW_TRUST_REVERSE_PROXY | `false` | If true, headers set by a reverse proxy (i.e. `X-Forwarded-Proto`) are trusted. | |
| VW_OPENID_PROVIDER_NAME | `Mafiasi` | A human readable name identifying the authentication provider. | |
| VW_OPENID_ISSUER | *mafiasi-identity* | The url of the openid issue | |
| VW_OPENID_CLIENT_ID | *required* | Mafiasi-Identity client ID. Used for authentication | |
| VW_OPENID_CLIENT_SECRET | *required* | Mafiasi-Identity client secret. Used for authentication | |
| VW_ALLOWED_METRICS_NETS | `127.0.0.0/8`, `::/64` | List of IP networks which are allowed to access the /metrics endpoint | |
| VW_ORG_NAME | `Bit-Bots Drinks` | Application Title related to the organisation that hosts it | |
| VW_DEFAULT_AMOUNT | `1.5` | A float describing how much a drink costs per default | |
| VW_MAFIASI_COLORS | `false` | Whether a color scheme specific to Mafiasi should be used | |
| Name | Default | Description | Notes |
|-------------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
| VW_DEBUG | `false` | Whether Debug Mode should be enabled. | When `true`, dependencies have to be installed with `pipenv install -d --ignore-pipfile` |
| VW_DB | *required* | Url that specifies the complete database connection. [Documentation](https://pypi.org/project/dj-database-url/) | In container based deployments this preconfigured to point to `/app/data/db.sqlite` |
| VW_SECRET_KEY | *required* | Django secret key. **Keep this secret!** | |
| VW_ALLOWED_HOSTS | *required* | List of hostnames which may be used when accessing the application. | |
| VW_SERVED_OVER_HTTPS | `false` | Whether the application is served over HTTPS. If enabled, automatic redirects and additional security measures are activated. | |
| VW_HSTS_SECONDS | `63072000` | If larger than 0 and `BL_SERVED_OVER_HTTPS` is true, HSTS is enabled with this configured value. | |
| VW_TRUST_REVERSE_PROXY | `false` | If true, headers set by a reverse proxy (i.e. `X-Forwarded-Proto`) are trusted. | |
| VW_OPENID_PROVIDER_NAME | `Mafiasi` | A human readable name identifying the authentication provider. | |
| VW_OPENID_ISSUER | *mafiasi-identity* | The url of the openid issue | |
| VW_OPENID_CLIENT_ID | *required* | Mafiasi-Identity client ID. Used for authentication | |
| VW_OPENID_CLIENT_SECRET | *required* | Mafiasi-Identity client secret. Used for authentication | |
| VW_ALLOWED_METRICS_NETS | `127.0.0.0/8`, `::/64` | List of IP networks which are allowed to access the /metrics endpoint | |
| VW_ORG_NAME | `Bit-Bots Drinks` | Application Title related to the organisation that hosts it | |
| VW_DEFAULT_AMOUNT | `1.5` | A float describing how much a drink costs per default | |
| VW_THEME_COLOR | `teal` | Which color theme should be used (tailwindcss colors) | |
1 change: 1 addition & 0 deletions docker/prestart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
set -e

./manage.py check --deploy
./manage.py tailwind build
./manage.py collectstatic --no-input
./manage.py migrate
15 changes: 15 additions & 0 deletions src/vinywaji/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
1 change: 1 addition & 0 deletions src/vinywaji/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
20 changes: 20 additions & 0 deletions src/vinywaji/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)()]
12 changes: 3 additions & 9 deletions src/vinywaji/core/management/commands/load_old_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ def handle(self, *args, **options):

# re-add users
for user_data in (
i
for i in fixture_json
if i["model"] == "django_auth_mafiasi.mafiasiauthmodeluser"
i for i in fixture_json if i["model"] == "django_auth_mafiasi.mafiasiauthmodeluser"
):
logger.info(
"adding user %s with openid sub %s",
Expand All @@ -43,12 +41,8 @@ def handle(self, *args, **options):
)

# re-add transactions
for transact_data in (
i for i in fixture_json if i["model"] == "vinywaji_core.transaction"
):
user = openid_models.OpenidUser.objects.get(
sub=transact_data["fields"]["user"]
).user
for transact_data in (i for i in fixture_json if i["model"] == "vinywaji_core.transaction"):
user = openid_models.OpenidUser.objects.get(sub=transact_data["fields"]["user"]).user
parsed_datetime = timezone.datetime.strptime(
transact_data["fields"]["time"], "%Y-%m-%dT%H:%M:%S.%fZ"
)
Expand Down
32 changes: 8 additions & 24 deletions src/vinywaji/core/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ class Migration(migrations.Migration):
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
models.DateTimeField(blank=True, null=True, verbose_name="last login"),
),
(
"is_superuser",
Expand All @@ -48,35 +46,25 @@ class Migration(migrations.Migration):
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
models.CharField(blank=True, max_length=150, verbose_name="first name"),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
models.CharField(blank=True, max_length=150, verbose_name="last name"),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
models.EmailField(blank=True, max_length=254, verbose_name="email address"),
),
(
"is_staff",
Expand All @@ -96,9 +84,7 @@ class Migration(migrations.Migration):
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"),
),
(
"groups",
Expand Down Expand Up @@ -160,9 +146,7 @@ class Migration(migrations.Migration):
),
(
"time",
models.DateTimeField(
auto_now_add=True, help_text="When this transaction occurred"
),
models.DateTimeField(auto_now_add=True, help_text="When this transaction occurred"),
),
(
"user",
Expand Down
74 changes: 74 additions & 0 deletions src/vinywaji/core/migrations/0002_webhookconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Generated by Django 5.0.6 on 2024-07-09 14:52

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

import vinywaji.core.models


class Migration(migrations.Migration):

dependencies = [
("vinywaji_core", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="WebhookConfig",
fields=[
(
"id",
models.UUIDField(
default=vinywaji.core.models.uuid_default,
help_text="The ID of this webhook",
primary_key=True,
serialize=False,
),
),
(
"description",
models.CharField(
blank=True,
default="",
help_text="A free-form description which the user can give this webhook",
max_length=128,
),
),
(
"transaction_description",
models.CharField(
blank=True,
default="",
help_text="The description that will be added to the transaction when this webhook is triggered",
max_length=30,
),
),
(
"trigger_key",
models.CharField(
default=vinywaji.core.models.webhook_trigger_default,
editable=False,
help_text="The key required to trigger this webhook",
max_length=64,
),
),
(
"amount",
models.IntegerField(
help_text="How much money the triggered transaction records in euro-cent. Negative amounts represent purchases while positive amounts represent deposits."
),
),
(
"user",
models.ForeignKey(
editable=False,
help_text="The user who configured this webhook and who is impacted when it is called",
on_delete=django.db.models.deletion.CASCADE,
related_name="webhooks",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
Loading

0 comments on commit e0ad05a

Please sign in to comment.