import logging
from apscheduler.schedulers.background import BackgroundScheduler
from django.apps import AppConfig
from django.db import models, connection
from rest_framework import serializers, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny, BasePermission, IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
class Role:
User = 1 << 0
Admin = 1 << 1
class HasRole(BasePermission):
def __init__(self, role: int):
self.role = role
def has_permission(self, request: Request, view) -> bool:
roles = getattr(request.user, "role_mask", 0)
return bool(roles & self.role)
def require(role: int):
return [IsAuthenticated, HasRole(role)]
class Note(models.Model):
owner = models.CharField(max_length=255)
title = models.CharField(max_length=255)
body = models.TextField()
class Meta:
db_table = "notes"
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ["id", "owner", "title", "body"]
class NoteInputSerializer(serializers.Serializer):
title = serializers.CharField()
body = serializers.CharField()
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
@api_view(["POST"])
@permission_classes([AllowAny])
def login(request: Request) -> Response:
s = LoginSerializer(data=request.data)
s.is_valid(raise_exception=True)
if s.validated_data["username"] != "alice" or s.validated_data["password"] != "secret":
return Response({"detail": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)
user = request.user.__class__(username=s.validated_data["username"])
user.role_mask = Role.User
refresh = RefreshToken.for_user(user)
resp = Response()
resp.set_cookie("access_token", str(refresh.access_token), httponly=True, samesite="Lax")
resp.set_cookie("refresh_token", str(refresh), httponly=True, samesite="Strict")
return resp
class NoteListCreate(APIView):
permission_classes = require(Role.User)
def get(self, request: Request) -> Response:
notes = Note.objects.filter(owner=request.user.username)
return Response(NoteSerializer(notes, many=True).data)
def post(self, request: Request) -> Response:
s = NoteInputSerializer(data=request.data)
s.is_valid(raise_exception=True)
note = Note.objects.create(owner=request.user.username, **s.validated_data)
return Response(NoteSerializer(note).data, status=status.HTTP_201_CREATED)
class PurgeNotes(APIView):
permission_classes = require(Role.Admin)
def delete(self, request: Request) -> Response:
deleted, _ = Note.objects.all().delete()
return Response({"deleted": deleted})
def nightly_prune():
logging.info("nightly prune fired", extra={"triggered_by": "cron"})
class NotesConfig(AppConfig):
name = "notes"
def ready(self):
scheduler = BackgroundScheduler()
scheduler.add_job(nightly_prune, "cron", hour=0, minute=0, second=0)
scheduler.start()