send message api

This commit is contained in:
Sayyid Hamid Mahdavi 2025-07-15 13:59:42 +03:30
parent da9e640440
commit 40d3579d13
17 changed files with 229 additions and 148 deletions

View file

@ -1,5 +0,0 @@
from django.contrib import admin
from .models import PushUser
admin.site.register(PushUser)

View file

@ -1,32 +0,0 @@
# Generated by Django 5.2.3 on 2025-07-01 08:37
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='PushUser',
fields=[
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
('client_token', models.CharField(blank=True, max_length=32, null=True)),
('application_token', models.CharField(blank=True, max_length=32, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View file

@ -1,13 +0,0 @@
from rest_framework import serializers
from .models import PushUser
class PushSerializer(serializers.ModelSerializer):
class Meta:
model = PushUser
fields = (
"user",
"client_token",
"application_token"
)

View file

@ -1,38 +0,0 @@
from django.shortcuts import render
from django.http import Http404
from django.shortcuts import render
from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements
from rest_framework import viewsets, mixins
from rest_framework.generics import get_object_or_404
from rest_framework.viewsets import GenericViewSet
from .models import PushUser
from .serializers import PushSerializer
from apps.gooyal_oauth2.models import Application
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
from apps.gooyal_oauth2.utils import get_application
class PushUserViewSet(mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin,
# mixins.DestroyModelMixin,
GenericViewSet):
queryset = PushUser.objects.all()
serializer_class = PushSerializer
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
required_alternate_scopes = {
"GET": [["notifications.push:get_client_token"]],
}
def get_object(self):
user = self.request.user
auth = self.request.auth
if user:
# plan = Plan.objects.filter(application_id=auth.application_id).first()
push_user = PushUser.objects.submit(user)
return push_user
raise Http404

View file

@ -0,0 +1,6 @@
from django.contrib import admin
from .models import PushUser, PushMessage
admin.site.register(PushUser)
admin.site.register(PushMessage)

View file

@ -0,0 +1,59 @@
from rest_framework import routers
from rest_framework.routers import DefaultRouter, DynamicRoute, Route
from . import views
from django.urls import NoReverseMatch, path, re_path
app_name = 'push_notifications'
class ApplicationRouter(DefaultRouter):
routes = [
# List route.
Route(
url=r'^{prefix}/<user_uuid>{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create'
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
# Dynamically generated list routes. Generated using
# @action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/<user_uuid>/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
# # Detail route.
Route(
url=r'^{prefix}/<user_uuid>/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# # Dynamically generated detail routes. Generated using
# # @action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/<user_uuid>/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
]
application_router = DefaultRouter()
application_router.register('application', views.ApplicationPushUserViewSet, basename='application')
urlpatterns = application_router.urls

View file

@ -3,4 +3,4 @@ from django.apps import AppConfig
class NotificationsConfig(AppConfig): class NotificationsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.notifications' name = 'apps.push_notifications'

View file

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from oauth2_provider.settings import oauth2_settings
from apps.users.models import User from apps.users.models import User
from utils.models import BaseModel from utils.models import BaseModel
@ -15,9 +16,11 @@ class PushUserQuerySet(models.QuerySet):
return push_user return push_user
class PushUser(BaseModel): class PushUser(BaseModel):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.PROTECT)
client_token = models.CharField(max_length=32, null=True, blank=True) client_token = models.CharField(max_length=32, null=True, blank=True)
application_token = models.CharField(max_length=32, null=True, blank=True) application_token = models.CharField(max_length=32, null=True, blank=True)
app_id = None
objects = PushUserQuerySet.as_manager() objects = PushUserQuerySet.as_manager()
@ -39,3 +42,17 @@ class PushUser(BaseModel):
def send_push(self): def send_push(self):
return return
class PushMessage(BaseModel):
push_user = models.ForeignKey(PushUser, on_delete=models.PROTECT)
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
related_name='+', null=True, blank=True)
title = models.CharField(max_length=128)
message = models.TextField()
priority = models.IntegerField(default=5)
extras = models.JSONField(default=dict)
def send_push(self):
gotify.send_notif(token=self.push_user.application_token ,title=self.title, message=self.message, priority=self.priority, extras=self.extras)

View file

@ -0,0 +1,26 @@
from rest_framework import serializers
from .models import PushUser, PushMessage
class PushUserSerializer(serializers.ModelSerializer):
class Meta:
model = PushUser
fields = (
"user",
"client_token",
"application_token"
)
class PushMessageSerializer(serializers.ModelSerializer):
class Meta:
model = PushMessage
fields = (
"push_user",
"application",
"title",
"message",
"priority",
"extras"
)
read_only_fields = ("push_user","application")

View file

@ -1,9 +1,12 @@
from rest_framework import routers from rest_framework import routers
from rest_framework.routers import DefaultRouter, DynamicRoute, Route from rest_framework.routers import DefaultRouter, DynamicRoute, Route
from . import views from . import views
from django.urls import NoReverseMatch, path, re_path
class CRUDRouter(DefaultRouter): app_name = 'push_notifications'
class PushUserRouter(DefaultRouter):
routes = [ routes = [
# List route. # List route.
Route( Route(
@ -47,11 +50,8 @@ class CRUDRouter(DefaultRouter):
), ),
] ]
user_router = PushUserRouter()
user_router.register('push_user', views.PushUserViewSet, basename='user-data')
router = CRUDRouter()
app_name = 'notifications' urlpatterns = user_router.urls
router.register('push_user', views.PushUserViewSet, basename='user-data')
urlpatterns = router.urls

View file

@ -0,0 +1,69 @@
from django.shortcuts import render
from django.http import Http404
from django.shortcuts import render
from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements
from rest_framework import viewsets, mixins
from rest_framework.generics import get_object_or_404
from rest_framework.viewsets import GenericViewSet
from .models import PushUser, PushMessage
from .serializers import PushUserSerializer, PushMessageSerializer
from apps.gooyal_oauth2.models import Application
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
from apps.gooyal_oauth2.utils import get_application
from ..users.models import User
class PushUserViewSet(mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin,
# mixins.DestroyModelMixin,
GenericViewSet):
queryset = PushUser.objects.all()
serializer_class = PushUserSerializer
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
required_alternate_scopes = {
"GET": [["notifications.push:get_client_token"]],
}
def get_object(self):
user = self.request.user
auth = self.request.auth
if user:
# plan = Plan.objects.filter(application_id=auth.application_id).first()
push_user = PushUser.objects.submit(user)
return push_user
raise Http404
class ApplicationPushUserViewSet(
# mixins.RetrieveModelMixin,
mixins.ListModelMixin,
# mixins.UpdateModelMixin,
# mixins.DestroyModelMixin,
mixins.CreateModelMixin,
GenericViewSet
):
queryset = PushMessage.objects.all()
serializer_class = PushMessageSerializer
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
required_alternate_scopes = {
"GET": [["notifications.application.push:submit_message"]],
"POST": [["notifications.application.push:submit_message"]],
}
def get_object(self):
queryset = self.get_queryset()
user_uuid = self.kwargs.get('user_uuid')
push_message = get_object_or_404(queryset, user=user_uuid)
return push_message
def perform_create(self, serializer):
user_uuid = self.kwargs.get('user_uuid')
user, _ = User.objects.get_or_create(uuid=user_uuid)
push_user = PushUser.objects.submit(user)
application = get_application(self.request)
instance = serializer.save(push_user=push_user, application=application)
instance.send_push()

View file

@ -54,7 +54,7 @@ INSTALLED_APPS = [
'apps.core', 'apps.core',
'apps.users', 'apps.users',
'apps.gooyal_oauth2', 'apps.gooyal_oauth2',
'apps.notifications', 'apps.push_notifications',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -241,37 +241,37 @@ CACHES = {
} }
} }
LOGGING = { # LOGGING = {
'version': 1, # 'version': 1,
'disable_existing_loggers': False, # 'disable_existing_loggers': False,
'formatters': { # 'formatters': {
'verbose': { # 'verbose': {
'format': '{levelname} AT: {asctime} LINE: {lineno} LOGGER: {name} FILE: {pathname} MESSAGE: {message}', # 'format': '{levelname} AT: {asctime} LINE: {lineno} LOGGER: {name} FILE: {pathname} MESSAGE: {message}',
'style': '{', # 'style': '{',
}, # },
'simple': { # 'simple': {
'format': '{levelname} {message}', # 'format': '{levelname} {message}',
'style': '{', # 'style': '{',
}, # },
}, # },
'handlers': { # 'handlers': {
'file': { # 'file': {
'level': 'DEBUG', # 'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler', # 'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': 'logs/app.log', # 'filename': 'logs/app.log',
'formatter': 'verbose', # 'formatter': 'verbose',
"when": 'D', # "when": 'D',
"backupCount": 5, # "backupCount": 5,
}, # },
}, # },
'loggers': { # 'loggers': {
'root': { # 'root': {
'handlers': ['file'], # 'handlers': ['file'],
'level': 'DEBUG', # 'level': 'DEBUG',
'propagate': True, # 'propagate': True,
}, # },
}, # },
} # }
# from datetime import timedelta # from datetime import timedelta
# from typing import List, Tuple # from typing import List, Tuple

View file

@ -35,7 +35,8 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('', include('apps.core.urls')), path('', include('apps.core.urls')),
path('', include('apps.notifications.urls')), path('push/user/', include('apps.push_notifications.user_urls')),
path('push/application/<user_uuid>/', include('apps.push_notifications.application_urls')),
# path('users/', include('apps.users.urls')), # path('users/', include('apps.users.urls')),
# path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')), # path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')),

View file

@ -1,4 +1,5 @@
import time import time
from django.utils import timezone
from typing import Any from typing import Any
import httpx import httpx
@ -14,6 +15,7 @@ from gotify_rest_api_client.api.message import create_message
from gotify_rest_api_client.api.user import create_user from gotify_rest_api_client.api.user import create_user
from gotify_rest_api_client.api.client import create_client from gotify_rest_api_client.api.client import create_client
from gotify_rest_api_client.api.application import create_app from gotify_rest_api_client.api.application import create_app
from gotify_rest_api_client.types import Unset
ADMIN_CLIENT_TOKEN = 'CbWdsJrmRTDzbuD' ADMIN_CLIENT_TOKEN = 'CbWdsJrmRTDzbuD'
@ -62,30 +64,19 @@ def get_basic_client(username, password):
return client return client
def send_notif(data): def send_notif(token, title=None, message=None, priority=None, extras=None):
class Tmp(): extras = extras or Unset()
def to_dict(self): final_data = MessageExternalModel(appid=1, date=timezone.now(), id=1, title=title, message=message, priority=priority, extras=extras)
return data client = get_client(access_token=token)
final_data = Tmp()
client = get_client()
response: Any = create_message.sync(client=client, body=final_data) response: Any = create_message.sync(client=client, body=final_data)
return response return response
def submit_user(user_uuid): def submit_user(user_uuid):
data_dict = { final_data = CreateUserExternalModel(admin=False,
"admin": False, name=user_uuid,
"name": user_uuid, pass_=user_uuid)
"pass": user_uuid
}
class Tmp():
def to_dict(self):
return data_dict
final_data = Tmp()
client = get_client(ADMIN_CLIENT_TOKEN) client = get_client(ADMIN_CLIENT_TOKEN)
response: UserExternalModel = create_user.sync(client=client, body=final_data) response: UserExternalModel = create_user.sync(client=client, body=final_data)