Compare commits
21 Commits
47833176e2
...
7d6618155f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d6618155f | ||
|
|
11605e6e6c | ||
|
|
8a1a66a05d | ||
|
|
70555fa93a | ||
| 674eafe65b | |||
| b03d2e1c5e | |||
|
|
c5624e361d | ||
| c44b08bb28 | |||
|
|
0508a49c1d | ||
| 8b97ca53bb | |||
|
|
9f7b29fa13 | ||
| b4a3243e9c | |||
|
|
3c9438aff6 | ||
| a3c67c5bfb | |||
|
|
a2684ff749 | ||
| 91bbb6b6e5 | |||
|
|
ae9f10aa4d | ||
| f2dc2e1d52 | |||
|
|
10646e2ebf | ||
| 93f6e79f58 | |||
| d1aad04ab8 |
@@ -73,6 +73,9 @@ STORAGE_BUCKET_STATIC=name
|
|||||||
STORAGE_PATH=127.0.0.1:8081/bucket/
|
STORAGE_PATH=127.0.0.1:8081/bucket/
|
||||||
STORAGE_PROTOCOL=http:
|
STORAGE_PROTOCOL=http:
|
||||||
|
|
||||||
|
# Didox configs
|
||||||
|
DIDOX_PARTNER_TOKEN=...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Celery configs
|
# Celery configs
|
||||||
@@ -9,7 +9,7 @@ import environ
|
|||||||
environ.Env.read_env(os.path.join(".env"))
|
environ.Env.read_env(os.path.join(".env"))
|
||||||
|
|
||||||
env = environ.Env(
|
env = environ.Env(
|
||||||
DEBUG=(bool, False),
|
DEBUG=(bool, True),
|
||||||
CACHE_TIME=(int, 180),
|
CACHE_TIME=(int, 180),
|
||||||
OTP_EXPIRE_TIME=(int, 2),
|
OTP_EXPIRE_TIME=(int, 2),
|
||||||
VITE_LIVE=(bool, False),
|
VITE_LIVE=(bool, False),
|
||||||
@@ -26,4 +26,5 @@ env = environ.Env(
|
|||||||
OTP_SERVICE="EskizService",
|
OTP_SERVICE="EskizService",
|
||||||
PROJECT_ENV=(str, "prod"),
|
PROJECT_ENV=(str, "prod"),
|
||||||
SILK_ENABLED=(bool, False),
|
SILK_ENABLED=(bool, False),
|
||||||
|
DIDOX_PARTNER_TOKEN=(str),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"django.contrib.postgres",
|
||||||
] + APPS
|
] + APPS
|
||||||
|
|
||||||
MODULES = [app for app in MODULES if isinstance(app, str)]
|
MODULES = [app for app in MODULES if isinstance(app, str)]
|
||||||
@@ -165,6 +166,8 @@ SITE_URL = env.str("SITE_URL", default="http://localhost:8055")
|
|||||||
MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en")
|
MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en")
|
||||||
MODELTRANSLATION_DEFAULT_LANGUAGE = "uz"
|
MODELTRANSLATION_DEFAULT_LANGUAGE = "uz"
|
||||||
|
|
||||||
|
DIDOX_PARTNER_TOKEN = env.str("DIDOX_PARTNER_TOKEN")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JST_LANGUAGES = [
|
JST_LANGUAGES = [
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from config.env import env
|
|||||||
|
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
return HttpResponse("OK: #503c16bdb81281f1483c938c26a17bbdb9ec6e2a")
|
return HttpResponse("OK: #8a1a66a05dfdf0a268b2db2da73cc5046266f4c1")
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
|||||||
async def receive(self, text_data):
|
async def receive(self, text_data):
|
||||||
user = self.scope.get("user")
|
user = self.scope.get("user")
|
||||||
if not user or isinstance(user, AnonymousUser):
|
if not user or isinstance(user, AnonymousUser):
|
||||||
await self.close(code=4001)
|
await self.close(code=401)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -53,13 +53,10 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
|||||||
message_type = data.get("message_type", "text")
|
message_type = data.get("message_type", "text")
|
||||||
text = (data.get("text") or "").strip()
|
text = (data.get("text") or "").strip()
|
||||||
|
|
||||||
# Matn xabari uchun text majburiy
|
|
||||||
if message_type == "text" and not text:
|
if message_type == "text" and not text:
|
||||||
await self.send(text_data=json.dumps({"error": "Matn bo'sh bo'lishi mumkin emas."}))
|
await self.send(text_data=json.dumps({"error": "Matn bo'sh bo'lishi mumkin emas."}))
|
||||||
return
|
return
|
||||||
|
|
||||||
# WS orqali faqat matn + caption saqlanadi.
|
|
||||||
# Fayl yuklash uchun REST /chat/messages/ POST ishlatiladi.
|
|
||||||
if message_type != "text":
|
if message_type != "text":
|
||||||
await self.send(
|
await self.send(
|
||||||
text_data=json.dumps(
|
text_data=json.dumps(
|
||||||
@@ -68,22 +65,29 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# DB ga saqlash — post_save signal WS ga broadcast qiladi
|
|
||||||
await self._save_message(user, text)
|
await self._save_message(user, text)
|
||||||
|
|
||||||
async def chat_message(self, event):
|
# Uz
|
||||||
await self.send(
|
# Bu funksiya ishlatilmayapti, shuning uchun commentga olib qoydim, bu funksiya orniga /core/apps/chat/tasks/message.py ichida rest api yordamida message
|
||||||
text_data=json.dumps(
|
# yuborilsa ishlatiladigan task bor.
|
||||||
{
|
|
||||||
"id": event["id"],
|
# En
|
||||||
"message_type": event["message_type"],
|
# This function is not used, so I commented it out. Instead, a task is used in /core/apps/chat/tasks/message.py
|
||||||
"text": event["text"],
|
# to send message when message is added from REST API.
|
||||||
"file_url": event["file_url"],
|
|
||||||
"sender": event["sender"],
|
# async def chat_message(self, event):
|
||||||
"created_at": event["created_at"],
|
# await self.send(
|
||||||
}
|
# text_data=json.dumps(
|
||||||
)
|
# {
|
||||||
)
|
# "id": event["id"],
|
||||||
|
# "message_type": event["message_type"],
|
||||||
|
# "text": event["text"],
|
||||||
|
# "file_url": event["file_url"],
|
||||||
|
# "sender": event["sender"],
|
||||||
|
# "created_at": event["created_at"],
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def _save_message(self, user, text):
|
def _save_message(self, user, text):
|
||||||
|
|||||||
@@ -78,5 +78,6 @@ class CreateChatmessageSerializer(serializers.ModelSerializer):
|
|||||||
request = self.context["request"]
|
request = self.context["request"]
|
||||||
message = super().create(validated_data)
|
message = super().create(validated_data)
|
||||||
file_url = request.build_absolute_uri(message.file.url) if message.file else None
|
file_url = request.build_absolute_uri(message.file.url) if message.file else None
|
||||||
send_message_to_chat.delay(message.id, file_url)
|
avatar_url = request.build_absolute_uri(self.context['request'].user.avatar.url) if self.context['request'].user.avatar else None
|
||||||
|
send_message_to_chat.delay(message.id, file_url, avatar_url)
|
||||||
return message
|
return message
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from core.apps.chat.models import ChatmessageModel
|
|||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def send_message_to_chat(message_id, file_url):
|
def send_message_to_chat(message_id, file_url, avatar_url):
|
||||||
try:
|
try:
|
||||||
message = ChatmessageModel.objects.get(id=message_id)
|
message = ChatmessageModel.objects.get(id=message_id)
|
||||||
except ChatmessageModel.DoesNotExist:
|
except ChatmessageModel.DoesNotExist:
|
||||||
@@ -24,12 +24,12 @@ def send_message_to_chat(message_id, file_url):
|
|||||||
"id": sender_obj.id,
|
"id": sender_obj.id,
|
||||||
"full_name": full_name,
|
"full_name": full_name,
|
||||||
"role": sender_obj.role,
|
"role": sender_obj.role,
|
||||||
|
"phone": sender_obj.phone,
|
||||||
|
"avatar": avatar_url,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
sender_data = None
|
sender_data = None
|
||||||
|
|
||||||
# file_url = request.build_absolute_uri(message.file.url) if message.file else None
|
|
||||||
|
|
||||||
async_to_sync(channel_layer.group_send)(
|
async_to_sync(channel_layer.group_send)(
|
||||||
f"chat_room_{message.room_id}",
|
f"chat_room_{message.room_id}",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from .views import (
|
|||||||
AutoEvaluationListAppraisersView,
|
AutoEvaluationListAppraisersView,
|
||||||
AutoEvaluationSetAppraisersView,
|
AutoEvaluationSetAppraisersView,
|
||||||
AutoEvaluationRemoveAppraisersView,
|
AutoEvaluationRemoveAppraisersView,
|
||||||
|
DidoxCompanyInfoAPIView,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,4 +61,9 @@ urlpatterns = [
|
|||||||
path("<int:id>/remove/", AutoEvaluationRemoveAppraisersView.as_view(), name="auto-evaluation-remove-appraisers"),
|
path("<int:id>/remove/", AutoEvaluationRemoveAppraisersView.as_view(), name="auto-evaluation-remove-appraisers"),
|
||||||
]
|
]
|
||||||
)),
|
)),
|
||||||
|
path(
|
||||||
|
"didox/info/<int:tin>/",
|
||||||
|
DidoxCompanyInfoAPIView.as_view(),
|
||||||
|
name="didox-info"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ from .report import * # noqa
|
|||||||
from .request import * # noqa
|
from .request import * # noqa
|
||||||
from .valuation import * # noqa
|
from .valuation import * # noqa
|
||||||
from .vehicle import * # noqa
|
from .vehicle import * # noqa
|
||||||
|
from .didox import * # noqa
|
||||||
|
|||||||
51
core/apps/evaluation/views/didox.py
Normal file
51
core/apps/evaluation/views/didox.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from rest_framework.generics import GenericAPIView
|
||||||
|
|
||||||
|
|
||||||
|
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||||
|
|
||||||
|
from core.services.didox import DidoxService
|
||||||
|
|
||||||
|
|
||||||
|
class DidoxCompanyInfoAPIView(GenericAPIView):
|
||||||
|
authentication_classes = []
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
tags=["Didox"],
|
||||||
|
summary="Get company info by TIN",
|
||||||
|
description="TIN/JSHSHIR orqali Didoxdan ma'lumot olish",
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(
|
||||||
|
name="tin",
|
||||||
|
type=int,
|
||||||
|
location=OpenApiParameter.PATH,
|
||||||
|
required=True,
|
||||||
|
description="TIN / STIR / INN / JSHSHIR"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses={200: dict},
|
||||||
|
)
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
tin = kwargs.get("tin")
|
||||||
|
|
||||||
|
# 🔥 TYPE CHECK
|
||||||
|
try:
|
||||||
|
tin = int(tin)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return Response(
|
||||||
|
{"detail": "TIN must be a valid integer"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
data = DidoxService.get_company_info(tin)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return Response(
|
||||||
|
{"detail": "Didox service unavailable"},
|
||||||
|
status=status.HTTP_502_BAD_GATEWAY
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
from .otp import * # noqa
|
from .otp import * # noqa
|
||||||
from .sms import * # noqa
|
from .sms import * # noqa
|
||||||
from .user import * # noqa
|
from .user import * # noqa
|
||||||
|
from .didox import * # noqa
|
||||||
27
core/services/didox.py
Normal file
27
core/services/didox.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DidoxService:
|
||||||
|
BASE_URL = "https://testapi3.didox.uz/v1"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_company_info(cls, tin: int) -> dict:
|
||||||
|
url = f"{cls.BASE_URL}/utils/info/{tin}"
|
||||||
|
|
||||||
|
headers = {"Partner-Authorization": settings.DIDOX_PARTNER_TOKEN}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url=url, headers=headers, timeout=15,verify=False)
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.exception(f"Didox API error: {str(e)}")
|
||||||
|
return {}
|
||||||
@@ -84,7 +84,7 @@ services:
|
|||||||
max-file: "5"
|
max-file: "5"
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: husanjon/sifatbaho:82
|
image: husanjon/sifatbaho:93
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -129,7 +129,7 @@ services:
|
|||||||
max-file: "5"
|
max-file: "5"
|
||||||
|
|
||||||
celery:
|
celery:
|
||||||
image: husanjon/sifatbaho:82
|
image: husanjon/sifatbaho:93
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Reference in New Issue
Block a user