Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -85,9 +85,8 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||||||
tokens = OutstandingToken.objects.filter(user=self)
|
tokens = OutstandingToken.objects.filter(user=self)
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
BlacklistedToken.objects.get_or_create(token=token)
|
BlacklistedToken.objects.get_or_create(token=token)
|
||||||
print('done')
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ridi dadash")
|
print(f"block list error: {e}")
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ghasedak_sms
|
|||||||
class SendOTPView(APIView):
|
class SendOTPView(APIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
tags=["Authentication"],
|
||||||
request={
|
request={
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -68,9 +69,9 @@ class SendOTPView(APIView):
|
|||||||
|
|
||||||
class CustomTokenObtainPairView(TokenObtainPairView):
|
class CustomTokenObtainPairView(TokenObtainPairView):
|
||||||
serializer_class = CustomTokenObtainPairSerializer
|
serializer_class = CustomTokenObtainPairSerializer
|
||||||
# @extend_schema(
|
@extend_schema(
|
||||||
# tags=["Authentication"]
|
tags=["Authentication"]
|
||||||
# )
|
)
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
phone = request.data.get("phone")
|
phone = request.data.get("phone")
|
||||||
otp = request.data.get("otp")
|
otp = request.data.get("otp")
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import *
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ProductChatModel)
|
||||||
|
class ProductChatAdmin(ModelAdmin):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ChatConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'chat'
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-01-01 17:58
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0005_categorymodel'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProductChatModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('thread', models.CharField(blank=True, max_length=400, null=True)),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='product.productmodel')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('user', 'product')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
from django.db import models
|
||||||
|
from account.models import User
|
||||||
|
from product.models import ProductModel
|
||||||
|
from django.conf import settings
|
||||||
|
import openai
|
||||||
|
from time import sleep
|
||||||
|
from product.serializers import ProductChatSerializer
|
||||||
|
|
||||||
|
|
||||||
|
ASSISTANT_ID = 'asst_1wOnCKncEHkOfp0FjOIz4Xkp'
|
||||||
|
class ProductChatModel(models.Model):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE)
|
||||||
|
thread = models.CharField(max_length=400, blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ['user', 'product']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.user} - {self.product}'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.thread:
|
||||||
|
client = openai.OpenAI(api_key=settings.OPENAI_API_KEY)
|
||||||
|
product_json = ProductChatSerializer(instance=self.product).data
|
||||||
|
print(product_json)
|
||||||
|
try:
|
||||||
|
|
||||||
|
thread = client.beta.threads.create(
|
||||||
|
messages=[
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
#TODO update first message
|
||||||
|
"content": f"this is the start of the chat greet the user this chat is about the product with given detail: {product_json}",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
run = client.beta.threads.runs.create(
|
||||||
|
thread_id=thread.id,
|
||||||
|
assistant_id=ASSISTANT_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
while run.status != "completed":
|
||||||
|
run = client.beta.threads.runs.retrieve(
|
||||||
|
thread_id=thread.id,
|
||||||
|
run_id=run.id
|
||||||
|
)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
self.thread = thread.id
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f'error in chat class: {e}')
|
||||||
|
raise ValueError(f"Error creating OpenAI thread: {e}")
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('product/<int:pk>', views.ProductChatView.as_view(), name='product-chat-view'),
|
||||||
|
]
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
from django.core.paginator import Paginator
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.db.models import Q
|
||||||
|
from time import sleep
|
||||||
|
import json
|
||||||
|
from drf_spectacular.utils import (
|
||||||
|
extend_schema,
|
||||||
|
extend_schema_view,
|
||||||
|
OpenApiParameter,
|
||||||
|
OpenApiExample,
|
||||||
|
)
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
|
||||||
|
from .models import ProductModel, ProductChatModel
|
||||||
|
from utils.pagination import StructurePagination
|
||||||
|
import openai
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
ASSISTANT_ID = 'asst_1wOnCKncEHkOfp0FjOIz4Xkp'
|
||||||
|
|
||||||
|
|
||||||
|
# Serializer for the `new_message` field in POST requests
|
||||||
|
class NewMessageSerializer(serializers.Serializer):
|
||||||
|
new_message = serializers.CharField(
|
||||||
|
required=True,
|
||||||
|
help_text="The message content to send to the chat."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Custom Pagination Class
|
||||||
|
class StructurePagination(LimitOffsetPagination):
|
||||||
|
default_limit = 10
|
||||||
|
max_limit = 100
|
||||||
|
|
||||||
|
|
||||||
|
# Documentation for the ProductChatView
|
||||||
|
@extend_schema_view(
|
||||||
|
get=extend_schema(
|
||||||
|
summary="Retrieve messages for a product chat",
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(
|
||||||
|
name="limit",
|
||||||
|
description="Number of results to return per page.",
|
||||||
|
required=False,
|
||||||
|
type=int,
|
||||||
|
default=10,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="offset",
|
||||||
|
description="The starting position of the results.",
|
||||||
|
required=False,
|
||||||
|
type=int,
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses={
|
||||||
|
200: OpenApiExample(
|
||||||
|
"Chat messages retrieved",
|
||||||
|
value={
|
||||||
|
"messages": [
|
||||||
|
{"sender": "user", "content": "Hello!"},
|
||||||
|
{"sender": "assistant", "content": "How can I help you?"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
post=extend_schema(
|
||||||
|
summary="Send a new message in the product chat",
|
||||||
|
request=NewMessageSerializer,
|
||||||
|
responses={
|
||||||
|
200: OpenApiExample(
|
||||||
|
"Message sent successfully",
|
||||||
|
value={
|
||||||
|
"messages": [
|
||||||
|
{"sender": "user", "content": "Hello!"},
|
||||||
|
{"sender": "assistant", "content": "How can I help you?"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
400: OpenApiExample(
|
||||||
|
"Error example",
|
||||||
|
value={"detail": "پیام جدید نمیتواند خالی باشد"},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
class ProductChatView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
pagination_class = StructurePagination
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
"""
|
||||||
|
Retrieve all messages for a product chat.
|
||||||
|
"""
|
||||||
|
user = request.user
|
||||||
|
product = get_object_or_404(ProductModel, id=pk)
|
||||||
|
chat, created = ProductChatModel.objects.get_or_create(user=user, product=product)
|
||||||
|
|
||||||
|
client = openai.OpenAI(api_key=settings.OPENAI_API_KEY)
|
||||||
|
message_response = client.beta.threads.messages.list(thread_id=chat.thread)
|
||||||
|
|
||||||
|
# Format messages
|
||||||
|
formatted_messages = []
|
||||||
|
counter = 1
|
||||||
|
for message in message_response.data:
|
||||||
|
for content in message.content:
|
||||||
|
if content.type == "text" and 'this is the start of the chat greet the user this chat is about the product with given detail:' not in content.text.value:
|
||||||
|
sender = 'user' if message.role == 'user' else 'ai'
|
||||||
|
formatted_messages.append({'sender': sender, 'content': content.text.value, 'id': counter})
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
paginator = StructurePagination()
|
||||||
|
paginated_messages = paginator.paginate_queryset(formatted_messages, request)
|
||||||
|
return paginator.get_paginated_response(paginated_messages)
|
||||||
|
|
||||||
|
def post(self, request, pk):
|
||||||
|
"""
|
||||||
|
Send a new message in the product chat.
|
||||||
|
"""
|
||||||
|
user = request.user
|
||||||
|
product = get_object_or_404(ProductModel, id=pk)
|
||||||
|
chat, created = ProductChatModel.objects.get_or_create(user=user, product=product)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
return Response({'detail': 'چت این کاربر با این محصول هنوز ساخته نشده'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
new_message = request.data.get('new_message', '').strip()
|
||||||
|
if not new_message:
|
||||||
|
return Response({'detail': 'پیام جدید نمیتواند خالی باشد'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
client = openai.OpenAI(api_key=settings.OPENAI_API_KEY)
|
||||||
|
|
||||||
|
# Send the user message
|
||||||
|
client.beta.threads.messages.create(
|
||||||
|
thread_id=chat.thread,
|
||||||
|
role="user",
|
||||||
|
content=new_message
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start the assistant's run
|
||||||
|
run = client.beta.threads.runs.create(thread_id=chat.thread, assistant_id=ASSISTANT_ID)
|
||||||
|
while run.status != "completed":
|
||||||
|
run = client.beta.threads.runs.retrieve(thread_id=chat.thread, run_id=run.id)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
# Fetch the updated messages
|
||||||
|
message_response = client.beta.threads.messages.list(thread_id=chat.thread)
|
||||||
|
formatted_messages = []
|
||||||
|
for message in message_response.data:
|
||||||
|
for content in message.content:
|
||||||
|
if content.type == "text" and 'this is the start of the chat greet the user this chat is about the product with given detail:' not in content.text.value:
|
||||||
|
sender = 'user' if message.role == 'user' else 'ai'
|
||||||
|
formatted_messages.append({'sender': sender, 'content': content.text.value})
|
||||||
|
|
||||||
|
formatted_messages = formatted_messages[:2]
|
||||||
|
return Response(formatted_messages, status=status.HTTP_201_CREATED)
|
||||||
@@ -13,7 +13,7 @@ load_dotenv(".env.local")
|
|||||||
|
|
||||||
DOMAIN = os.getenv("DOMAIN")
|
DOMAIN = os.getenv("DOMAIN")
|
||||||
API_DOMAIN = os.getenv("API_DOMAIN")
|
API_DOMAIN = os.getenv("API_DOMAIN")
|
||||||
|
OPENAI_API_KEY = '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'
|
||||||
# TODO update telegram bot token
|
# TODO update telegram bot token
|
||||||
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||||
|
|
||||||
@@ -96,6 +96,7 @@ INSTALLED_APPS = [
|
|||||||
'product',
|
'product',
|
||||||
'account',
|
'account',
|
||||||
'ticket',
|
'ticket',
|
||||||
|
'chat',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -305,7 +306,7 @@ UNFOLD = {
|
|||||||
"items": [
|
"items": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": _("users"),
|
"title": _("کاربران"),
|
||||||
"icon": "person",
|
"icon": "person",
|
||||||
"link": reverse_lazy("admin:account_user_changelist"),
|
"link": reverse_lazy("admin:account_user_changelist"),
|
||||||
},
|
},
|
||||||
@@ -313,6 +314,21 @@ UNFOLD = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": _("بخش هوش مصنوعی"),
|
||||||
|
"separator": True,
|
||||||
|
"collapsible": True,
|
||||||
|
"items": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": _("چت محصول"),
|
||||||
|
"icon": "chat",
|
||||||
|
"link": reverse_lazy("admin:chat_productchatmodel_changelist"),
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ urlpatterns = [
|
|||||||
|
|
||||||
|
|
||||||
path('token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
|
path('token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
|
||||||
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
# path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('schema/', SpectacularAPIView.as_view(), name='schema'),
|
path('schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||||
# path('comment/<int:pk>', views.CommentView.as_view(), name='comment-list'),
|
# path('comment/<int:pk>', views.CommentView.as_view(), name='comment-list'),
|
||||||
path('products/', include('product.urls')),
|
path('products/', include('product.urls')),
|
||||||
path('accounts/', include('account.urls')),
|
path('accounts/', include('account.urls')),
|
||||||
|
path('chat/', include('chat.urls')),
|
||||||
path('tickets/', include('ticket.urls')),
|
path('tickets/', include('ticket.urls')),
|
||||||
path('', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
path('', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 MiB |
@@ -5,7 +5,10 @@ class ProductSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ProductModel
|
model = ProductModel
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
class ProductChatSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ProductModel
|
||||||
|
fields = ['name', 'description', 'price', 'in_stock', 'discount', ]
|
||||||
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
class CommentSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
aiohappyeyeballs==2.4.0
|
aiohappyeyeballs==2.4.0
|
||||||
aiohttp==3.10.5
|
aiohttp==3.10.5
|
||||||
aiosignal==1.3.1
|
aiosignal==1.3.1
|
||||||
|
annotated-types==0.7.0
|
||||||
anyio==4.6.0
|
anyio==4.6.0
|
||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
attrs==24.2.0
|
attrs==24.2.0
|
||||||
@@ -10,6 +11,7 @@ charset-normalizer==3.3.2
|
|||||||
cryptography==43.0.3
|
cryptography==43.0.3
|
||||||
defusedxml==0.8.0rc2
|
defusedxml==0.8.0rc2
|
||||||
diff-match-patch==20230430
|
diff-match-patch==20230430
|
||||||
|
distro==1.9.0
|
||||||
Django==5.1.2
|
Django==5.1.2
|
||||||
django-cleanup==8.1.0
|
django-cleanup==8.1.0
|
||||||
django-cors-headers==4.4.0
|
django-cors-headers==4.4.0
|
||||||
@@ -27,6 +29,7 @@ Faker==28.4.1
|
|||||||
frozenlist==1.4.1
|
frozenlist==1.4.1
|
||||||
geoip2==4.8.0
|
geoip2==4.8.0
|
||||||
ghasedak_sms==1.0.3
|
ghasedak_sms==1.0.3
|
||||||
|
ghasedakpack==0.1.13
|
||||||
gnupg==2.3.1
|
gnupg==2.3.1
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
httpagentparser==1.9.5
|
httpagentparser==1.9.5
|
||||||
@@ -36,15 +39,19 @@ idna==3.10
|
|||||||
inflection==0.5.1
|
inflection==0.5.1
|
||||||
jalali_core==1.0.0
|
jalali_core==1.0.0
|
||||||
jdatetime==5.0.0
|
jdatetime==5.0.0
|
||||||
|
jiter==0.8.2
|
||||||
jsonschema==4.23.0
|
jsonschema==4.23.0
|
||||||
jsonschema-specifications==2024.10.1
|
jsonschema-specifications==2024.10.1
|
||||||
maxminddb==2.6.2
|
maxminddb==2.6.2
|
||||||
multidict==6.1.0
|
multidict==6.1.0
|
||||||
oauthlib==3.2.2
|
oauthlib==3.2.2
|
||||||
|
openai==1.58.1
|
||||||
pillow==10.4.0
|
pillow==10.4.0
|
||||||
psutil==6.0.0
|
psutil==6.0.0
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
|
pydantic==2.10.4
|
||||||
|
pydantic_core==2.27.2
|
||||||
PyJWT==2.9.0
|
PyJWT==2.9.0
|
||||||
pyTelegramBotAPI==4.23.0
|
pyTelegramBotAPI==4.23.0
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
@@ -64,6 +71,8 @@ social-auth-app-django==5.4.2
|
|||||||
social-auth-core==4.5.4
|
social-auth-core==4.5.4
|
||||||
sqlparse==0.5.1
|
sqlparse==0.5.1
|
||||||
tablib==3.5.0
|
tablib==3.5.0
|
||||||
|
tqdm==4.67.1
|
||||||
|
typing_extensions==4.12.2
|
||||||
tzdata==2024.1
|
tzdata==2024.1
|
||||||
uritemplate==4.1.1
|
uritemplate==4.1.1
|
||||||
urllib3==2.2.3
|
urllib3==2.2.3
|
||||||
|
|||||||
@@ -46,5 +46,3 @@ otp_input = ghasedak_sms.SendOtpInput(
|
|||||||
# Send the OTP SMS
|
# Send the OTP SMS
|
||||||
response = sms_api.send_otp_sms(otp_input)
|
response = sms_api.send_otp_sms(otp_input)
|
||||||
|
|
||||||
# Print the response to check the result
|
|
||||||
print(response)
|
|
||||||
+2
-2
@@ -3,7 +3,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
ports:
|
ports:
|
||||||
- "80:3000"
|
- "3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- django
|
- django
|
||||||
networks:
|
networks:
|
||||||
@@ -13,7 +13,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./backend
|
context: ./backend
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8001:8000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Reference in New Issue
Block a user