Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions addon/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.contrib import admin

from addon.models import Post


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
pass
3 changes: 3 additions & 0 deletions addon/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@

class Post(models.Model):
token = models.CharField(max_length=50)

def __str__(self):
return self.token
1 change: 1 addition & 0 deletions boilerplate/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from django.urls import include, path

urlpatterns = [
path("admin/", admin.site.urls),
path("addon/", include("addon.urls")),
path("chat/", include("chat.urls")),
path("oauth/", include("oauth.urls")),
Expand Down
7 changes: 7 additions & 0 deletions chat/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.contrib import admin
from chat.models import Chat


@admin.register(Chat)
class ChatAdmin(admin.ModelAdmin):
pass
75 changes: 75 additions & 0 deletions chat/handler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,85 @@
import abc
from datetime import datetime
from enum import Enum
from typing import Optional

from pydantic import BaseModel

from chat.models import Chat


class EventType(str, Enum):
UNKNOWN = "UNKNOWN"
NEW_MESSAGE = "NEW_MESSAGE"
NEW_CHATBOT_MESSAGE = "NEW_CHATBOT_MESSAGE"


class MessageSenderSide(str, Enum):
SIDE_UNKNOWN = "SIDE_UNKNOWN"
BUYER = "BUYER"
SELLER = "SELLER"


class MessageSenderType(str, Enum):
TYPE_UNKNOWN = "TYPE_UNKNOWN"
HUMAN = "HUMAN"
BOT = "BOT"


class MessageSender(BaseModel):
side: MessageSenderSide = MessageSenderSide.SIDE_UNKNOWN
type: MessageSenderType = MessageSenderType.TYPE_UNKNOWN


class ConversationType(str, Enum):
UNKNOWN = "UNKNOWN"
POST = "POST"
BOT = "BOT"


class Conversation(BaseModel):
id: str
type: ConversationType
post_token: Optional[str] = None


class MessageType(str, Enum):
UNKNOWN = "UNKNOWN"
TEXT = "TEXT"


class Message(BaseModel):
id: str
conversation: Conversation
sender: MessageSender
type: MessageType
sent_at: datetime
text: Optional[str] = None


class Event(BaseModel):
type: EventType
new_message: Optional[Message] = None
new_chatbot_message: Optional[Message] = None
metadata: Optional[dict] = None


class EventHandler(BaseModel):
def handle_event(self, event: Event):
if event.type == EventType.NEW_MESSAGE:
self.handle_new_message(event)
elif event.type == EventType.NEW_CHATBOT_MESSAGE:
self.handle_new_chatbot_message(event)
else:
raise ValueError(f"Unknown event type: {event.type}")

def handle_new_message(self, event: Event):
raise NotImplemented

def handle_new_chatbot_message(self, event: Event):
raise NotImplemented


class StartChatSessionUser(BaseModel):
id: str

Expand Down
38 changes: 38 additions & 0 deletions chat/migrations/0002_chatbot_chatmessage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 5.0.6 on 2025-03-09 07:44

import django.db.models.deletion
import uuid
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chat', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='ChatBot',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('user_id', models.CharField(max_length=255, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='ChatMessage',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('sender_side', models.CharField(max_length=255)),
('sender_type', models.CharField(max_length=255)),
('type', models.CharField(max_length=255)),
('message', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('chat', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='chat.chat')),
('chat_bot', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bot_messages', to='chat.chatbot')),
],
),
]
21 changes: 21 additions & 0 deletions chat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,24 @@ class Chat(models.Model):

class Meta:
unique_together = ("post", "user_id", "peer_id")


class ChatBot(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user_id = models.CharField(max_length=255, unique=True, blank=False, null=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)


class ChatMessage(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
chat_bot = models.ForeignKey(ChatBot, on_delete=models.CASCADE, related_name="bot_messages", null=True)
chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name="messages", null=True)

sender_side = models.CharField(max_length=255)
sender_type = models.CharField(max_length=255)
type = models.CharField(max_length=255)
message = models.TextField()

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Binary file added chat/static/sample.pdf
Binary file not shown.
44 changes: 7 additions & 37 deletions chat/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from addon.models import Post
from boilerplate import settings
from boilerplate.clients import get_divar_kenar_client
from chat.handler import ChatNotificationHandler, Notification, StartChatSessionRequest
from chat.handler import *
from chat.models import Chat
from oauth.models import OAuth
from oauth.schemas import OAuthSession, OAuthSessionType
Expand Down Expand Up @@ -135,43 +135,13 @@ def chat_app(request):

@api_view(["POST"])
def receive_notify(request):
signed_authorization = request.headers.get("Authorization")
if not signed_authorization:
return JsonResponse(
{
"status": "403",
}
)

try:
chat_id = signer.unsign(signed_authorization)
except signing.BadSignature:
return JsonResponse(
{
"status": "403",
}
)

try:
chat = Chat.objects.get(id=chat_id)
except Chat.DoesNotExist:
return JsonResponse(
{
"status": "404",
}
)
auth_header = request.headers.get("Authorization")

try:
notification = Notification(**json.loads(request.body))
handler = ChatNotificationHandler(chat=chat)
handler.handle(notification)
except Exception as e:
logger.error(e)
return JsonResponse(
{
"status": "500",
}
)
if not auth_header or auth_header != settings.DIVAR_IDENTIFICATION_KEY:
return HttpResponseForbidden("Unauthorized access")
event = Event(**json.loads(request.body))
handler = EventHandler()
handler.handle_event(event)

return JsonResponse(
{
Expand Down
8 changes: 8 additions & 0 deletions oauth/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.contrib import admin

from oauth.models import OAuth


@admin.register(OAuth)
class OAuthAdmin(admin.ModelAdmin):
pass
3 changes: 3 additions & 0 deletions oauth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ class OAuth(models.Model):

def is_expired(self):
return now() >= self.expires_in

def __str__(self):
return self.session_id
16 changes: 5 additions & 11 deletions oauth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
from kenar.chatmessage import SetNotifyChatPostConversationsRequest
from kenar import RegisterEventSubscriptionRequest
from rest_framework.decorators import api_view

from addon.models import Post
Expand Down Expand Up @@ -71,18 +71,12 @@ def oauth_callback(request):
oauth.save()

logger.error(access_token_response.access_token)
logger.error(SetNotifyChatPostConversationsRequest(
post_token=chat.post.token,
endpoint=settings.APP_BASE_URL + reverse("receive_notify"),
identification_key=signer.sign(str(chat.id)),
),)

kenar_client.chat.set_notify_chat_post_conversations(
kenar_client.events.register_event_subscription(
access_token=access_token_response.access_token,
data=SetNotifyChatPostConversationsRequest(
post_token=chat.post.token,
endpoint=settings.APP_BASE_URL + reverse("receive_notify"),
identification_key=signer.sign(str(chat.id)),
data=RegisterEventSubscriptionRequest(
event_type=RegisterEventSubscriptionRequest.EventType.NEW_MESSAGE_ON_POST,
event_resource_id=chat.post.token,
),
)
base_url = reverse("chat_app")
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Django==5.0.10
djangorestframework==3.15.2
psycopg2-binary==2.9.9
kenar==0.5.*
pydantic==2.7.4
httpx==0.27.0
kenar