From dba8c44643546b53cbbc1156ceb5f8dec63b2e67 Mon Sep 17 00:00:00 2001 From: Helder Souza <42891390+helllllllder@users.noreply.github.com> Date: Mon, 27 Nov 2023 08:38:59 -0300 Subject: [PATCH] Feature: Discussion App Base Functions (#310) * feat: add discussions drawio file with the db tables * feat: create discussions app * feat: add discussion related models * feat: update models, add notify methods * feat: fix migrations * feat: separate models into two files for discussions and messages models * feat: add model constraints feat: add parametrized to help write unit tests * feat: Add base functionality for constructing the discussion endpoints * feat: makemigrations * feat: add can_create_discussion method to the room model * feat: update db model diagram --- chats/apps/discussions/__init__.py | 0 .../apps/discussions/app_services/__init__.py | 0 .../discussions/app_services/feedbacks.py | 15 + chats/apps/discussions/apps.py | 6 + chats/apps/discussions/exceptions.py | 2 + .../discussions/migrations/0001_initial.py | 249 +++ chats/apps/discussions/migrations/__init__.py | 0 chats/apps/discussions/models/__init__.py | 3 + chats/apps/discussions/models/discussion.py | 153 ++ .../discussions/models/discussion_message.py | 85 + .../discussions/models/discussion_user.py | 44 + chats/apps/discussions/models/validators.py | 2 + chats/apps/discussions/tests/__init__.py | 0 chats/apps/discussions/usecases/__init__.py | 0 chats/apps/discussions/views/__init__.py | 0 chats/apps/rooms/models.py | 10 + chats/core/models.py | 28 + chats/fixtures/fixture_app.json | 48 +- chats/fixtures/fixture_discussion.json | 1406 +++++++++++++++++ chats/settings.py | 3 + docs/diagrams/discussions.drawio | 99 ++ poetry.lock | 14 +- pyproject.toml | 1 + 23 files changed, 2163 insertions(+), 5 deletions(-) create mode 100644 chats/apps/discussions/__init__.py create mode 100644 chats/apps/discussions/app_services/__init__.py create mode 100644 chats/apps/discussions/app_services/feedbacks.py create mode 100644 chats/apps/discussions/apps.py create mode 100644 chats/apps/discussions/exceptions.py create mode 100644 chats/apps/discussions/migrations/0001_initial.py create mode 100644 chats/apps/discussions/migrations/__init__.py create mode 100644 chats/apps/discussions/models/__init__.py create mode 100644 chats/apps/discussions/models/discussion.py create mode 100644 chats/apps/discussions/models/discussion_message.py create mode 100644 chats/apps/discussions/models/discussion_user.py create mode 100644 chats/apps/discussions/models/validators.py create mode 100644 chats/apps/discussions/tests/__init__.py create mode 100644 chats/apps/discussions/usecases/__init__.py create mode 100644 chats/apps/discussions/views/__init__.py create mode 100644 chats/fixtures/fixture_discussion.json create mode 100644 docs/diagrams/discussions.drawio diff --git a/chats/apps/discussions/__init__.py b/chats/apps/discussions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chats/apps/discussions/app_services/__init__.py b/chats/apps/discussions/app_services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chats/apps/discussions/app_services/feedbacks.py b/chats/apps/discussions/app_services/feedbacks.py new file mode 100644 index 00000000..d322d029 --- /dev/null +++ b/chats/apps/discussions/app_services/feedbacks.py @@ -0,0 +1,15 @@ +import json + + +def create_feedback_json(method: str, content: dict): + return {"method": method, "content": content} + + +def create_discussion_feedback_message( + discussion: object, feedback: dict, method: str, notify=True +): + return discussion.create_discussion_message( + message=json.dumps(create_feedback_json(method=method, content=feedback)), + system=True, + notify=notify, + ) diff --git a/chats/apps/discussions/apps.py b/chats/apps/discussions/apps.py new file mode 100644 index 00000000..c97706b9 --- /dev/null +++ b/chats/apps/discussions/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DiscussionConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "chats.apps.discussions" diff --git a/chats/apps/discussions/exceptions.py b/chats/apps/discussions/exceptions.py new file mode 100644 index 00000000..25e426a7 --- /dev/null +++ b/chats/apps/discussions/exceptions.py @@ -0,0 +1,2 @@ +class DiscussionValidationException(Exception): + pass diff --git a/chats/apps/discussions/migrations/0001_initial.py b/chats/apps/discussions/migrations/0001_initial.py new file mode 100644 index 00000000..31296108 --- /dev/null +++ b/chats/apps/discussions/migrations/0001_initial.py @@ -0,0 +1,249 @@ +# Generated by Django 4.1.2 on 2023-11-22 22:00 + +import chats.core.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("queues", "0005_queue_config"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("rooms", "0010_alter_room_urn"), + ("projects", "0019_flowstart_contact_data"), + ] + + operations = [ + migrations.CreateModel( + name="Discussion", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False + ), + ), + ( + "created_on", + models.DateTimeField(auto_now_add=True, verbose_name="Created on"), + ), + ( + "modified_on", + models.DateTimeField(auto_now=True, verbose_name="Modified on"), + ), + ( + "is_deleted", + models.BooleanField(default=False, verbose_name="is deleted?"), + ), + ( + "subject", + models.CharField(max_length=50, verbose_name="Subject Text"), + ), + ( + "is_queued", + models.BooleanField(default=True, verbose_name="Is queued?"), + ), + ( + "is_active", + models.BooleanField(default=True, verbose_name="Is active?"), + ), + ( + "created_by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="discussions", + to=settings.AUTH_USER_MODEL, + to_field="email", + verbose_name="Created By", + ), + ), + ( + "queue", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="discussions", + to="queues.queue", + verbose_name="Queue", + ), + ), + ( + "room", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="discussions", + to="rooms.room", + verbose_name="Room", + ), + ), + ], + options={ + "verbose_name": "Discussion", + "verbose_name_plural": "Discussions", + }, + bases=(models.Model, chats.core.models.WebSocketsNotifiableMixin), + ), + migrations.CreateModel( + name="DiscussionMessage", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False + ), + ), + ( + "created_on", + models.DateTimeField(auto_now_add=True, verbose_name="Created on"), + ), + ( + "modified_on", + models.DateTimeField(auto_now=True, verbose_name="Modified on"), + ), + ("text", models.TextField(blank=True, null=True, verbose_name="Text")), + ( + "discussion", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="messages", + to="discussions.discussion", + verbose_name="discussion", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="discussion_messages", + to=settings.AUTH_USER_MODEL, + to_field="email", + verbose_name="user", + ), + ), + ], + options={ + "verbose_name": "Message", + "verbose_name_plural": "Messages", + "ordering": ["created_on"], + }, + bases=(chats.core.models.WebSocketsNotifiableMixin, models.Model), + ), + migrations.CreateModel( + name="DiscussionUser", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False + ), + ), + ( + "created_on", + models.DateTimeField(auto_now_add=True, verbose_name="Created on"), + ), + ( + "modified_on", + models.DateTimeField(auto_now=True, verbose_name="Modified on"), + ), + ( + "role", + models.PositiveIntegerField( + choices=[(0, "Creator"), (1, "Admin"), (2, "Participant")], + default=2, + verbose_name="role", + ), + ), + ( + "discussion", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="added_users", + to="discussions.discussion", + verbose_name="Discussion", + ), + ), + ( + "permission", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="discussion_users", + to="projects.projectpermission", + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Discussion User", + "verbose_name_plural": "Discussions Users", + }, + ), + migrations.CreateModel( + name="DiscussionMessageMedia", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False + ), + ), + ( + "created_on", + models.DateTimeField(auto_now_add=True, verbose_name="Created on"), + ), + ( + "modified_on", + models.DateTimeField(auto_now=True, verbose_name="Modified on"), + ), + ( + "content_type", + models.CharField(max_length=300, verbose_name="Content Type"), + ), + ( + "media_file", + models.FileField( + blank=True, + max_length=300, + null=True, + upload_to="discussionmedia/%Y/%m/%d/", + verbose_name="Media File", + ), + ), + ( + "message", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="medias", + to="discussions.discussionmessage", + verbose_name="discussion message", + ), + ), + ], + options={ + "verbose_name": "Discussion Message Media", + "verbose_name_plural": "Discussion Message Medias", + }, + ), + migrations.AddConstraint( + model_name="discussionuser", + constraint=models.UniqueConstraint( + fields=("permission", "discussion"), + name="unique_permission_per_discussion", + ), + ), + migrations.AddConstraint( + model_name="discussion", + constraint=models.UniqueConstraint( + condition=models.Q(("is_active", True)), + fields=("room",), + name="unique_room_is_activetrue_discussion", + ), + ), + ] diff --git a/chats/apps/discussions/migrations/__init__.py b/chats/apps/discussions/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chats/apps/discussions/models/__init__.py b/chats/apps/discussions/models/__init__.py new file mode 100644 index 00000000..34428b08 --- /dev/null +++ b/chats/apps/discussions/models/__init__.py @@ -0,0 +1,3 @@ +from .discussion import Discussion # noqa +from .discussion_message import DiscussionMessage, DiscussionMessageMedia # noqa +from .discussion_user import DiscussionUser # noqa diff --git a/chats/apps/discussions/models/discussion.py b/chats/apps/discussions/models/discussion.py new file mode 100644 index 00000000..9374b881 --- /dev/null +++ b/chats/apps/discussions/models/discussion.py @@ -0,0 +1,153 @@ +from django.conf import settings +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from chats.core.models import BaseModel, BaseSoftDeleteModel, WebSocketsNotifiableMixin + + +class Discussion(BaseSoftDeleteModel, BaseModel, WebSocketsNotifiableMixin): + subject = models.CharField( + _("Subject Text"), max_length=50, blank=False, null=False + ) + created_by = models.ForeignKey( + "accounts.User", + related_name="discussions", + on_delete=models.CASCADE, + verbose_name=_("Created By"), + to_field="email", + ) + room = models.ForeignKey( + "rooms.Room", + related_name="discussions", + on_delete=models.CASCADE, + verbose_name=_("Room"), + ) + queue = models.ForeignKey( + "queues.Queue", + related_name="discussions", + on_delete=models.CASCADE, + verbose_name=_("Queue"), + ) + is_queued = models.BooleanField(_("Is queued?"), default=True) + is_active = models.BooleanField(_("Is active?"), default=True) + + class Meta: + verbose_name = "Discussion" + verbose_name_plural = "Discussions" + constraints = [ + models.UniqueConstraint( + fields=[ + "room", + ], + condition=models.Q(is_active=True), + name="unique_room_is_activetrue_discussion", + ) + ] + + def __str__(self) -> str: + return f"{self.created_by.full_name} {self.subject}" + + def delete(self): + self.is_active = False + self.save() + self.notify("close") + + @property + def project(self): + return self.queue.sector.project + + @property + def sector(self): + return self.queue.sector + + @property + def serialized_ws_data(self): + # TODO: add serializer when creating discussion endpoints + return {} + + @property + def notification_groups(self) -> list: + if self.is_queued: + return [f"queue_{self.queue.pk}"] + return [ + f"permission_{user_permission}" + for user_permission in self.added_users.values_list("permission", flat=True) + ] + + def get_action(self, action: str) -> str: + return f"discussions.{action}" + + def notify_user(self, user_permission, action: str, content: dict = {}): + if "." not in action: + action = f"discussions.{action}" + content = content or self.serialized_ws_data + self.notify( + action=action, content=content, groups=[f"permission_{user_permission.pk}"] + ) + + def notify_queue( + self, + action: str, + content: dict = {}, + ): + content = content or self.serialized_ws_data + self.notify(action=action, content=content, groups=[f"queue_{self.queue.pk}"]) + + def check_queued(self): + if self.is_queued and self.added_users.count() > 1: + self.is_queued = False + self.save() + self.notify_queue("update") + + # permission block + def get_permission(self, user): + return self.project.get_permission(user) + + def can_add_user(self, user_permission) -> bool: + if user_permission.is_manager(any_sector=True): + return True + return self.added_users.count() < settings.DISCUSSION_AGENTS_LIMIT + + def is_admin_manager_or_creator(self, user): + perm = self.get_permission(user) + try: + return perm.is_manager(any_sector=True) or self.created_by == user + except AttributeError: + return False + + def is_added_user(self, user): + return self.added_users.filter(permission__user=user).exists() + + def can_retrieve(self, user): + if self.is_added_user(user): + return True + if self.is_admin_manager_or_creator(user): + return True + if ( + self.is_queued + and self.queue.authorizations.filter(permission__user=user).exists() + ): + return True + + return False + + # messages and discussion users + def create_discussion_user(self, from_user, to_user, role=None): + from_permission = self.get_permission(user=from_user) + to_permission = self.get_permission(user=to_user) + discussion_user = None + + if (from_permission and to_permission) and self.can_add_user(from_permission): + role = role if role is not None else to_permission.role + discussion_user = self.added_users.create( + permission=to_permission, role=role + ) + self.check_queued() + return discussion_user + + def create_discussion_message(self, message, user=None, system=False, notify=True): + sender = (user or self.created_by) if not system else None + msg = self.messages.create(user=sender, text=message) + if notify: + msg.notify("create") + return msg diff --git a/chats/apps/discussions/models/discussion_message.py b/chats/apps/discussions/models/discussion_message.py new file mode 100644 index 00000000..18f34c20 --- /dev/null +++ b/chats/apps/discussions/models/discussion_message.py @@ -0,0 +1,85 @@ +from django.conf import settings +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from chats.core.models import BaseModel, WebSocketsNotifiableMixin + + +class DiscussionMessage(WebSocketsNotifiableMixin, BaseModel): + discussion = models.ForeignKey( + "discussions.Discussion", + related_name="messages", + verbose_name=_("discussion"), + on_delete=models.CASCADE, + ) + user = models.ForeignKey( + "accounts.User", + related_name="discussion_messages", + verbose_name=_("user"), + on_delete=models.CASCADE, + null=True, + blank=True, + to_field="email", + ) + text = models.TextField(_("Text"), blank=True, null=True) + + class Meta: + verbose_name = "Message" + verbose_name_plural = "Messages" + ordering = ["created_on"] + + @property + def media(self): + return self.medias.all() + + @property + def serialized_ws_data(self): + # TODO: add serializer when creating message endpoints + return {} + + @property + def notification_groups(self) -> list: + return self.discussion.notification_groups + + def get_action(self, action: str) -> str: + return f"discussion_msg.{action}" + + +class DiscussionMessageMedia(BaseModel): + message = models.ForeignKey( + "discussions.DiscussionMessage", + related_name="medias", + verbose_name=_("discussion message"), + on_delete=models.CASCADE, + blank=True, + null=True, + ) + + content_type = models.CharField(_("Content Type"), max_length=300) + media_file = models.FileField( + _("Media File"), + null=True, + blank=True, + max_length=300, + upload_to="discussionmedia/%Y/%m/%d/", + ) + + class Meta: + verbose_name = _("Discussion Message Media") + verbose_name_plural = _("Discussion Message Medias") + + def __str__(self): + return f"{self.message.pk} - {self.url}" + + @property + def url(self): + url = self.media_file.url + try: + if url.startswith("/"): + url = settings.ENGINE_BASE_URL + url + except AttributeError: + return "" + return url + + def notify(self): + self.message.notify("update") diff --git a/chats/apps/discussions/models/discussion_user.py b/chats/apps/discussions/models/discussion_user.py new file mode 100644 index 00000000..af94d090 --- /dev/null +++ b/chats/apps/discussions/models/discussion_user.py @@ -0,0 +1,44 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from chats.core.models import BaseModel + + +class DiscussionUser(BaseModel): + class Role(models.IntegerChoices): + CREATOR = 0, _("Creator") + ADMIN = 1, _("Admin") + PARTICIPANT = 2, _("Participant") + + permission = models.ForeignKey( + "projects.ProjectPermission", + related_name="discussion_users", + on_delete=models.CASCADE, + verbose_name=_("User"), + ) + discussion = models.ForeignKey( + "discussions.Discussion", + related_name="added_users", + on_delete=models.CASCADE, + verbose_name=_("Discussion"), + ) + role = models.PositiveIntegerField( + _("role"), choices=Role.choices, default=Role.PARTICIPANT + ) + + class Meta: + verbose_name = "Discussion User" + verbose_name_plural = "Discussions Users" + constraints = [ + models.UniqueConstraint( + fields=["permission", "discussion"], + name="unique_permission_per_discussion", + ) + ] + + def __str__(self) -> str: + return f"{self.discussion.subject} {self.user.full_name} {self.role}" + + @property + def user(self): + return self.permission.user diff --git a/chats/apps/discussions/models/validators.py b/chats/apps/discussions/models/validators.py new file mode 100644 index 00000000..27f17b7c --- /dev/null +++ b/chats/apps/discussions/models/validators.py @@ -0,0 +1,2 @@ +def validate_queue_and_room(queue, room): + return queue.sector.project == room.queue.sector.project diff --git a/chats/apps/discussions/tests/__init__.py b/chats/apps/discussions/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chats/apps/discussions/usecases/__init__.py b/chats/apps/discussions/usecases/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chats/apps/discussions/views/__init__.py b/chats/apps/discussions/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chats/apps/rooms/models.py b/chats/apps/rooms/models.py index 7e766778..9a6c631c 100644 --- a/chats/apps/rooms/models.py +++ b/chats/apps/rooms/models.py @@ -275,3 +275,13 @@ def queue_connection(self, action: str, queue=None): content={"name": "room", "id": str(self.pk)}, action=f"group.{action}", ) + + def can_create_discussion(self, user): + """ + Active rooms: only admin, managers and the user on the room are able to create discussions + Inactive rooms: anyone in the project can create discussions + """ + if user == self.user: + return True + perm = self.get_permission(user) + return not self.is_active or perm.is_manager(any_sector=True) diff --git a/chats/core/models.py b/chats/core/models.py index 25733967..7ac4b312 100644 --- a/chats/core/models.py +++ b/chats/core/models.py @@ -4,6 +4,34 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from chats.utils.websockets import send_channels_group + + +class WebSocketsNotifiableMixin: + @property + def serialized_ws_data(self) -> dict: + ... + + @property + def notification_groups(self) -> list: + ... + + def get_action(self, action: str) -> str: + ... + + def notify(self, action: str, groups: list = [], content: dict = {}) -> None: + if "." not in action: + action = self.get_action(action) + content = content if content else self.serialized_ws_data + groups = groups if groups else self.notification_groups + for group in groups: + send_channels_group( + group_name=group, + call_type="notify", + content=content, + action=action, + ) + class BaseModel(models.Model): uuid = models.UUIDField(default=uuid.uuid4, primary_key=True) diff --git a/chats/fixtures/fixture_app.json b/chats/fixtures/fixture_app.json index d5a60bce..c864c081 100644 --- a/chats/fixtures/fixture_app.json +++ b/chats/fixtures/fixture_app.json @@ -2584,10 +2584,18 @@ }, { "model": "authtoken.token", - "pk": "4215e6d6666e54f7db9f98100533aa68909fd855", + "pk": "6e52f41093468740d96649736e66e3eb7fbd008a", "fields": { - "user": 8, - "created": "2022-08-25T04:56:37.823Z" + "user": 2, + "created": "2022-08-23T20:07:49.267Z" + } + }, + { + "model": "authtoken.token", + "pk": "0a62ae56258011d9cbbb8f0414d28a515100269d", + "fields": { + "user": 3, + "created": "2023-11-06T18:27:03.349Z" } }, { @@ -2598,6 +2606,38 @@ "created": "2022-08-24T21:15:15.007Z" } }, + { + "model": "authtoken.token", + "pk": "a0358e20c8755568189d3a7e688ac3ec771317e2", + "fields": { + "user": 5, + "created": "2023-11-06T18:30:14.350Z" + } + }, + { + "model": "authtoken.token", + "pk": "c23e0173ac75c1a9ab448967e6a624e1a6ac1a2d", + "fields": { + "user": 6, + "created": "2023-03-01T18:40:25.186Z" + } + }, + { + "model": "authtoken.token", + "pk": "2e8b075af1c4b7a97fbdab043f1584ff518b3301", + "fields": { + "user": 7, + "created": "2022-11-08T20:51:15.599Z" + } + }, + { + "model": "authtoken.token", + "pk": "4215e6d6666e54f7db9f98100533aa68909fd855", + "fields": { + "user": 8, + "created": "2022-08-25T04:56:37.823Z" + } + }, { "model": "authtoken.token", "pk": "d116bca8757372f3b5936096473929ed1465915e", @@ -2614,4 +2654,4 @@ "created": "2022-08-24T22:06:09.160Z" } } -] \ No newline at end of file +] diff --git a/chats/fixtures/fixture_discussion.json b/chats/fixtures/fixture_discussion.json new file mode 100644 index 00000000..5a0537e3 --- /dev/null +++ b/chats/fixtures/fixture_discussion.json @@ -0,0 +1,1406 @@ +[ + { + "model": "rooms.room", + "pk": "8b120093-da6b-4417-8b47-52166564fcb5", + "fields": { + "created_on": "2022-09-03T00:20:09.559Z", + "modified_on": "2022-09-04T18:16:00.031Z", + "user": null, + "contact": "3dda1d84-7c03-4f9a-92a5-24059e10d7a7", + "queue": "f2519480-7e58-4fc4-9894-9ab1769e29cf", + "custom_fields": { + "mood": "angry", + "country": "brazil" + }, + "callback_url": "http://localhost/test/66a47111-6e6f-43b3-9fdc-a92a18bc57d2", + "ended_at": null, + "ended_by": null, + "is_active": true, + "transfer_history": [ + { + "name": "ENGINE", + "type": "queue" + }, + { + "name": "", + "type": "user" + } + ], + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "4857b3f7-90e0-4df6-a4b1-8f2f6f6b471a", + "fields": { + "created_on": "2022-09-03T00:20:09.559Z", + "modified_on": "2022-09-04T18:16:00.031Z", + "user": "amywong@chats.weni.ai", + "contact": "4e9b16eb-57d4-4215-954d-939f7742106a", + "queue": "f2519480-7e58-4fc4-9894-9ab1769e29cf", + "custom_fields": { + "mood": "angry", + "country": "brazil" + }, + "callback_url": "http://localhost/test/1c830ac0-1ba7-49f9-b8c8-b96af41d4213", + "ended_at": null, + "ended_by": null, + "is_active": true, + "transfer_history": [ + { + "name": "ENGINE", + "type": "queue" + }, + { + "name": "", + "type": "user" + } + ], + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "8eeace79-fbca-454f-a811-56116c87adc5", + "fields": { + "created_on": "2022-09-03T00:20:09.559Z", + "modified_on": "2022-09-04T18:16:00.031Z", + "user": "amywong@chats.weni.ai", + "contact": "4e9b16eb-57d4-4215-954d-939f7742106a", + "queue": "f2519480-7e58-4fc4-9894-9ab1769e29cf", + "custom_fields": { + "mood": "angry", + "country": "brazil" + }, + "callback_url": "http://localhost/test/ac6322ca-4a5b-4e5f-bb00-050c60e93b0b", + "ended_at": "2023-09-03T00:20:09.559Z", + "ended_by": "agent", + "is_active": false, + "transfer_history": [ + { + "name": "ENGINE", + "type": "queue" + }, + { + "name": "", + "type": "user" + } + ], + "tags": [] + } + }, + { + "model": "contacts.contact", + "pk": "1b691cb7-eb40-4ec8-9ca8-7f3d00d5a36b", + "fields": { + "created_on": "2023-11-06T18:08:28.154Z", + "modified_on": "2023-11-06T18:08:28.154Z", + "external_id": "19c6b5f1-4985-40a9-950f-c9c94b99a189", + "name": "Amos Trantow", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "03a23568-ea38-4ddb-bb14-924ac9a0b120", + "fields": { + "created_on": "2023-11-06T18:10:55.251Z", + "modified_on": "2023-11-06T18:10:55.251Z", + "external_id": "a542ddb1-4ea6-4d98-aca5-302713dab34e", + "name": "Eugene Nikolaus DDS", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "a13c4263-a1eb-4d54-83c9-855d6fe998a9", + "fields": { + "created_on": "2023-11-06T18:11:18.969Z", + "modified_on": "2023-11-06T18:11:18.969Z", + "external_id": "5c9a7889-67d0-49b3-9b72-1c6fce1866a1", + "name": "Jaime Kemmer", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "82c89830-3e00-408a-8a8e-d91c16ef2ff1", + "fields": { + "created_on": "2023-11-06T18:11:19.877Z", + "modified_on": "2023-11-06T18:11:19.877Z", + "external_id": "973a6bb1-8bbb-41b2-8bee-d588d80f2ca0", + "name": "Jeffrey Rath", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "5a77f131-c51f-47f9-a68d-417c25966ac8", + "fields": { + "created_on": "2023-11-06T18:11:20.634Z", + "modified_on": "2023-11-06T18:11:20.634Z", + "external_id": "b0e504db-2b6b-4781-9b06-1ec15a5c5df7", + "name": "Anna Buckridge DVM", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "dc7cdf20-1b8a-4c57-881e-6efbf39a3191", + "fields": { + "created_on": "2023-11-06T18:13:50.660Z", + "modified_on": "2023-11-06T18:13:50.660Z", + "external_id": "70afbdfb-722f-4f0c-aec8-34c03320d326", + "name": "Tyler Feil", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "424f0e73-7597-46fa-80fc-d10ce7ef6481", + "fields": { + "created_on": "2023-11-06T18:13:51.500Z", + "modified_on": "2023-11-06T18:13:51.500Z", + "external_id": "a666485e-f4f1-4f9e-833d-aafda616612e", + "name": "Vera Kling", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "820646b6-9611-42dc-be49-c9fc3b9ded25", + "fields": { + "created_on": "2023-11-06T18:14:01.060Z", + "modified_on": "2023-11-06T18:14:01.060Z", + "external_id": "4341b9a4-4eca-459a-8671-064cf4ee103f", + "name": "Natasha Roob", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "b8712ab3-50e4-400b-b14e-4f87979bf70f", + "fields": { + "created_on": "2023-11-06T18:14:01.931Z", + "modified_on": "2023-11-06T18:14:01.931Z", + "external_id": "4e6a6d58-d93b-4045-b50d-da1fd901d20f", + "name": "Glenda Kutch", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "4f83db67-0799-41dc-b502-7abb06abfc31", + "fields": { + "created_on": "2023-11-06T18:14:18.753Z", + "modified_on": "2023-11-06T18:14:18.753Z", + "external_id": "1d655f9a-4c16-47d3-98d5-a4729cb46530", + "name": "Mrs. Whitney Marquardt", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "9d184183-d390-4eb3-9ef1-3a10f77694e5", + "fields": { + "created_on": "2023-11-06T18:14:19.671Z", + "modified_on": "2023-11-06T18:14:19.671Z", + "external_id": "888a14d1-558d-43eb-a9b6-09fb860d8660", + "name": "Christie Schultz", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "contacts.contact", + "pk": "e3e12ca8-8e76-4dc5-8d90-e5e90b61450e", + "fields": { + "created_on": "2023-11-06T19:02:04.462Z", + "modified_on": "2023-11-06T19:02:04.462Z", + "external_id": "803470ca-1acf-4b77-93c1-80b7e5bcdef2", + "name": "Melissa Walker", + "email": null, + "status": "", + "phone": "", + "custom_fields": null + } + }, + { + "model": "projects.project", + "pk": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "fields": { + "created_on": "2023-11-03T13:24:13.218Z", + "modified_on": "2023-11-03T13:24:13.218Z", + "config": null, + "name": "Projeto Discussoes", + "timezone": "America/Sao_Paulo", + "flows_authorization": null, + "date_format": "D", + "is_template": false, + "template_type": null + } + }, + { + "model": "projects.projectpermission", + "pk": "26e9b6cb-a0e0-4de3-9037-54628363b4f8", + "fields": { + "created_on": "2023-11-03T13:31:40.973Z", + "modified_on": "2023-11-03T13:31:40.973Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "admin@weni.ai", + "role": 1, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "6aac1cca-c02c-4fad-b8d9-9d36292f8dc7", + "fields": { + "created_on": "2023-11-03T13:28:34.799Z", + "modified_on": "2023-11-03T13:28:34.799Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "amywong@chats.weni.ai", + "role": 1, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "b1fea9b3-f216-4119-8f86-50115c49dfd4", + "fields": { + "created_on": "2023-11-03T13:29:33.911Z", + "modified_on": "2023-11-03T13:29:33.911Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "amazoninhaweni@chats.weni.ai", + "role": 1, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "349a3842-8915-4d20-9df3-f505a24d2f5c", + "fields": { + "created_on": "2023-11-03T13:30:30.971Z", + "modified_on": "2023-11-03T13:30:30.971Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "sectormanager@chats.weni.ai", + "role": 2, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "ea2a97cd-1dc8-43f1-a204-1a4f72c665df", + "fields": { + "created_on": "2023-11-03T13:30:58.373Z", + "modified_on": "2023-11-03T13:30:58.373Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "brucebanner@chats.weni.ai", + "role": 2, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "27dd61c2-16d5-41b0-8fc0-bb027ffb90f8", + "fields": { + "created_on": "2023-11-03T13:31:22.351Z", + "modified_on": "2023-11-03T13:31:22.351Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "linalawson@chats.weni.ai", + "role": 2, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "754bd473-2921-4755-958e-9bf29272ce2a", + "fields": { + "created_on": "2023-11-03T13:30:41.146Z", + "modified_on": "2023-11-03T13:30:41.146Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "johndoe@chats.weni.ai", + "role": 2, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "fd7f2121-d407-4154-b442-736697568153", + "fields": { + "created_on": "2023-11-03T13:31:05.910Z", + "modified_on": "2023-11-03T13:31:05.910Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "janeduren@chats.weni.ai", + "role": 2, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "a7cf2af5-e43c-4219-b5b7-64708c279357", + "fields": { + "created_on": "2023-11-03T13:30:20.469Z", + "modified_on": "2023-11-03T13:30:20.469Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "agentqueue@chats.weni.ai", + "role": 2, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "projects.projectpermission", + "pk": "ec9226f3-dae7-474d-8c31-a0276c19f63e", + "fields": { + "created_on": "2023-11-03T13:30:50.148Z", + "modified_on": "2023-11-03T13:30:50.148Z", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "user": "foobar@chats.weni.ai", + "role": 2, + "status": "OFFLINE", + "first_access": true + } + }, + { + "model": "sectors.sector", + "pk": "9e76311c-3da4-4115-8558-34958b047734", + "fields": { + "created_on": "2023-11-03T13:43:51.310Z", + "modified_on": "2023-11-03T13:43:51.310Z", + "config": {}, + "name": "Dúvidas", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "rooms_limit": 3, + "work_start": "07:00:00", + "work_end": "23:59:00", + "can_trigger_flows": false, + "sign_messages": false, + "is_deleted": false, + "open_offline": true, + "can_edit_custom_fields": false + } + }, + { + "model": "sectors.sector", + "pk": "b5e7f2e4-c10a-44ea-9588-5aba52d3d720", + "fields": { + "created_on": "2023-11-03T13:44:04.953Z", + "modified_on": "2023-11-03T13:44:04.953Z", + "config": {}, + "name": "Soluções", + "project": "dae39bcc-bdc2-4b03-b4da-023a117f8474", + "rooms_limit": 3, + "work_start": "07:00:00", + "work_end": "23:59:00", + "can_trigger_flows": false, + "sign_messages": false, + "is_deleted": false, + "open_offline": true, + "can_edit_custom_fields": false + } + }, + { + "model": "sectors.sectorauthorization", + "pk": "d02859f0-e7fa-4fff-a304-d645befe5e63", + "fields": { + "created_on": "2023-11-03T13:59:30.001Z", + "modified_on": "2023-11-03T13:59:30.001Z", + "permission": "349a3842-8915-4d20-9df3-f505a24d2f5c", + "sector": "9e76311c-3da4-4115-8558-34958b047734", + "role": 1 + } + }, + { + "model": "sectors.sectorauthorization", + "pk": "3c46d911-9150-4799-9db8-16704f2b31c8", + "fields": { + "created_on": "2023-11-03T14:22:47.906Z", + "modified_on": "2023-11-03T14:22:47.906Z", + "permission": "ea2a97cd-1dc8-43f1-a204-1a4f72c665df", + "sector": "b5e7f2e4-c10a-44ea-9588-5aba52d3d720", + "role": 1 + } + }, + { + "model": "queues.queue", + "pk": "71a8b436-7239-4a42-b05c-7d430d98cd66", + "fields": { + "created_on": "2023-11-06T11:52:57.665Z", + "modified_on": "2023-11-06T11:52:57.669Z", + "is_deleted": false, + "config": null, + "sector": "b5e7f2e4-c10a-44ea-9588-5aba52d3d720", + "name": "Financeiro", + "default_message": null + } + }, + { + "model": "queues.queue", + "pk": "687ca2aa-978b-48e6-ae95-1d03fc1d1a5b", + "fields": { + "created_on": "2023-11-06T11:53:39.041Z", + "modified_on": "2023-11-06T11:53:39.051Z", + "is_deleted": false, + "config": null, + "sector": "b5e7f2e4-c10a-44ea-9588-5aba52d3d720", + "name": "Suporte Tecnico", + "default_message": null + } + }, + { + "model": "queues.queue", + "pk": "4a7fdaac-7aec-4add-b9aa-e95765def91d", + "fields": { + "created_on": "2023-11-06T11:53:57.704Z", + "modified_on": "2023-11-06T11:53:57.716Z", + "is_deleted": false, + "config": null, + "sector": "9e76311c-3da4-4115-8558-34958b047734", + "name": "Empreendimentos", + "default_message": null + } + }, + { + "model": "queues.queue", + "pk": "f362336c-672b-4962-be82-efdf6101be6d", + "fields": { + "created_on": "2023-11-06T11:55:43.503Z", + "modified_on": "2023-11-06T11:55:43.513Z", + "is_deleted": false, + "config": null, + "sector": "9e76311c-3da4-4115-8558-34958b047734", + "name": "Construção Propria", + "default_message": null + } + }, + { + "model": "queues.queueauthorization", + "pk": "4284ca67-6bc4-4489-8c72-6d8ad6df96da", + "fields": { + "created_on": "2023-11-06T14:35:17.544Z", + "modified_on": "2023-11-06T14:35:17.544Z", + "queue": "71a8b436-7239-4a42-b05c-7d430d98cd66", + "role": 1, + "permission": "ec9226f3-dae7-474d-8c31-a0276c19f63e" + } + }, + { + "model": "queues.queueauthorization", + "pk": "d3755411-3b90-4aa5-91a3-f6b040ac9750", + "fields": { + "created_on": "2023-11-06T14:35:38.954Z", + "modified_on": "2023-11-06T14:35:38.954Z", + "queue": "71a8b436-7239-4a42-b05c-7d430d98cd66", + "role": 1, + "permission": "27dd61c2-16d5-41b0-8fc0-bb027ffb90f8" + } + }, + { + "model": "queues.queueauthorization", + "pk": "cfcf3cea-343b-4beb-a7fd-9f55776e4539", + "fields": { + "created_on": "2023-11-06T14:34:50.576Z", + "modified_on": "2023-11-06T14:34:50.576Z", + "queue": "4a7fdaac-7aec-4add-b9aa-e95765def91d", + "role": 1, + "permission": "a7cf2af5-e43c-4219-b5b7-64708c279357" + } + }, + { + "model": "queues.queueauthorization", + "pk": "03bea013-5ec6-4c0f-823c-306df1786b37", + "fields": { + "created_on": "2023-11-06T12:13:17.795Z", + "modified_on": "2023-11-06T12:13:17.795Z", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "role": 1, + "permission": "27dd61c2-16d5-41b0-8fc0-bb027ffb90f8" + } + }, + { + "model": "queues.queueauthorization", + "pk": "e8005b2b-4ae4-4633-9a86-69a8b7823fc4", + "fields": { + "created_on": "2023-11-06T12:13:35.041Z", + "modified_on": "2023-11-06T12:13:35.041Z", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "role": 1, + "permission": "754bd473-2921-4755-958e-9bf29272ce2a" + } + }, + { + "model": "queues.queueauthorization", + "pk": "4d7b6905-8974-4720-ab5d-230096c6352b", + "fields": { + "created_on": "2023-11-06T12:13:49.807Z", + "modified_on": "2023-11-06T12:13:49.807Z", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "role": 1, + "permission": "fd7f2121-d407-4154-b442-736697568153" + } + }, + { + "model": "rooms.room", + "pk": "b095d950-e98c-40a9-8397-9baa4697b241", + "fields": { + "created_on": "2023-11-06T18:14:18.766Z", + "modified_on": "2023-11-06T19:15:32.903Z", + "user": null, + "contact": "4f83db67-0799-41dc-b502-7abb06abfc31", + "queue": "71a8b436-7239-4a42-b05c-7d430d98cd66", + "custom_fields": null, + "urn": "whatsapp:81-978-259-8604", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "Financeiro", + "type": "queue" + }, + "from": { + "name": "", + "type": "" + }, + "action": "forward" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "50da4076-1f3b-4355-9a2e-be91bd6a410c", + "fields": { + "created_on": "2023-11-06T18:14:19.700Z", + "modified_on": "2023-11-06T19:15:40.487Z", + "user": "linalawson@chats.weni.ai", + "contact": "9d184183-d390-4eb3-9ef1-3a10f77694e5", + "queue": "71a8b436-7239-4a42-b05c-7d430d98cd66", + "custom_fields": null, + "urn": "whatsapp:54-624-630-7336", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "teste", + "type": "user" + }, + "from": { + "name": "Financeiro", + "type": "queue" + }, + "action": "transfer" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "67930b18-b464-470d-ba57-757dc23dc58c", + "fields": { + "created_on": "2023-11-06T18:14:01.080Z", + "modified_on": "2023-11-06T19:15:48.918Z", + "user": null, + "contact": "820646b6-9611-42dc-be49-c9fc3b9ded25", + "queue": "687ca2aa-978b-48e6-ae95-1d03fc1d1a5b", + "custom_fields": null, + "urn": "whatsapp:70-373-918-6450", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "Suporte Tecnico", + "type": "queue" + }, + "from": { + "name": "", + "type": "" + }, + "action": "forward" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "3272fa3d-0ebf-4a85-94f2-e973a0ea6fcd", + "fields": { + "created_on": "2023-11-06T18:14:01.954Z", + "modified_on": "2023-11-06T19:16:00.530Z", + "user": null, + "contact": "b8712ab3-50e4-400b-b14e-4f87979bf70f", + "queue": "687ca2aa-978b-48e6-ae95-1d03fc1d1a5b", + "custom_fields": null, + "urn": "whatsapp:9-582-311-6916", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "Suporte Tecnico", + "type": "queue" + }, + "from": { + "name": "", + "type": "" + }, + "action": "forward" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "f487cabc-4ec9-4b2a-9b9a-e552831ba937", + "fields": { + "created_on": "2023-11-06T19:02:04.485Z", + "modified_on": "2023-11-06T19:16:10.949Z", + "user": null, + "contact": "e3e12ca8-8e76-4dc5-8d90-e5e90b61450e", + "queue": "687ca2aa-978b-48e6-ae95-1d03fc1d1a5b", + "custom_fields": null, + "urn": "whatsapp:75-955-726-9757", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "Suporte Tecnico", + "type": "queue" + }, + "from": { + "name": "", + "type": "" + }, + "action": "forward" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "222f291e-5ba8-4073-adae-7af36ff6eaeb", + "fields": { + "created_on": "2023-11-06T18:13:50.701Z", + "modified_on": "2023-11-06T19:14:18.102Z", + "user": null, + "contact": "dc7cdf20-1b8a-4c57-881e-6efbf39a3191", + "queue": "4a7fdaac-7aec-4add-b9aa-e95765def91d", + "custom_fields": null, + "urn": "whatsapp:90-989-342-0795", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "Empreendimentos", + "type": "queue" + }, + "from": { + "name": "", + "type": "" + }, + "action": "forward" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "b103c168-f84a-46bb-8fb3-dde700d16841", + "fields": { + "created_on": "2023-11-06T18:13:51.541Z", + "modified_on": "2023-11-06T19:14:26.671Z", + "user": "agentqueue@chats.weni.ai", + "contact": "424f0e73-7597-46fa-80fc-d10ce7ef6481", + "queue": "4a7fdaac-7aec-4add-b9aa-e95765def91d", + "custom_fields": null, + "urn": "whatsapp:6-749-778-3887", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "teste", + "type": "user" + }, + "from": { + "name": "Empreendimentos", + "type": "queue" + }, + "action": "transfer" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "16eb28e9-ddbb-4f08-8bf2-cddd38046431", + "fields": { + "created_on": "2023-11-06T18:10:55.271Z", + "modified_on": "2023-11-06T19:14:57.242Z", + "user": null, + "contact": "03a23568-ea38-4ddb-bb14-924ac9a0b120", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "custom_fields": null, + "urn": "whatsapp:17-983-901-4028", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "Construção Propria", + "type": "queue" + }, + "from": { + "name": "", + "type": "" + }, + "action": "forward" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "cf215ed2-d0c0-449a-bc10-fd85a4a63bbd", + "fields": { + "created_on": "2023-11-06T18:11:18.995Z", + "modified_on": "2023-11-06T19:15:05.957Z", + "user": null, + "contact": "a13c4263-a1eb-4d54-83c9-855d6fe998a9", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "custom_fields": null, + "urn": "whatsapp:88-665-811-2037", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "Construção Propria", + "type": "queue" + }, + "from": { + "name": "", + "type": "" + }, + "action": "forward" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "21c30743-8e12-4d19-b91f-d5c7d88addd1", + "fields": { + "created_on": "2023-11-06T18:11:20.656Z", + "modified_on": "2023-11-06T19:15:15.813Z", + "user": "linalawson@chats.weni.ai", + "contact": "5a77f131-c51f-47f9-a68d-417c25966ac8", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "custom_fields": null, + "urn": "whatsapp:58-865-412-3925", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "teste", + "type": "user" + }, + "from": { + "name": "Construção Propria", + "type": "queue" + }, + "action": "transfer" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "307dc034-2ecf-48b0-9d15-b4841ec7da5f", + "fields": { + "created_on": "2023-11-06T18:11:19.896Z", + "modified_on": "2023-11-06T19:15:26.483Z", + "user": "johndoe@chats.weni.ai", + "contact": "82c89830-3e00-408a-8a8e-d91c16ef2ff1", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "custom_fields": null, + "urn": "whatsapp:13-673-471-1080", + "callback_url": null, + "ended_at": null, + "ended_by": null, + "is_active": true, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "teste", + "type": "user" + }, + "from": { + "name": "Construção Propria", + "type": "queue" + }, + "action": "transfer" + }, + "tags": [] + } + }, + { + "model": "rooms.room", + "pk": "47a983f3-d4b0-4fd4-a66c-f2f3856fb2ac", + "fields": { + "created_on": "2023-11-06T18:08:28.174Z", + "modified_on": "2023-11-07T17:37:36.159Z", + "user": "johndoe@chats.weni.ai", + "contact": "1b691cb7-eb40-4ec8-9ca8-7f3d00d5a36b", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "custom_fields": null, + "urn": "whatsapp:32-239-317-1671", + "callback_url": null, + "ended_at": "2023-11-07T17:37:36.158Z", + "ended_by": "agent", + "is_active": false, + "is_waiting": false, + "transfer_history": { + "to": { + "name": "teste", + "type": "user" + }, + "from": { + "name": "Construção Propria", + "type": "queue" + }, + "action": "pick" + }, + "tags": [] + } + }, + { + "model": "msgs.message", + "pk": "bed81d9b-8597-4625-845b-a7c6eec3734d", + "fields": { + "created_on": "2023-11-06T19:14:18.087Z", + "modified_on": "2023-11-06T19:14:18.087Z", + "room": "222f291e-5ba8-4073-adae-7af36ff6eaeb", + "user": null, + "contact": "dc7cdf20-1b8a-4c57-881e-6efbf39a3191", + "text": "If we index the port, we can get to the AGP circuit through the open-source AI protocol!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "421761ea-bd15-4af1-bb52-79ae88692be0", + "fields": { + "created_on": "2023-11-06T19:14:18.760Z", + "modified_on": "2023-11-06T19:14:18.760Z", + "room": "222f291e-5ba8-4073-adae-7af36ff6eaeb", + "user": null, + "contact": "dc7cdf20-1b8a-4c57-881e-6efbf39a3191", + "text": "We need to copy the mobile AI circuit!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "d5efc866-41ae-4ae0-9f45-a4a112056d76", + "fields": { + "created_on": "2023-11-06T19:14:26.660Z", + "modified_on": "2023-11-06T19:14:26.660Z", + "room": "b103c168-f84a-46bb-8fb3-dde700d16841", + "user": null, + "contact": "424f0e73-7597-46fa-80fc-d10ce7ef6481", + "text": "If we hack the protocol, we can get to the JBOD matrix through the primary SCSI matrix!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "68cf1268-5c2c-44f8-bdea-db09e7890cc1", + "fields": { + "created_on": "2023-11-06T19:14:27.325Z", + "modified_on": "2023-11-06T19:14:27.325Z", + "room": "b103c168-f84a-46bb-8fb3-dde700d16841", + "user": null, + "contact": "424f0e73-7597-46fa-80fc-d10ce7ef6481", + "text": "Try to calculate the COM bus, maybe it will compress the bluetooth alarm!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "18d67e87-6601-4b61-8b51-157c04071593", + "fields": { + "created_on": "2023-11-06T19:14:33.500Z", + "modified_on": "2023-11-06T19:14:33.500Z", + "room": "47a983f3-d4b0-4fd4-a66c-f2f3856fb2ac", + "user": null, + "contact": "1b691cb7-eb40-4ec8-9ca8-7f3d00d5a36b", + "text": "Use the wireless JBOD interface, then you can copy the solid state port!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "d9c22fca-26bd-4ef9-98aa-e5a0f0272062", + "fields": { + "created_on": "2023-11-06T19:14:34.315Z", + "modified_on": "2023-11-06T19:14:34.315Z", + "room": "47a983f3-d4b0-4fd4-a66c-f2f3856fb2ac", + "user": null, + "contact": "1b691cb7-eb40-4ec8-9ca8-7f3d00d5a36b", + "text": "Use the 1080p ADP card, then you can index the digital hard drive!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "e4265f73-5910-4fd4-a3d8-2b085e60c041", + "fields": { + "created_on": "2023-11-06T19:14:57.231Z", + "modified_on": "2023-11-06T19:14:57.231Z", + "room": "16eb28e9-ddbb-4f08-8bf2-cddd38046431", + "user": null, + "contact": "03a23568-ea38-4ddb-bb14-924ac9a0b120", + "text": "If we reboot the array, we can get to the CSS bandwidth through the 1080p GB array!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "66e6c991-6a07-4290-8926-a87ed66be019", + "fields": { + "created_on": "2023-11-06T19:14:57.923Z", + "modified_on": "2023-11-06T19:14:57.923Z", + "room": "16eb28e9-ddbb-4f08-8bf2-cddd38046431", + "user": null, + "contact": "03a23568-ea38-4ddb-bb14-924ac9a0b120", + "text": "hacking the feed won't do anything, we need to synthesize the haptic XSS matrix!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "777c53c8-f5c8-48e8-a47b-5e2bb6568ccc", + "fields": { + "created_on": "2023-11-06T19:15:05.946Z", + "modified_on": "2023-11-06T19:15:05.946Z", + "room": "cf215ed2-d0c0-449a-bc10-fd85a4a63bbd", + "user": null, + "contact": "a13c4263-a1eb-4d54-83c9-855d6fe998a9", + "text": "I'll index the redundant SQL protocol, that should bus the COM monitor!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "69444fac-fa1f-4c12-836b-9f971c20d266", + "fields": { + "created_on": "2023-11-06T19:15:06.608Z", + "modified_on": "2023-11-06T19:15:06.608Z", + "room": "cf215ed2-d0c0-449a-bc10-fd85a4a63bbd", + "user": null, + "contact": "a13c4263-a1eb-4d54-83c9-855d6fe998a9", + "text": "The SAS driver is down, copy the open-source panel so we can override the THX firewall!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "f7493ec9-b1e8-40c2-8b28-c708e9b7bf3f", + "fields": { + "created_on": "2023-11-06T19:15:15.800Z", + "modified_on": "2023-11-06T19:15:15.800Z", + "room": "21c30743-8e12-4d19-b91f-d5c7d88addd1", + "user": null, + "contact": "5a77f131-c51f-47f9-a68d-417c25966ac8", + "text": "If we reboot the port, we can get to the AI bandwidth through the mobile SDD program!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "a17ab094-b9ba-454c-ad31-c8e20bc139e5", + "fields": { + "created_on": "2023-11-06T19:15:19.041Z", + "modified_on": "2023-11-06T19:15:19.041Z", + "room": "21c30743-8e12-4d19-b91f-d5c7d88addd1", + "user": null, + "contact": "5a77f131-c51f-47f9-a68d-417c25966ac8", + "text": "We need to override the online FTP protocol!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "9aab10aa-cb80-4dd6-bd4b-2e7b09871591", + "fields": { + "created_on": "2023-11-06T19:15:26.472Z", + "modified_on": "2023-11-06T19:15:26.472Z", + "room": "307dc034-2ecf-48b0-9d15-b4841ec7da5f", + "user": null, + "contact": "82c89830-3e00-408a-8a8e-d91c16ef2ff1", + "text": "Try to calculate the XML hard drive, maybe it will copy the digital application!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "6c2c923c-acd4-471e-adf4-b1cbe52894b2", + "fields": { + "created_on": "2023-11-06T19:15:32.897Z", + "modified_on": "2023-11-06T19:15:32.897Z", + "room": "b095d950-e98c-40a9-8397-9baa4697b241", + "user": null, + "contact": "4f83db67-0799-41dc-b502-7abb06abfc31", + "text": "If we reboot the monitor, we can get to the RSS program through the primary HTTP port!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "209d21aa-8184-4b89-a159-af2e0d18b750", + "fields": { + "created_on": "2023-11-06T19:15:33.701Z", + "modified_on": "2023-11-06T19:15:33.701Z", + "room": "b095d950-e98c-40a9-8397-9baa4697b241", + "user": null, + "contact": "4f83db67-0799-41dc-b502-7abb06abfc31", + "text": "We need to bypass the auxiliary HDD alarm!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "a5a9487f-4c96-4999-ba58-f29a72a79fa7", + "fields": { + "created_on": "2023-11-06T19:15:40.484Z", + "modified_on": "2023-11-06T19:15:40.484Z", + "room": "50da4076-1f3b-4355-9a2e-be91bd6a410c", + "user": null, + "contact": "9d184183-d390-4eb3-9ef1-3a10f77694e5", + "text": "I'll input the online SMS card, that should microchip the AGP application!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "dc668ce2-283e-40e2-94d8-117ce173ad48", + "fields": { + "created_on": "2023-11-06T19:15:41.124Z", + "modified_on": "2023-11-06T19:15:41.124Z", + "room": "50da4076-1f3b-4355-9a2e-be91bd6a410c", + "user": null, + "contact": "9d184183-d390-4eb3-9ef1-3a10f77694e5", + "text": "The SCSI capacitor is down, hack the wireless driver so we can generate the EXE feed!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "1231018a-d6c9-4f79-8e44-e11d97ef99e5", + "fields": { + "created_on": "2023-11-06T19:15:48.892Z", + "modified_on": "2023-11-06T19:15:48.892Z", + "room": "67930b18-b464-470d-ba57-757dc23dc58c", + "user": null, + "contact": "820646b6-9611-42dc-be49-c9fc3b9ded25", + "text": "The COM capacitor is down, reboot the back-end matrix so we can calculate the GB hard drive!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "07e1c4c2-6a82-4315-be66-71106f967550", + "fields": { + "created_on": "2023-11-06T19:15:51.468Z", + "modified_on": "2023-11-06T19:15:51.468Z", + "room": "67930b18-b464-470d-ba57-757dc23dc58c", + "user": null, + "contact": "820646b6-9611-42dc-be49-c9fc3b9ded25", + "text": "backing up the sensor won't do anything, we need to transmit the redundant PNG feed!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "1efc3a32-84a7-41bf-b63a-61c7e5286adb", + "fields": { + "created_on": "2023-11-06T19:16:00.519Z", + "modified_on": "2023-11-06T19:16:00.519Z", + "room": "3272fa3d-0ebf-4a85-94f2-e973a0ea6fcd", + "user": null, + "contact": "b8712ab3-50e4-400b-b14e-4f87979bf70f", + "text": "Try to program the FTP program, maybe it will reboot the cross-platform transmitter!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "4b7d8816-3419-4f95-a30f-9b9437c54606", + "fields": { + "created_on": "2023-11-06T19:16:01.309Z", + "modified_on": "2023-11-06T19:16:01.309Z", + "room": "3272fa3d-0ebf-4a85-94f2-e973a0ea6fcd", + "user": null, + "contact": "b8712ab3-50e4-400b-b14e-4f87979bf70f", + "text": "Try to transmit the AGP microchip, maybe it will quantify the redundant card!", + "seen": false + } + }, + { + "model": "msgs.message", + "pk": "8d92c017-a5d5-4717-927b-37cc7be249ca", + "fields": { + "created_on": "2023-11-06T19:16:10.923Z", + "modified_on": "2023-11-06T19:16:10.923Z", + "room": "f487cabc-4ec9-4b2a-9b9a-e552831ba937", + "user": null, + "contact": "e3e12ca8-8e76-4dc5-8d90-e5e90b61450e", + "text": "Use the digital IB interface, then you can input the primary system!", + "seen": false + } + }, + { + "model": "discussions.discussion", + "pk": "3c2d1694-8db9-4f09-976b-e263f9d79c99", + "fields": { + "created_on": "2023-11-06T19:47:58.045Z", + "modified_on": "2023-11-06T19:47:58.045Z", + "is_deleted": false, + "subject": "synergize strategic experiences", + "created_by": "linalawson@chats.weni.ai", + "room": "50da4076-1f3b-4355-9a2e-be91bd6a410c", + "queue": "71a8b436-7239-4a42-b05c-7d430d98cd66", + "is_queued": true, + "is_active": true + } + }, + { + "model": "discussions.discussion", + "pk": "36584c70-aaf9-4f5c-b0c3-0547bb23879d", + "fields": { + "created_on": "2023-11-06T21:06:20.689Z", + "modified_on": "2023-11-06T21:11:46.295Z", + "is_deleted": false, + "subject": "whiteboard cross-platform web-readiness", + "created_by": "linalawson@chats.weni.ai", + "room": "21c30743-8e12-4d19-b91f-d5c7d88addd1", + "queue": "687ca2aa-978b-48e6-ae95-1d03fc1d1a5b", + "is_queued": false, + "is_active": false + } + }, + { + "model": "discussions.discussion", + "pk": "9e2d9ea7-a130-4d07-ab54-de8284c8c922", + "fields": { + "created_on": "2023-11-06T19:58:56.897Z", + "modified_on": "2023-11-06T20:16:44.894Z", + "is_deleted": false, + "subject": "synthesize sexy partnerships", + "created_by": "agentqueue@chats.weni.ai", + "room": "b103c168-f84a-46bb-8fb3-dde700d16841", + "queue": "f362336c-672b-4962-be82-efdf6101be6d", + "is_queued": false, + "is_active": true + } + }, + { + "model": "discussions.discussionuser", + "pk": "d880c2fe-524c-4ab7-823c-506a946878df", + "fields": { + "created_on": "2023-11-06T19:47:58.092Z", + "modified_on": "2023-11-06T19:47:58.092Z", + "permission": "27dd61c2-16d5-41b0-8fc0-bb027ffb90f8", + "discussion": "3c2d1694-8db9-4f09-976b-e263f9d79c99", + "role": 0 + } + }, + { + "model": "discussions.discussionuser", + "pk": "305e0f95-f9db-40e1-88a6-a01fad34bcfd", + "fields": { + "created_on": "2023-11-06T21:06:20.719Z", + "modified_on": "2023-11-06T21:06:20.719Z", + "permission": "27dd61c2-16d5-41b0-8fc0-bb027ffb90f8", + "discussion": "36584c70-aaf9-4f5c-b0c3-0547bb23879d", + "role": 0 + } + }, + { + "model": "discussions.discussionuser", + "pk": "404ecb31-ae5e-4236-b95a-c6a1f8391030", + "fields": { + "created_on": "2023-11-06T21:07:30.891Z", + "modified_on": "2023-11-06T21:07:30.891Z", + "permission": "754bd473-2921-4755-958e-9bf29272ce2a", + "discussion": "36584c70-aaf9-4f5c-b0c3-0547bb23879d", + "role": 2 + } + }, + { + "model": "discussions.discussionuser", + "pk": "48ab983c-e297-4cc7-b63e-fc405aee882f", + "fields": { + "created_on": "2023-11-06T21:07:41.970Z", + "modified_on": "2023-11-06T21:07:41.970Z", + "permission": "ec9226f3-dae7-474d-8c31-a0276c19f63e", + "discussion": "36584c70-aaf9-4f5c-b0c3-0547bb23879d", + "role": 2 + } + }, + { + "model": "discussions.discussionuser", + "pk": "59c25277-ae51-47c4-b1dd-772cced4cbe8", + "fields": { + "created_on": "2023-11-06T19:58:56.940Z", + "modified_on": "2023-11-06T19:58:56.940Z", + "permission": "a7cf2af5-e43c-4219-b5b7-64708c279357", + "discussion": "9e2d9ea7-a130-4d07-ab54-de8284c8c922", + "role": 0 + } + }, + { + "model": "discussions.discussionuser", + "pk": "06aee1a5-62de-4c92-9615-d0159d9a2920", + "fields": { + "created_on": "2023-11-06T20:16:42.363Z", + "modified_on": "2023-11-06T20:16:42.363Z", + "permission": "fd7f2121-d407-4154-b442-736697568153", + "discussion": "9e2d9ea7-a130-4d07-ab54-de8284c8c922", + "role": 2 + } + }, + { + "model": "discussions.discussionuser", + "pk": "a6408afb-0c2d-470f-ba65-93396da238ca", + "fields": { + "created_on": "2023-11-06T20:21:51.140Z", + "modified_on": "2023-11-06T20:21:51.140Z", + "permission": "27dd61c2-16d5-41b0-8fc0-bb027ffb90f8", + "discussion": "9e2d9ea7-a130-4d07-ab54-de8284c8c922", + "role": 2 + } + }, + { + "model": "discussions.discussionuser", + "pk": "8b3e6648-b7b2-49a0-98db-cd39095bcfca", + "fields": { + "created_on": "2023-11-06T20:23:52.720Z", + "modified_on": "2023-11-06T20:23:52.720Z", + "permission": "6aac1cca-c02c-4fad-b8d9-9d36292f8dc7", + "discussion": "9e2d9ea7-a130-4d07-ab54-de8284c8c922", + "role": 1 + } + }, + { + "model": "discussions.discussionuser", + "pk": "c6dbfc4b-b1e9-4c83-9c7c-b5927a00edef", + "fields": { + "created_on": "2023-11-06T20:24:02.772Z", + "modified_on": "2023-11-06T20:24:02.772Z", + "permission": "b1fea9b3-f216-4119-8f86-50115c49dfd4", + "discussion": "9e2d9ea7-a130-4d07-ab54-de8284c8c922", + "role": 1 + } + }, + { + "model": "discussions.discussionmessage", + "pk": "77fa59a5-6f29-4e55-b68f-3fbf4f7b08e3", + "fields": { + "created_on": "2023-11-06T19:47:58.077Z", + "modified_on": "2023-11-06T19:47:58.078Z", + "discussion": "3c2d1694-8db9-4f09-976b-e263f9d79c99", + "user": "linalawson@chats.weni.ai", + "text": "Streamlined holistic archive" + } + }, + { + "model": "discussions.discussionmessage", + "pk": "96d9aaec-0914-46e4-919d-dc5c6860be7a", + "fields": { + "created_on": "2023-11-06T19:58:56.927Z", + "modified_on": "2023-11-06T19:58:56.927Z", + "discussion": "9e2d9ea7-a130-4d07-ab54-de8284c8c922", + "user": "agentqueue@chats.weni.ai", + "text": "Switchable client-server infrastructure" + } + }, + { + "model": "discussions.discussionmessage", + "pk": "4a4d2b8d-6efc-4158-96bb-aec38883fa32", + "fields": { + "created_on": "2023-11-06T21:06:20.705Z", + "modified_on": "2023-11-06T21:06:20.705Z", + "discussion": "36584c70-aaf9-4f5c-b0c3-0547bb23879d", + "user": "linalawson@chats.weni.ai", + "text": "Multi-lateral mobile instruction set" + } + } +] diff --git a/chats/settings.py b/chats/settings.py index fb5b0581..49728c58 100644 --- a/chats/settings.py +++ b/chats/settings.py @@ -65,6 +65,7 @@ "chats.apps.dashboard", "chats.apps.event_driven", "chats.apps.history", + "chats.apps.discussions", "chats.core", # third party apps "channels", @@ -363,6 +364,8 @@ CHATS_FLOWS_TAG = env.str("CHATS_FLOWS_TAG", default="chats") CHATS_CACHE_TIME = env.int("CHATS_CACHE_TIME", default=1 * 60 * 60) +DISCUSSION_AGENTS_LIMIT = env.int("DISCUSSION_AGENTS_LIMIT", default=5) + # Celery METRICS_CUSTOM_QUEUE = env("METRICS_CUSTOM_QUEUE", default="celery") diff --git a/docs/diagrams/discussions.drawio b/docs/diagrams/discussions.drawio new file mode 100644 index 00000000..f72b356e --- /dev/null +++ b/docs/diagrams/discussions.drawio @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/poetry.lock b/poetry.lock index 9cf62f33..bfef8e76 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1120,6 +1120,17 @@ sql-other = ["SQLAlchemy (>=1.4.16)"] test = ["hypothesis (>=6.34.2)", "pytest (>=7.0.0)", "pytest-xdist (>=2.2.0)", "pytest-asyncio (>=0.17.0)"] xml = ["lxml (>=4.6.3)"] +[[package]] +name = "parameterized" +version = "0.9.0" +description = "Parameterized testing with any Python test framework" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["jinja2"] + [[package]] name = "parso" version = "0.8.3" @@ -1827,7 +1838,7 @@ python-versions = ">=3.6" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "417e11e22c478190e112501a7d90e5644351419cd4e5eb8488323f2d9f15ecfa" +content-hash = "3221629b05496d2c207dff31ed6d2702513bbeaf6bc3bd93aedb10e395e82c7b" [metadata.files] amqp = [] @@ -1913,6 +1924,7 @@ numpy = [] openpyxl = [] packaging = [] pandas = [] +parameterized = [] parso = [] pathspec = [] pbr = [] diff --git a/pyproject.toml b/pyproject.toml index 91d49996..14387c16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ elastic-apm = "^6.10.1" celery = "^5.3.1" django-celery-results = "^2.5.1" django-celery-beat = "^2.5.0" +parameterized = "^0.9.0" [tool.poetry.dev-dependencies] pytest = "^5.2"