Initial commit
This commit is contained in:
commit
5cda7bc309
28 changed files with 1173 additions and 0 deletions
64
yksa_kit/models.py
Normal file
64
yksa_kit/models.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def _generate_token() -> str:
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
class TokenKind(models.TextChoices):
|
||||
READ = "read", _("Read (non-public visibility)")
|
||||
INGEST = "ingest", _("Ingest (push)")
|
||||
|
||||
|
||||
class ApiToken(models.Model):
|
||||
"""Static API token for machine clients. Not tied to a user, no scopes.
|
||||
|
||||
Read tokens extend visibility to non-public records; ingest tokens may also
|
||||
push data (ground-station frames in tdas, station logs in ops). A service
|
||||
that has nothing to ingest simply never issues the second kind.
|
||||
"""
|
||||
|
||||
name = models.CharField(
|
||||
max_length=64,
|
||||
unique=True,
|
||||
help_text=_("Human-readable label, e.g. 'TMTC pipeline' or 'ground station 1'."),
|
||||
)
|
||||
token = models.CharField(
|
||||
max_length=128,
|
||||
unique=True,
|
||||
default=_generate_token,
|
||||
editable=False,
|
||||
db_index=True,
|
||||
help_text=_("Bearer credential. Treat as a secret."),
|
||||
)
|
||||
kind = models.CharField(
|
||||
max_length=16,
|
||||
choices=TokenKind.choices,
|
||||
default=TokenKind.READ,
|
||||
help_text=_("Read tokens extend visibility; ingest tokens may also push data."),
|
||||
)
|
||||
is_active = models.BooleanField(default=True, db_index=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
last_used_at = models.DateTimeField(null=True, blank=True)
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
verbose_name = _("API token")
|
||||
verbose_name_plural = _("API tokens")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def mark_used(self) -> None:
|
||||
# Written on every authenticated request, so it must not touch the rest
|
||||
# of the row: a full save() here would clobber a concurrent admin edit.
|
||||
self.last_used_at = timezone.now()
|
||||
type(self).objects.filter(pk=self.pk).update(last_used_at=self.last_used_at)
|
||||
Loading…
Add table
Add a link
Reference in a new issue