Update webhook page
This commit is contained in:
@ -21,9 +21,9 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- id: name-tests-test
|
||||
args: [--pytest-test-first]
|
||||
args: [ --pytest-test-first ]
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
args: [ --markdown-linebreak-ext=md ]
|
||||
exclude_types:
|
||||
- "html"
|
||||
|
||||
@ -32,22 +32,22 @@ repos:
|
||||
rev: "1.19.0"
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version, "5.1"]
|
||||
args: [ --target-version, "5.1" ]
|
||||
|
||||
# Run Pyupgrade on all Python files. This will upgrade the code to Python 3.12.
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.16.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ["--py312-plus"]
|
||||
args: [ "--py312-plus" ]
|
||||
|
||||
# An extremely fast Python linter and formatter.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.5.0
|
||||
rev: v0.5.1
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
args: ["--fix", "--exit-non-zero-on-fix"]
|
||||
args: [ "--fix", "--exit-non-zero-on-fix" ]
|
||||
|
||||
# Static checker for GitHub Actions workflow files.
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
|
52
.vscode/launch.json
vendored
52
.vscode/launch.json
vendored
@ -1,28 +1,28 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Django: Runserver",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/manage.py",
|
||||
"args": [
|
||||
"runserver",
|
||||
"--nothreading"
|
||||
],
|
||||
"django": true,
|
||||
"justMyCode": true
|
||||
},
|
||||
{
|
||||
"name": "python manage.py scrape_twitch",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/manage.py",
|
||||
"args": [
|
||||
"scrape_twitch"
|
||||
],
|
||||
"django": true,
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Django: Runserver",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/manage.py",
|
||||
"args": [
|
||||
"runserver",
|
||||
"--nothreading"
|
||||
],
|
||||
"django": true,
|
||||
"justMyCode": true
|
||||
},
|
||||
{
|
||||
"name": "python manage.py scrape_twitch",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/manage.py",
|
||||
"args": [
|
||||
"scrape_twitch"
|
||||
],
|
||||
"django": true,
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
30
.vscode/settings.json
vendored
30
.vscode/settings.json
vendored
@ -1,17 +1,17 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"allauth",
|
||||
"appendonly",
|
||||
"asgiref",
|
||||
"forloop",
|
||||
"logdir",
|
||||
"memlock",
|
||||
"networkidle",
|
||||
"PGID",
|
||||
"PUID",
|
||||
"requirepass",
|
||||
"socialaccount",
|
||||
"ttvdrops",
|
||||
"ulimits"
|
||||
]
|
||||
"cSpell.words": [
|
||||
"allauth",
|
||||
"appendonly",
|
||||
"asgiref",
|
||||
"forloop",
|
||||
"logdir",
|
||||
"memlock",
|
||||
"networkidle",
|
||||
"PGID",
|
||||
"PUID",
|
||||
"requirepass",
|
||||
"socialaccount",
|
||||
"ttvdrops",
|
||||
"ulimits"
|
||||
]
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ from platformdirs import user_data_dir
|
||||
|
||||
load_dotenv(dotenv_path=find_dotenv(), verbose=True)
|
||||
|
||||
|
||||
DATA_DIR = Path(
|
||||
user_data_dir(
|
||||
appname="TTVDrops",
|
||||
@ -28,7 +27,6 @@ sentry_sdk.init(
|
||||
profiles_sample_rate=0.2,
|
||||
)
|
||||
|
||||
|
||||
BASE_DIR: Path = Path(__file__).resolve().parent.parent
|
||||
ADMINS: list[tuple[str, str]] = [("Joakim Hellsén", "tlovinator@gmail.com")]
|
||||
WSGI_APPLICATION = "config.wsgi.application"
|
||||
@ -89,7 +87,6 @@ MIDDLEWARE: list[str] = [
|
||||
"simple_history.middleware.HistoryRequestMiddleware",
|
||||
]
|
||||
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
|
@ -107,16 +107,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<script type="module">
|
||||
const scrollSpy = new bootstrap.ScrollSpy(document.body, {
|
||||
target: '.toc'
|
||||
})
|
||||
const scrollSpy = new bootstrap.ScrollSpy(document.body, {
|
||||
target: '.toc'
|
||||
})
|
||||
|
||||
document.body.addEventListener('activate.bs.scrollspy', function (event) {
|
||||
const activeItem = document.querySelector('.toc .active');
|
||||
if (activeItem) {
|
||||
activeItem.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
|
||||
}
|
||||
});
|
||||
document.body.addEventListener('activate.bs.scrollspy', function (event) {
|
||||
const activeItem = document.querySelector('.toc .active');
|
||||
if (activeItem) {
|
||||
activeItem.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
{% endblock content %}
|
||||
|
@ -2,22 +2,27 @@
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1 class="my-4">Add Discord Webhook</h1>
|
||||
<form method="post" class="needs-validation" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
<div class="mb-3">
|
||||
{{ form.webhook_url.errors }}
|
||||
<label for="{{ form.webhook_url.id_for_label }}" class="form-label">{{ form.webhook_url.label }}</label>
|
||||
<input type="url"
|
||||
name="webhook_url"
|
||||
required=""
|
||||
class="form-control"
|
||||
aria-describedby="id_webhook_url_helptext"
|
||||
id="id_webhook_url">
|
||||
<div class="form-text text-muted">{{ form.webhook_url.help_text }}</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add Webhook</button>
|
||||
</form>
|
||||
<div class="card card-body mb-3">
|
||||
Webhooks will be saved in a cookie and will be sent to the server when you subscribe to a drop.
|
||||
</div>
|
||||
<div>
|
||||
<form method="post" class="needs-validation" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
<div class="mb-3">
|
||||
{{ form.webhook_url.errors }}
|
||||
<label for="{{ form.webhook_url.id_for_label }}" class="form-label">{{ form.webhook_url.label }}</label>
|
||||
<input type="url"
|
||||
name="webhook_url"
|
||||
required=""
|
||||
class="form-control"
|
||||
aria-describedby="id_webhook_url_helptext"
|
||||
id="id_webhook_url">
|
||||
<div class="form-text text-muted">{{ form.webhook_url.help_text }}</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add Webhook</button>
|
||||
</form>
|
||||
</div>
|
||||
<h2 class="mt-5">Webhooks</h2>
|
||||
{% if webhooks %}
|
||||
<ul class="list-group mt-3">
|
||||
|
@ -8,13 +8,13 @@ if TYPE_CHECKING:
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def factory() -> RequestFactory:
|
||||
"""Factory for creating requests."""
|
||||
return RequestFactory()
|
||||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
@pytest.mark.django_db
|
||||
def test_index_view(client: Client) -> None:
|
||||
"""Test index view."""
|
||||
url: str = reverse(viewname="core:index")
|
||||
|
@ -6,7 +6,6 @@ from . import views
|
||||
|
||||
app_name: str = "core"
|
||||
|
||||
|
||||
urlpatterns: list[URLPattern | URLResolver] = [
|
||||
path(route="", view=views.index, name="index"),
|
||||
path(
|
||||
|
@ -11,7 +11,6 @@ from django.contrib import messages
|
||||
from django.http.response import HttpResponse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views.generic import FormView, ListView
|
||||
from httpx._models import Response
|
||||
|
||||
from twitch_app.models import (
|
||||
DropBenefit,
|
||||
@ -33,7 +32,6 @@ if TYPE_CHECKING:
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
cache_dir: Path = settings.DATA_DIR / "cache"
|
||||
cache_dir.mkdir(exist_ok=True, parents=True)
|
||||
storage = hishel.FileStorage(base_path=cache_dir)
|
||||
@ -284,7 +282,7 @@ class WebhooksView(FormView):
|
||||
|
||||
webhooks.append(webhook)
|
||||
response: HttpResponse = self.render_to_response(self.get_context_data(form=form))
|
||||
response.set_cookie(key="webhooks", value=",".join(webhooks), max_age=60 * 60 * 24 * 365)
|
||||
response.set_cookie(key="webhooks", value=",".join(webhooks), max_age=315360000) # 10 years
|
||||
|
||||
messages.success(self.request, "Webhook successfully added.")
|
||||
return response
|
||||
|
@ -6,7 +6,7 @@ services:
|
||||
restart: always
|
||||
ulimits:
|
||||
memlock: -1
|
||||
command: ["--auth", "Password", "--password", "${GARNET_PASSWORD}", "--storage-tier", "--logdir", "/logs", "--aof", "--port", "6380"]
|
||||
command: [ "--auth", "Password", "--password", "${GARNET_PASSWORD}", "--storage-tier", "--logdir", "/logs", "--aof", "--port", "6380" ]
|
||||
ports:
|
||||
- "6380:6380"
|
||||
volumes:
|
||||
|
@ -8,36 +8,36 @@ lint.select = ["ALL"]
|
||||
line-length = 119
|
||||
lint.pydocstyle.convention = "google"
|
||||
lint.ignore = [
|
||||
"CPY001", # Missing copyright notice at top of file
|
||||
"D100", # Checks for undocumented public module definitions.
|
||||
"D101", # Checks for undocumented public class definitions.
|
||||
"D102", # Checks for undocumented public method definitions.
|
||||
"D104", # Missing docstring in public package.
|
||||
"D105", # Missing docstring in magic method.
|
||||
"D106", # Checks for undocumented public class definitions, for nested classes.
|
||||
"ERA001", # Found commented-out code
|
||||
"FIX002", # Line contains TODO
|
||||
"COM812", # Checks for the absence of trailing commas.
|
||||
"ISC001", # Checks for implicitly concatenated strings on a single line.
|
||||
"DJ001", # Checks nullable string-based fields (like CharField and TextField) in Django models.
|
||||
"CPY001", # Missing copyright notice at top of file
|
||||
"D100", # Checks for undocumented public module definitions.
|
||||
"D101", # Checks for undocumented public class definitions.
|
||||
"D102", # Checks for undocumented public method definitions.
|
||||
"D104", # Missing docstring in public package.
|
||||
"D105", # Missing docstring in magic method.
|
||||
"D106", # Checks for undocumented public class definitions, for nested classes.
|
||||
"ERA001", # Found commented-out code
|
||||
"FIX002", # Line contains TODO
|
||||
"COM812", # Checks for the absence of trailing commas.
|
||||
"ISC001", # Checks for implicitly concatenated strings on a single line.
|
||||
"DJ001", # Checks nullable string-based fields (like CharField and TextField) in Django models.
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"**/tests/**" = [
|
||||
"ARG", # Unused function args -> fixtures nevertheless are functionally relevant...
|
||||
"FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize()
|
||||
"PLR2004", # Magic value used in comparison, ...
|
||||
"S101", # asserts allowed in tests...
|
||||
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
"ARG", # Unused function args -> fixtures nevertheless are functionally relevant...
|
||||
"FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize()
|
||||
"PLR2004", # Magic value used in comparison, ...
|
||||
"S101", # asserts allowed in tests...
|
||||
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
|
||||
]
|
||||
"**/migrations/**" = [
|
||||
"RUF012", # Checks for mutable default values in class attributes.
|
||||
"RUF012", # Checks for mutable default values in class attributes.
|
||||
]
|
||||
|
||||
[tool.djlint]
|
||||
profile = "django"
|
||||
format_attribute_template_tags = true
|
||||
ignore="H006" # Img tag should have height and width attributes.
|
||||
ignore = "H006" # Img tag should have height and width attributes.
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "config.settings"
|
||||
|
@ -1,5 +1,6 @@
|
||||
djlint
|
||||
pip
|
||||
pre-commit
|
||||
ruff
|
||||
pytest
|
||||
pytest-django
|
||||
ruff
|
||||
|
@ -8,7 +8,6 @@ httpx
|
||||
pillow
|
||||
platformdirs
|
||||
playwright
|
||||
psycopg[binary]
|
||||
python-dotenv
|
||||
sentry-sdk[django]
|
||||
whitenoise[brotli]
|
||||
|
@ -86,9 +86,7 @@ class DropCampaignSchema(Schema):
|
||||
|
||||
# http://localhost:8000/api/twitch/organizations
|
||||
@router.get("/organizations", response=list[OrganizationSchema])
|
||||
def get_organizations(
|
||||
request: HttpRequest, # noqa: ARG001
|
||||
) -> BaseManager[Organization]:
|
||||
def get_organizations(request: HttpRequest) -> BaseManager[Organization]: # noqa: ARG001
|
||||
"""Get all organizations."""
|
||||
return Organization.objects.all()
|
||||
|
||||
@ -109,17 +107,13 @@ def get_drop_benefits(request: HttpRequest) -> BaseManager[DropBenefit]: # noqa
|
||||
|
||||
# http://localhost:8000/api/twitch/drop_campaigns
|
||||
@router.get("/drop_campaigns", response=list[DropCampaignSchema])
|
||||
def get_drop_campaigns(
|
||||
request: HttpRequest, # noqa: ARG001
|
||||
) -> BaseManager[DropCampaign]:
|
||||
def get_drop_campaigns(request: HttpRequest) -> BaseManager[DropCampaign]: # noqa: ARG001
|
||||
"""Get all drop campaigns."""
|
||||
return DropCampaign.objects.all()
|
||||
|
||||
|
||||
# http://localhost:8000/api/twitch/time_based_drops
|
||||
@router.get("/time_based_drops", response=list[TimeBasedDropSchema])
|
||||
def get_time_based_drops(
|
||||
request: HttpRequest, # noqa: ARG001
|
||||
) -> BaseManager[TimeBasedDrop]:
|
||||
def get_time_based_drops(request: HttpRequest) -> BaseManager[TimeBasedDrop]: # noqa: ARG001
|
||||
"""Get all time-based drops."""
|
||||
return TimeBasedDrop.objects.all()
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Literal
|
||||
|
||||
import auto_prefetch
|
||||
from django.db import models
|
||||
from django.db.models import Value
|
||||
@ -19,9 +21,9 @@ class Organization(auto_prefetch.Model):
|
||||
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
|
||||
|
||||
class Meta(auto_prefetch.Model.Meta):
|
||||
verbose_name = "Organization"
|
||||
verbose_name_plural = "Organizations"
|
||||
ordering = ("name",)
|
||||
verbose_name: str = "Organization"
|
||||
verbose_name_plural: str = "Organizations"
|
||||
ordering: tuple[Literal["name"]] = ("name",)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name or self.id
|
||||
@ -55,9 +57,9 @@ class Game(auto_prefetch.Model):
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta(auto_prefetch.Model.Meta):
|
||||
verbose_name = "Game"
|
||||
verbose_name_plural = "Games"
|
||||
ordering = ("display_name",)
|
||||
verbose_name: str = "Game"
|
||||
verbose_name_plural: str = "Games"
|
||||
ordering: tuple[Literal["display_name"]] = ("display_name",)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.display_name or self.slug or self.id
|
||||
@ -83,9 +85,9 @@ class DropBenefit(auto_prefetch.Model):
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta(auto_prefetch.Model.Meta):
|
||||
verbose_name = "Drop Benefit"
|
||||
verbose_name_plural = "Drop Benefits"
|
||||
ordering = ("name",)
|
||||
verbose_name: str = "Drop Benefit"
|
||||
verbose_name_plural: str = "Drop Benefits"
|
||||
ordering: tuple[Literal["name"]] = ("name",)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.owner_organization.name} - {self.game.display_name} - {self.name}"
|
||||
@ -106,9 +108,9 @@ class TimeBasedDrop(auto_prefetch.Model):
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta(auto_prefetch.Model.Meta):
|
||||
verbose_name = "Time-Based Drop"
|
||||
verbose_name_plural = "Time-Based Drops"
|
||||
ordering = ("name",)
|
||||
verbose_name: str = "Time-Based Drop"
|
||||
verbose_name_plural: str = "Time-Based Drops"
|
||||
ordering: tuple[Literal["name"]] = ("name",)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.benefits.first()} - {self.name}"
|
||||
@ -145,9 +147,9 @@ class DropCampaign(auto_prefetch.Model):
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta(auto_prefetch.Model.Meta):
|
||||
verbose_name = "Drop Campaign"
|
||||
verbose_name_plural = "Drop Campaigns"
|
||||
ordering = ("name",)
|
||||
verbose_name: str = "Drop Campaign"
|
||||
verbose_name_plural: str = "Drop Campaigns"
|
||||
ordering: tuple[Literal["name"]] = ("name",)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.owner.name} - {self.game.display_name} - {self.name}"
|
||||
|
Reference in New Issue
Block a user