Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: transaction webhooks #9

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
266f0aa
add model for webhook configuration
ftsell Jul 9, 2024
bb10ba4
wire up some basic webhook triggering and a profile page
ftsell Jul 9, 2024
2edb00b
remove dependencies from cloudflare and google (!)
kritzl Jul 11, 2024
2fd818b
redesign templates using tailwind css
kritzl Jul 11, 2024
94d35f1
redesign templates using tailwind css
kritzl Jul 11, 2024
370a925
Merge remote-tracking branch 'origin/feature/redesign' into feature/r…
kritzl Jul 11, 2024
bff7240
fix formatting
kritzl Jul 11, 2024
93cb8fc
fix: include browserReload when in DEBUG mode
kritzl Jul 11, 2024
3bd0248
add tailwind to deployment process
kritzl Jul 11, 2024
129bad9
style profile page
kritzl Jul 11, 2024
c9b5b01
style login page
kritzl Jul 11, 2024
364eecc
style login page (light mode)
kritzl Jul 11, 2024
995540b
remove unused pipfile
kritzl Jul 11, 2024
e40e5b3
update styles.css
kritzl Jul 11, 2024
e3a95e1
add missing npm
kritzl Jul 11, 2024
a31c368
update logo and footer styles
kritzl Jul 11, 2024
c6ff6d6
Merge pull request #7 from fsinfuhh/feature/redesign
kritzl Jul 11, 2024
4dfd552
add development infos to README.md
kritzl Jul 11, 2024
7fca44a
remove default value for deposit
kritzl Jul 11, 2024
6b66b9b
add interface to manage webhooks; call webhook
kritzl Jul 11, 2024
769b023
add status codes
kritzl Jul 11, 2024
aefe303
clean up PR
kritzl Jul 12, 2024
5c7087a
replace "VW_MAFIASI_COLORS" with an option "VW_THEME_COLOR" to set a …
kritzl Jul 12, 2024
230f6eb
update theme
kritzl Jul 12, 2024
63fad2e
fix status code (upsi whoopsie)
kritzl Jul 12, 2024
7df8ee1
add theme mode switch
kritzl Jul 12, 2024
83a4952
improve information shown to user
kritzl Jul 12, 2024
9606de3
add icons
kritzl Jul 12, 2024
7427020
fix typo
kritzl Jul 16, 2024
40baa18
Update startup commands
kritzl Jul 16, 2024
bbeb13d
update style source
kritzl Jul 16, 2024
253822a
fix duplicate id
kritzl Jul 16, 2024
3b4c6cf
move scripts
kritzl Jul 16, 2024
1f961c4
add description on how to trigger webhooks
ftsell Jul 16, 2024
0b82eeb
add positive and negative value description to webhook form
ftsell Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading