This commit is contained in:
2026-03-24 20:10:48 +03:30
parent 7ab1ee3104
commit 0eda50f1c3
100 changed files with 2081 additions and 104 deletions
+32
View File
@@ -3,11 +3,16 @@ Account API module.
CRUD endpoints for user account profile.
"""
from rest_framework import serializers
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, extend_schema_view
from auth.serializers import AuthUserSerializer
from config.swagger import code_response
from .serializers import UpdateProfileSerializer
@@ -25,6 +30,13 @@ def _auth_user_to_data(user):
}
@extend_schema_view(
patch=extend_schema(
tags=["Account"],
request=UpdateProfileSerializer,
responses={200: code_response("ProfileUpdateResponse", data=AuthUserSerializer())},
),
)
class ProfileView(APIView):
"""
PATCH /api/account/profile/
@@ -63,6 +75,26 @@ class ProfileView(APIView):
)
@extend_schema_view(
get=extend_schema(
tags=["Account"],
responses={200: code_response("AccountGetResponse", data=serializers.JSONField())},
),
post=extend_schema(
tags=["Account"],
request=OpenApiTypes.OBJECT,
responses={200: code_response("AccountCreateResponse")},
),
patch=extend_schema(
tags=["Account"],
request=OpenApiTypes.OBJECT,
responses={200: code_response("AccountUpdateResponse")},
),
delete=extend_schema(
tags=["Account"],
responses={200: code_response("AccountDeleteResponse")},
),
)
class AccountView(APIView):
"""
Account CRUD endpoints. Dispatch by HTTP method and path (uuid for detail/update/delete).
+41
View File
@@ -5,13 +5,17 @@ from django.conf import settings
from django.core.cache import cache
from django.core.signing import BadSignature, SignatureExpired, TimestampSigner
from django.db import IntegrityError
from rest_framework import serializers
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework_simplejwt.tokens import RefreshToken
from account.models import User
from config.swagger import code_response
from .serializers import (
AuthUserSerializer,
LoginSerializer,
RegisterSerializer,
RequestOTPSerializer,
@@ -38,6 +42,16 @@ def _auth_user_to_data(user):
}
@extend_schema_view(
post=extend_schema(
tags=["Authentication"],
request=RegisterSerializer,
responses={
201: code_response("RegisterResponse", data=AuthUserSerializer(), token=True),
400: code_response("RegisterErrorResponse"),
},
),
)
class RegisterView(APIView):
"""
POST /api/auth/register/
@@ -92,6 +106,16 @@ class RegisterView(APIView):
)
@extend_schema_view(
post=extend_schema(
tags=["Authentication"],
request=LoginSerializer,
responses={
200: code_response("LoginResponse", data=AuthUserSerializer(), token=True),
401: code_response("LoginErrorResponse"),
},
),
)
class LoginView(APIView):
"""
POST /api/auth/login/
@@ -131,6 +155,23 @@ class LoginView(APIView):
)
@extend_schema_view(
post=extend_schema(
tags=["Authentication"],
request=RequestOTPSerializer,
responses={
200: code_response(
"RequestOtpResponse",
extra_fields={
"token": serializers.CharField(),
"sms_warning": serializers.CharField(required=False),
"debug_otp": serializers.CharField(required=False),
},
),
400: code_response("RequestOtpErrorResponse"),
},
),
)
class AuthenticationView(APIView):
"""
Single view for auth flows: request-otp and verify-otp.
+14
View File
@@ -35,6 +35,8 @@ INSTALLED_APPS = [
"fertilization_recommendation",
"farm_ai_assistant",
"rest_framework",
"drf_spectacular",
"drf_spectacular_sidecar",
"corsheaders",
]
@@ -113,6 +115,18 @@ REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
SPECTACULAR_SETTINGS = {
"TITLE": "CropLogic API",
"DESCRIPTION": "Swagger/OpenAPI documentation for all CropLogic API endpoints.",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
"SWAGGER_UI_DIST": "SIDECAR",
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
"REDOC_DIST": "SIDECAR",
"SCHEMA_PATH_PREFIX": r"/api/",
}
+31
View File
@@ -0,0 +1,31 @@
from rest_framework import serializers
from drf_spectacular.utils import inline_serializer
class TokenPairSerializer(serializers.Serializer):
access = serializers.CharField()
refresh = serializers.CharField()
def code_response(name, data=None, token=False, extra_fields=None):
fields = {
"code": serializers.IntegerField(),
"msg": serializers.CharField(),
}
if data is not None:
fields["data"] = data
if token:
fields["token"] = TokenPairSerializer()
if extra_fields:
fields.update(extra_fields)
return inline_serializer(name=name, fields=fields)
def status_response(name, data=None):
fields = {
"status": serializers.CharField(default="success"),
}
if data is not None:
fields["data"] = data
return inline_serializer(name=name, fields=fields)
+4
View File
@@ -1,8 +1,12 @@
from django.contrib import admin
from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns = [
path("admin/", admin.site.urls),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/docs/swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
path("api/docs/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
path("api/auth/", include("auth.urls")),
path("api/account/", include("account.urls")),
path("api/sensor-hub/", include("sensor_hub.urls")),
+60 -31
View File
@@ -1,16 +1,17 @@
"""
Crop Zoning API views.
Plain Django only; no DRF. No database. All responses are static mock data.
No database. All responses are static mock data.
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt on POST so frontend can call without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from config.swagger import status_response
from .mock_data import (
AREA_RESPONSE_DATA,
PRODUCTS_RESPONSE_DATA,
@@ -22,7 +23,7 @@ from .mock_data import (
)
class AreaView(View):
class AreaView(APIView):
"""
GET endpoint for fixed land area (GeoJSON polygon).
@@ -40,14 +41,18 @@ class AreaView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Crop Zoning"],
responses={200: status_response("CropZoningAreaResponse", data=serializers.JSONField())},
)
def get(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": AREA_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
class ProductsView(View):
class ProductsView(APIView):
"""
GET endpoint for list of crop products and colors.
@@ -67,15 +72,18 @@ class ProductsView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Crop Zoning"],
responses={200: status_response("CropZoningProductsResponse", data=serializers.JSONField())},
)
def get(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": PRODUCTS_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesInitialView(View):
class ZonesInitialView(APIView):
"""
POST endpoint for initial zone data (map + hover/tooltip).
@@ -99,15 +107,19 @@ class ZonesInitialView(View):
not used in the response.
"""
@extend_schema(
tags=["Crop Zoning"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("CropZoningZonesInitialResponse", data=serializers.JSONField())},
)
def post(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": ZONES_INITIAL_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesWaterNeedView(View):
class ZonesWaterNeedView(APIView):
"""
POST endpoint for water need per zone (water need layer).
@@ -126,15 +138,19 @@ class ZonesWaterNeedView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Crop Zoning"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("CropZoningZonesWaterNeedResponse", data=serializers.JSONField())},
)
def post(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": ZONES_WATER_NEED_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesSoilQualityView(View):
class ZonesSoilQualityView(APIView):
"""
POST endpoint for soil quality per zone (soil quality layer).
@@ -153,15 +169,19 @@ class ZonesSoilQualityView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Crop Zoning"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("CropZoningZonesSoilQualityResponse", data=serializers.JSONField())},
)
def post(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": ZONES_SOIL_QUALITY_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesCultivationRiskView(View):
class ZonesCultivationRiskView(APIView):
"""
POST endpoint for cultivation risk per zone (cultivation risk layer).
@@ -180,14 +200,19 @@ class ZonesCultivationRiskView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Crop Zoning"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("CropZoningZonesCultivationRiskResponse", data=serializers.JSONField())},
)
def post(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": ZONES_CULTIVATION_RISK_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
class ZoneDetailsView(View):
class ZoneDetailsView(APIView):
"""
GET endpoint for zone detail data (detail panel after click).
@@ -208,9 +233,13 @@ class ZoneDetailsView(View):
not used in the response.
"""
@extend_schema(
tags=["Crop Zoning"],
responses={200: status_response("CropZoningZoneDetailsResponse", data=serializers.JSONField())},
)
def get(self, request, zone_id):
data = ZONE_DETAILS_BY_ID.get(zone_id, ZONE_DETAILS_BY_ID["zone-0"])
return JsonResponse(
return Response(
{"status": "success", "data": data},
status=200,
status=status.HTTP_200_OK,
)
+21
View File
@@ -4,12 +4,27 @@ No database connection. All responses use static mock data from mock_data.py.
"""
from rest_framework import status
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, extend_schema_view
from config.swagger import code_response
from .mock_data import ALL_CARDS, CONFIG
@extend_schema_view(
get=extend_schema(
tags=["Farm Dashboard"],
responses={200: code_response("FarmDashboardConfigGetResponse", data=serializers.JSONField())},
),
patch=extend_schema(
tags=["Farm Dashboard"],
request=OpenApiTypes.OBJECT,
responses={200: code_response("FarmDashboardConfigPatchResponse", data=serializers.JSONField())},
),
)
class FarmDashboardConfigView(APIView):
"""
Farm dashboard config endpoints: GET and PATCH.
@@ -27,6 +42,12 @@ class FarmDashboardConfigView(APIView):
return Response({"code": 200, "msg": "OK", "data": CONFIG}, status=status.HTTP_200_OK)
@extend_schema_view(
get=extend_schema(
tags=["Farm Dashboard"],
responses={200: code_response("FarmDashboardCardsResponse", data=serializers.JSONField())},
),
)
class FarmDashboardCardsView(APIView):
"""
Farm dashboard cards endpoint: GET.
+24 -15
View File
@@ -1,20 +1,21 @@
"""
Farm AI Assistant API views.
Plain Django only; no DRF. No database. All responses are static mock data.
No database. All responses are static mock data.
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt on POST so frontend can call without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from config.swagger import status_response
from .mock_data import CHAT_RESPONSE_DATA, CONTEXT_RESPONSE_DATA
class ContextView(View):
class ContextView(APIView):
"""
GET endpoint for farm context (Farm AI Assistant bar).
@@ -34,15 +35,18 @@ class ContextView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Farm AI Assistant"],
responses={200: status_response("FarmAiAssistantContextResponse", data=serializers.JSONField())},
)
def get(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": CONTEXT_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
@method_decorator(csrf_exempt, name="dispatch")
class ChatView(View):
class ChatView(APIView):
"""
POST endpoint for Farm AI Assistant chat (send message, get structured reply).
@@ -70,8 +74,13 @@ class ChatView(View):
not used in the response.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": CHAT_RESPONSE_DATA},
status=200,
@extend_schema(
tags=["Farm AI Assistant"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("FarmAiAssistantChatResponse", data=serializers.JSONField())},
)
def post(self, request):
return Response(
{"status": "success", "data": CHAT_RESPONSE_DATA},
status=status.HTTP_200_OK,
)
+24 -15
View File
@@ -1,20 +1,21 @@
"""
Fertilization Recommendation API views.
Plain Django only; no DRF. No database. All responses are static mock data.
No database. All responses are static mock data.
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt on POST so frontend can call without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from config.swagger import status_response
from .mock_data import CONFIG_RESPONSE_DATA, RECOMMEND_RESPONSE_DATA
class ConfigView(View):
class ConfigView(APIView):
"""
GET endpoint for fertilization config (farm data, growth stages, crop options).
@@ -34,15 +35,18 @@ class ConfigView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Fertilization Recommendation"],
responses={200: status_response("FertilizationConfigResponse", data=serializers.JSONField())},
)
def get(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": CONFIG_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
@method_decorator(csrf_exempt, name="dispatch")
class RecommendView(View):
class RecommendView(APIView):
"""
POST endpoint for fertilization recommendation.
@@ -64,8 +68,13 @@ class RecommendView(View):
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": RECOMMEND_RESPONSE_DATA},
status=200,
@extend_schema(
tags=["Fertilization Recommendation"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("FertilizationRecommendResponse", data=serializers.JSONField())},
)
def post(self, request):
return Response(
{"status": "success", "data": RECOMMEND_RESPONSE_DATA},
status=status.HTTP_200_OK,
)
+24 -15
View File
@@ -1,20 +1,21 @@
"""
Irrigation Recommendation API views.
Plain Django only; no DRF. No database. All responses are static mock data.
No database. All responses are static mock data.
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt on POST so frontend can call without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from config.swagger import status_response
from .mock_data import CONFIG_RESPONSE_DATA, RECOMMEND_RESPONSE_DATA
class ConfigView(View):
class ConfigView(APIView):
"""
GET endpoint for irrigation config (farm info and crop options).
@@ -34,15 +35,18 @@ class ConfigView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Irrigation Recommendation"],
responses={200: status_response("IrrigationConfigResponse", data=serializers.JSONField())},
)
def get(self, request):
return JsonResponse(
return Response(
{"status": "success", "data": CONFIG_RESPONSE_DATA},
status=200,
status=status.HTTP_200_OK,
)
@method_decorator(csrf_exempt, name="dispatch")
class RecommendView(View):
class RecommendView(APIView):
"""
POST endpoint for irrigation recommendation.
@@ -64,8 +68,13 @@ class RecommendView(View):
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": RECOMMEND_RESPONSE_DATA},
status=200,
@extend_schema(
tags=["Irrigation Recommendation"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("IrrigationRecommendResponse", data=serializers.JSONField())},
)
def post(self, request):
return Response(
{"status": "success", "data": RECOMMEND_RESPONSE_DATA},
status=status.HTTP_200_OK,
)
@@ -0,0 +1,8 @@
{
"code": 202,
"msg": "dashboard task queued",
"data": {
"task_id": "dashboard-task-123",
"status_url": "/api/dashboard-data/dashboard-task-123/status/"
}
}
@@ -0,0 +1,5 @@
{
"code": 400,
"msg": "پارامتر sensor_id الزامی است.",
"data": null
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "dashboard-task-123",
"status": "FAILURE",
"error": "خطا در ساخت کارت‌های داشبورد."
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "dashboard-task-123",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
@@ -0,0 +1,14 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "dashboard-task-123",
"status": "PROGRESS",
"progress": {
"current": 5,
"total": 15,
"card": "sensorValuesList",
"message": "processing sensorValuesList"
}
}
}
@@ -0,0 +1,41 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "dashboard-task-123",
"status": "SUCCESS",
"result": {
"sensor_id": "550e8400-e29b-41d4-a716-446655440000",
"all_cards": {
"farmOverviewKpis": {
"healthScore": 82,
"activeAlerts": 2,
"waterNeedMm": 18.4
},
"sensorValuesList": {
"items": [
{
"label": "رطوبت خاک",
"value": 45.2,
"unit": "%"
},
{
"label": "دما خاک",
"value": 22.5,
"unit": "°C"
}
]
},
"recommendationsList": {
"items": [
{
"recommendation_title": "تنظیم نوبت آبیاری",
"suggested_action": "آبیاری بعدی را صبح فردا انجام دهید.",
"urgency_level": "high"
}
]
}
}
}
}
}
@@ -0,0 +1,8 @@
{
"code": 202,
"msg": "تسک توصیه کودهی در صف قرار گرفت.",
"data": {
"task_id": "fert-task-123",
"status_url": "/api/fertilization/recommend/fert-task-123/status/"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"sensor_uuid": [
"This field is required."
]
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "fert-task-123",
"status": "FAILURE",
"error": "خطا در دریافت توصیه کودهی."
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "fert-task-123",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
@@ -0,0 +1,11 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "fert-task-123",
"status": "PROGRESS",
"progress": {
"message": "در حال پردازش توصیه کودهی..."
}
}
}
@@ -0,0 +1,19 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "fert-task-123",
"status": "SUCCESS",
"result": {
"plan": {
"npkRatio": "20-20-20",
"amountPerHectare": "150 kg/ha",
"applicationMethod": "کودآبیاری در دو نوبت",
"applicationInterval": "هر ۱۰ روز",
"reasoning": "نیتروژن و پتاسیم خاک در محدوده متوسط است و گیاه در فاز رویشی نیاز تغذیه‌ای بالاتری دارد."
},
"raw_response": "{\"plan\":{\"npkRatio\":\"20-20-20\"}}",
"status": "completed"
}
}
}
+604
View File
@@ -0,0 +1,604 @@
[
{
"method": "POST",
"path": "/api/dashboard-data/generate/",
"status_code": 202,
"description": "Dashboard data task queued",
"file": "json/mock_data/dashboard-data/generate/post_202.json"
},
{
"method": "POST",
"path": "/api/dashboard-data/generate/",
"status_code": 400,
"description": "Missing sensor_id",
"file": "json/mock_data/dashboard-data/generate/post_400.json"
},
{
"method": "GET",
"path": "/api/dashboard-data/{task_id}/status/",
"status_code": 200,
"description": "Pending dashboard task",
"file": "json/mock_data/dashboard-data/status/get_200_pending.json"
},
{
"method": "GET",
"path": "/api/dashboard-data/{task_id}/status/",
"status_code": 200,
"description": "Dashboard task in progress",
"file": "json/mock_data/dashboard-data/status/get_200_progress.json"
},
{
"method": "GET",
"path": "/api/dashboard-data/{task_id}/status/",
"status_code": 200,
"description": "Successful dashboard task",
"file": "json/mock_data/dashboard-data/status/get_200_success.json"
},
{
"method": "GET",
"path": "/api/dashboard-data/{task_id}/status/",
"status_code": 200,
"description": "Failed dashboard task",
"file": "json/mock_data/dashboard-data/status/get_200_failure.json"
},
{
"method": "POST",
"path": "/api/fertilization/recommend/",
"status_code": 202,
"description": "Fertilization task queued",
"file": "json/mock_data/fertilization/recommend/post_202.json"
},
{
"method": "POST",
"path": "/api/fertilization/recommend/",
"status_code": 400,
"description": "Validation error",
"file": "json/mock_data/fertilization/recommend/post_400.json"
},
{
"method": "GET",
"path": "/api/fertilization/recommend/{task_id}/status/",
"status_code": 200,
"description": "Fertilization status pending",
"file": "json/mock_data/fertilization/status/get_200_pending.json"
},
{
"method": "GET",
"path": "/api/fertilization/recommend/{task_id}/status/",
"status_code": 200,
"description": "Fertilization status progress",
"file": "json/mock_data/fertilization/status/get_200_progress.json"
},
{
"method": "GET",
"path": "/api/fertilization/recommend/{task_id}/status/",
"status_code": 200,
"description": "Fertilization status success",
"file": "json/mock_data/fertilization/status/get_200_success.json"
},
{
"method": "GET",
"path": "/api/fertilization/recommend/{task_id}/status/",
"status_code": 200,
"description": "Fertilization status failure",
"file": "json/mock_data/fertilization/status/get_200_failure.json"
},
{
"method": "GET",
"path": "/api/irrigation/",
"status_code": 200,
"description": "List irrigation methods",
"file": "json/mock_data/irrigation/methods/get_200.json"
},
{
"method": "POST",
"path": "/api/irrigation/",
"status_code": 201,
"description": "Create irrigation method",
"file": "json/mock_data/irrigation/methods/post_201.json"
},
{
"method": "POST",
"path": "/api/irrigation/",
"status_code": 400,
"description": "Irrigation create validation error",
"file": "json/mock_data/irrigation/methods/post_400.json"
},
{
"method": "POST",
"path": "/api/irrigation/recommend/",
"status_code": 202,
"description": "Irrigation recommendation task queued",
"file": "json/mock_data/irrigation/recommend/post_202.json"
},
{
"method": "POST",
"path": "/api/irrigation/recommend/",
"status_code": 400,
"description": "Irrigation recommendation validation error",
"file": "json/mock_data/irrigation/recommend/post_400.json"
},
{
"method": "GET",
"path": "/api/irrigation/recommend/{task_id}/status/",
"status_code": 200,
"description": "Irrigation recommendation status pending",
"file": "json/mock_data/irrigation/recommend/status/get_200_pending.json"
},
{
"method": "GET",
"path": "/api/irrigation/recommend/{task_id}/status/",
"status_code": 200,
"description": "Irrigation recommendation status progress",
"file": "json/mock_data/irrigation/recommend/status/get_200_progress.json"
},
{
"method": "GET",
"path": "/api/irrigation/recommend/{task_id}/status/",
"status_code": 200,
"description": "Irrigation recommendation status success",
"file": "json/mock_data/irrigation/recommend/status/get_200_success.json"
},
{
"method": "GET",
"path": "/api/irrigation/recommend/{task_id}/status/",
"status_code": 200,
"description": "Irrigation recommendation status failure",
"file": "json/mock_data/irrigation/recommend/status/get_200_failure.json"
},
{
"method": "GET",
"path": "/api/irrigation/{pk}/",
"status_code": 200,
"description": "Irrigation method get success",
"file": "json/mock_data/irrigation/method-detail/get_200.json"
},
{
"method": "GET",
"path": "/api/irrigation/{pk}/",
"status_code": 404,
"description": "Irrigation method get not found",
"file": "json/mock_data/irrigation/method-detail/get_404.json"
},
{
"method": "PUT",
"path": "/api/irrigation/{pk}/",
"status_code": 200,
"description": "Irrigation method put success",
"file": "json/mock_data/irrigation/method-detail/put_200.json"
},
{
"method": "PUT",
"path": "/api/irrigation/{pk}/",
"status_code": 400,
"description": "Irrigation method put validation error",
"file": "json/mock_data/irrigation/method-detail/put_400.json"
},
{
"method": "PUT",
"path": "/api/irrigation/{pk}/",
"status_code": 404,
"description": "Irrigation method put not found",
"file": "json/mock_data/irrigation/method-detail/put_404.json"
},
{
"method": "PATCH",
"path": "/api/irrigation/{pk}/",
"status_code": 200,
"description": "Irrigation method patch success",
"file": "json/mock_data/irrigation/method-detail/patch_200.json"
},
{
"method": "PATCH",
"path": "/api/irrigation/{pk}/",
"status_code": 400,
"description": "Irrigation method patch validation error",
"file": "json/mock_data/irrigation/method-detail/patch_400.json"
},
{
"method": "PATCH",
"path": "/api/irrigation/{pk}/",
"status_code": 404,
"description": "Irrigation method patch not found",
"file": "json/mock_data/irrigation/method-detail/patch_404.json"
},
{
"method": "DELETE",
"path": "/api/irrigation/{pk}/",
"status_code": 200,
"description": "Delete irrigation method",
"file": "json/mock_data/irrigation/method-detail/delete_200.json"
},
{
"method": "DELETE",
"path": "/api/irrigation/{pk}/",
"status_code": 404,
"description": "Delete irrigation method not found",
"file": "json/mock_data/irrigation/method-detail/delete_404.json"
},
{
"method": "GET",
"path": "/api/soil-data/",
"status_code": 200,
"description": "Soil data served from database",
"file": "json/mock_data/soil-data/get_200_database.json"
},
{
"method": "GET",
"path": "/api/soil-data/",
"status_code": 202,
"description": "Soil data fetch task queued",
"file": "json/mock_data/soil-data/get_202_queued.json"
},
{
"method": "GET",
"path": "/api/soil-data/",
"status_code": 400,
"description": "Soil data validation error",
"file": "json/mock_data/soil-data/get_400.json"
},
{
"method": "POST",
"path": "/api/soil-data/",
"status_code": 200,
"description": "Soil data POST served from database",
"file": "json/mock_data/soil-data/post_200_database.json"
},
{
"method": "POST",
"path": "/api/soil-data/",
"status_code": 202,
"description": "Soil data POST task queued",
"file": "json/mock_data/soil-data/post_202_queued.json"
},
{
"method": "POST",
"path": "/api/soil-data/",
"status_code": 400,
"description": "Soil data POST validation error",
"file": "json/mock_data/soil-data/post_400.json"
},
{
"method": "GET",
"path": "/api/soil-data/tasks/{task_id}/status/",
"status_code": 200,
"description": "Soil task status pending",
"file": "json/mock_data/soil-data/status/get_200_pending.json"
},
{
"method": "GET",
"path": "/api/soil-data/tasks/{task_id}/status/",
"status_code": 200,
"description": "Soil task status progress",
"file": "json/mock_data/soil-data/status/get_200_progress.json"
},
{
"method": "GET",
"path": "/api/soil-data/tasks/{task_id}/status/",
"status_code": 200,
"description": "Soil task status success",
"file": "json/mock_data/soil-data/status/get_200_success.json"
},
{
"method": "GET",
"path": "/api/soil-data/tasks/{task_id}/status/",
"status_code": 200,
"description": "Soil task status failure",
"file": "json/mock_data/soil-data/status/get_200_failure.json"
},
{
"method": "GET",
"path": "/api/plants/",
"status_code": 200,
"description": "List plants",
"file": "json/mock_data/plant/list-get_200.json"
},
{
"method": "POST",
"path": "/api/plants/",
"status_code": 201,
"description": "Create plant",
"file": "json/mock_data/plant/create-post_201.json"
},
{
"method": "POST",
"path": "/api/plants/",
"status_code": 400,
"description": "Plant create validation error",
"file": "json/mock_data/plant/create-post_400.json"
},
{
"method": "GET",
"path": "/api/plants/{pk}/",
"status_code": 200,
"description": "Plant detail get success",
"file": "json/mock_data/plant/detail-get_200.json"
},
{
"method": "GET",
"path": "/api/plants/{pk}/",
"status_code": 404,
"description": "Plant detail get not found",
"file": "json/mock_data/plant/detail-get_404.json"
},
{
"method": "PUT",
"path": "/api/plants/{pk}/",
"status_code": 200,
"description": "Plant detail put success",
"file": "json/mock_data/plant/detail-put_200.json"
},
{
"method": "PUT",
"path": "/api/plants/{pk}/",
"status_code": 400,
"description": "Plant detail put validation error",
"file": "json/mock_data/plant/detail-put_400.json"
},
{
"method": "PUT",
"path": "/api/plants/{pk}/",
"status_code": 404,
"description": "Plant detail put not found",
"file": "json/mock_data/plant/detail-put_404.json"
},
{
"method": "PATCH",
"path": "/api/plants/{pk}/",
"status_code": 200,
"description": "Plant detail patch success",
"file": "json/mock_data/plant/detail-patch_200.json"
},
{
"method": "PATCH",
"path": "/api/plants/{pk}/",
"status_code": 400,
"description": "Plant detail patch validation error",
"file": "json/mock_data/plant/detail-patch_400.json"
},
{
"method": "PATCH",
"path": "/api/plants/{pk}/",
"status_code": 404,
"description": "Plant detail patch not found",
"file": "json/mock_data/plant/detail-patch_404.json"
},
{
"method": "DELETE",
"path": "/api/plants/{pk}/",
"status_code": 200,
"description": "Delete plant success",
"file": "json/mock_data/plant/detail-delete_200.json"
},
{
"method": "DELETE",
"path": "/api/plants/{pk}/",
"status_code": 404,
"description": "Delete plant not found",
"file": "json/mock_data/plant/detail-delete_404.json"
},
{
"method": "POST",
"path": "/api/plants/fetch-info/",
"status_code": 200,
"description": "Fetch plant info success",
"file": "json/mock_data/plant/fetch-info-post_200.json"
},
{
"method": "POST",
"path": "/api/plants/fetch-info/",
"status_code": 400,
"description": "Fetch plant info missing name",
"file": "json/mock_data/plant/fetch-info-post_400.json"
},
{
"method": "POST",
"path": "/api/plants/fetch-info/",
"status_code": 503,
"description": "Fetch plant info service unavailable",
"file": "json/mock_data/plant/fetch-info-post_503.json"
},
{
"method": "POST",
"path": "/api/rag/chat/",
"status_code": 200,
"description": "RAG chat streaming response",
"file": "json/mock_data/rag/chat-post_200_stream.json"
},
{
"method": "POST",
"path": "/api/rag/chat/",
"status_code": 400,
"description": "Missing query",
"file": "json/mock_data/rag/chat-post_400_missing_query.json"
},
{
"method": "POST",
"path": "/api/rag/chat/",
"status_code": 400,
"description": "Invalid service id",
"file": "json/mock_data/rag/chat-post_400_invalid_service.json"
},
{
"method": "POST",
"path": "/api/rag/chat/",
"status_code": 400,
"description": "Missing user_id for service",
"file": "json/mock_data/rag/chat-post_400_missing_user.json"
},
{
"method": "POST",
"path": "/api/rag/recommend/irrigation/",
"status_code": 202,
"description": "RAG irrigation task queued",
"file": "json/mock_data/rag/irrigation/post_202.json"
},
{
"method": "POST",
"path": "/api/rag/recommend/irrigation/",
"status_code": 400,
"description": "RAG irrigation validation error",
"file": "json/mock_data/rag/irrigation/post_400.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
"status_code": 200,
"description": "RAG irrigation status pending",
"file": "json/mock_data/rag/irrigation/status/get_200_pending.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
"status_code": 200,
"description": "RAG irrigation status progress",
"file": "json/mock_data/rag/irrigation/status/get_200_progress.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
"status_code": 200,
"description": "RAG irrigation status success",
"file": "json/mock_data/rag/irrigation/status/get_200_success.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
"status_code": 200,
"description": "RAG irrigation status failure",
"file": "json/mock_data/rag/irrigation/status/get_200_failure.json"
},
{
"method": "POST",
"path": "/api/rag/recommend/fertilization/",
"status_code": 202,
"description": "RAG fertilization task queued",
"file": "json/mock_data/rag/fertilization/post_202.json"
},
{
"method": "POST",
"path": "/api/rag/recommend/fertilization/",
"status_code": 400,
"description": "RAG fertilization validation error",
"file": "json/mock_data/rag/fertilization/post_400.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
"status_code": 200,
"description": "RAG fertilization status pending",
"file": "json/mock_data/rag/fertilization/status/get_200_pending.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
"status_code": 200,
"description": "RAG fertilization status progress",
"file": "json/mock_data/rag/fertilization/status/get_200_progress.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
"status_code": 200,
"description": "RAG fertilization status success",
"file": "json/mock_data/rag/fertilization/status/get_200_success.json"
},
{
"method": "GET",
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
"status_code": 200,
"description": "RAG fertilization status failure",
"file": "json/mock_data/rag/fertilization/status/get_200_failure.json"
},
{
"method": "PUT",
"path": "/api/sensor-data/{uuid_sensor}/",
"status_code": 200,
"description": "Sensor update put success",
"file": "json/mock_data/sensor-data/update-put_200.json"
},
{
"method": "PUT",
"path": "/api/sensor-data/{uuid_sensor}/",
"status_code": 400,
"description": "Sensor update put validation error",
"file": "json/mock_data/sensor-data/update-put_400.json"
},
{
"method": "PUT",
"path": "/api/sensor-data/{uuid_sensor}/",
"status_code": 404,
"description": "Sensor update put location not found",
"file": "json/mock_data/sensor-data/update-put_404.json"
},
{
"method": "PATCH",
"path": "/api/sensor-data/{uuid_sensor}/",
"status_code": 200,
"description": "Sensor update patch success",
"file": "json/mock_data/sensor-data/update-patch_200.json"
},
{
"method": "PATCH",
"path": "/api/sensor-data/{uuid_sensor}/",
"status_code": 400,
"description": "Sensor update patch validation error",
"file": "json/mock_data/sensor-data/update-patch_400.json"
},
{
"method": "PATCH",
"path": "/api/sensor-data/{uuid_sensor}/",
"status_code": 404,
"description": "Sensor update patch location not found",
"file": "json/mock_data/sensor-data/update-patch_404.json"
},
{
"method": "POST",
"path": "/api/sensor-data/parameters/",
"status_code": 201,
"description": "Create sensor parameter",
"file": "json/mock_data/sensor-data/parameters-post_201.json"
},
{
"method": "POST",
"path": "/api/sensor-data/parameters/",
"status_code": 400,
"description": "Sensor parameter validation error",
"file": "json/mock_data/sensor-data/parameters-post_400.json"
},
{
"method": "POST",
"path": "/api/tasks/",
"status_code": 200,
"description": "Task trigger success",
"file": "json/mock_data/tasks/post_200.json"
},
{
"method": "GET",
"path": "/api/tasks/{task_id}/status/",
"status_code": 200,
"description": "Task status pending",
"file": "json/mock_data/tasks/status/get_200_pending.json"
},
{
"method": "GET",
"path": "/api/tasks/{task_id}/status/",
"status_code": 200,
"description": "Task status progress",
"file": "json/mock_data/tasks/status/get_200_progress.json"
},
{
"method": "GET",
"path": "/api/tasks/{task_id}/status/",
"status_code": 200,
"description": "Task status success",
"file": "json/mock_data/tasks/status/get_200_success.json"
},
{
"method": "GET",
"path": "/api/tasks/{task_id}/status/",
"status_code": 200,
"description": "Task status failure",
"file": "json/mock_data/tasks/status/get_200_failure.json"
}
]
@@ -0,0 +1,5 @@
{
"code": 200,
"msg": "روش آبیاری با موفقیت حذف شد.",
"data": null
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "روش آبیاری یافت نشد.",
"data": null
}
@@ -0,0 +1,18 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "آبیاری قطره‌ای",
"category": "موضعی",
"description": "آبیاری با دبی کم و راندمان بالا",
"water_efficiency_percent": 90.0,
"water_pressure_required": "۱-۲ اتمسفر",
"flow_rate": "۲-۸ لیتر در ساعت",
"coverage_area": "بسته به طراحی سیستم",
"soil_type": "اکثر خاک‌ها",
"climate_suitability": "گرم و خشک",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "روش آبیاری یافت نشد.",
"data": null
}
@@ -0,0 +1,18 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "آبیاری قطره‌ای",
"category": "موضعی",
"description": "آبیاری با دبی کم و راندمان بالا",
"water_efficiency_percent": 90.0,
"water_pressure_required": "۱-۲ اتمسفر",
"flow_rate": "۲-۸ لیتر در ساعت",
"coverage_area": "بسته به طراحی سیستم",
"soil_type": "اکثر خاک‌ها",
"climate_suitability": "گرم و خشک",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"name": [
"This field may not be blank."
]
}
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "روش آبیاری یافت نشد.",
"data": null
}
@@ -0,0 +1,18 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "آبیاری قطره‌ای",
"category": "موضعی",
"description": "آبیاری با دبی کم و راندمان بالا",
"water_efficiency_percent": 90.0,
"water_pressure_required": "۱-۲ اتمسفر",
"flow_rate": "۲-۸ لیتر در ساعت",
"coverage_area": "بسته به طراحی سیستم",
"soil_type": "اکثر خاک‌ها",
"climate_suitability": "گرم و خشک",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"name": [
"This field may not be blank."
]
}
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "روش آبیاری یافت نشد.",
"data": null
}
@@ -0,0 +1,20 @@
{
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"name": "آبیاری قطره‌ای",
"category": "موضعی",
"description": "آبیاری با دبی کم و راندمان بالا",
"water_efficiency_percent": 90.0,
"water_pressure_required": "۱-۲ اتمسفر",
"flow_rate": "۲-۸ لیتر در ساعت",
"coverage_area": "بسته به طراحی سیستم",
"soil_type": "اکثر خاک‌ها",
"climate_suitability": "گرم و خشک",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
]
}
@@ -0,0 +1,18 @@
{
"code": 201,
"msg": "success",
"data": {
"id": 1,
"name": "آبیاری قطره‌ای",
"category": "موضعی",
"description": "آبیاری با دبی کم و راندمان بالا",
"water_efficiency_percent": 90.0,
"water_pressure_required": "۱-۲ اتمسفر",
"flow_rate": "۲-۸ لیتر در ساعت",
"coverage_area": "بسته به طراحی سیستم",
"soil_type": "اکثر خاک‌ها",
"climate_suitability": "گرم و خشک",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"name": [
"This field is required."
]
}
}
@@ -0,0 +1,8 @@
{
"code": 202,
"msg": "تسک توصیه آبیاری در صف قرار گرفت.",
"data": {
"task_id": "irr-task-123",
"status_url": "/api/irrigation/recommend/irr-task-123/status/"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"sensor_uuid": [
"This field is required."
]
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "irr-task-123",
"status": "FAILURE",
"error": "خطا در دریافت توصیه آبیاری."
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "irr-task-123",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
@@ -0,0 +1,11 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "irr-task-123",
"status": "PROGRESS",
"progress": {
"message": "در حال پردازش توصیه آبیاری..."
}
}
}
@@ -0,0 +1,37 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "irr-task-123",
"status": "SUCCESS",
"result": {
"plan": {
"frequencyPerWeek": 3,
"durationMinutes": 42,
"bestTimeOfDay": "صبح زود",
"moistureLevel": 68,
"warning": "در صورت بارش موثر، نوبت سوم این هفته را حذف کنید."
},
"raw_response": "{\"plan\":{\"frequencyPerWeek\":3,\"durationMinutes\":42}}",
"water_balance": {
"daily": [
{
"forecast_date": "2025-03-25",
"et0_mm": 4.7,
"etc_mm": 5.6,
"effective_rainfall_mm": 0.0,
"gross_irrigation_mm": 6.2,
"irrigation_timing": "06:00-08:00"
}
],
"crop_profile": {
"kc_initial": 0.6,
"kc_mid": 1.15,
"kc_end": 0.8
},
"active_kc": 1.15
},
"status": "completed"
}
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"code": 201,
"msg": "success",
"data": {
"id": 1,
"name": "گوجه‌فرنگی",
"light": "آفتاب کامل",
"watering": "منظم، هفته‌ای ۲ تا ۳ بار",
"soil": "لومی، غنی از مواد آلی",
"temperature": "۲۰ تا ۳۰ درجه سانتی‌گراد",
"planting_season": "بهار",
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
"spacing": "۴۵ تا ۶۰ سانتی‌متر",
"fertilizer": "کود NPK متعادل",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"name": [
"This field is required."
]
}
}
@@ -0,0 +1,5 @@
{
"code": 200,
"msg": "گیاه با موفقیت حذف شد.",
"data": null
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "گیاه یافت نشد.",
"data": null
}
+18
View File
@@ -0,0 +1,18 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "گوجه‌فرنگی",
"light": "آفتاب کامل",
"watering": "منظم، هفته‌ای ۲ تا ۳ بار",
"soil": "لومی، غنی از مواد آلی",
"temperature": "۲۰ تا ۳۰ درجه سانتی‌گراد",
"planting_season": "بهار",
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
"spacing": "۴۵ تا ۶۰ سانتی‌متر",
"fertilizer": "کود NPK متعادل",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "گیاه یافت نشد.",
"data": null
}
@@ -0,0 +1,18 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "گوجه‌فرنگی",
"light": "آفتاب کامل",
"watering": "منظم، هفته‌ای ۲ تا ۳ بار",
"soil": "لومی، غنی از مواد آلی",
"temperature": "۲۰ تا ۳۰ درجه سانتی‌گراد",
"planting_season": "بهار",
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
"spacing": "۴۵ تا ۶۰ سانتی‌متر",
"fertilizer": "کود NPK متعادل",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"name": [
"This field may not be blank."
]
}
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "گیاه یافت نشد.",
"data": null
}
+18
View File
@@ -0,0 +1,18 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "گوجه‌فرنگی",
"light": "آفتاب کامل",
"watering": "منظم، هفته‌ای ۲ تا ۳ بار",
"soil": "لومی، غنی از مواد آلی",
"temperature": "۲۰ تا ۳۰ درجه سانتی‌گراد",
"planting_season": "بهار",
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
"spacing": "۴۵ تا ۶۰ سانتی‌متر",
"fertilizer": "کود NPK متعادل",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"name": [
"This field may not be blank."
]
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "گیاه یافت نشد.",
"data": null
}
@@ -0,0 +1,18 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "گوجه‌فرنگی",
"light": "آفتاب کامل",
"watering": "منظم، هفته‌ای ۲ تا ۳ بار",
"soil": "لومی، غنی از مواد آلی",
"temperature": "۲۰ تا ۳۰ درجه سانتی‌گراد",
"planting_season": "بهار",
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
"spacing": "۴۵ تا ۶۰ سانتی‌متر",
"fertilizer": "کود NPK متعادل",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,5 @@
{
"code": 400,
"msg": "نام گیاه الزامی است.",
"data": null
}
@@ -0,0 +1,5 @@
{
"code": 503,
"msg": "سرویس API هنوز پیاده‌سازی نشده است.",
"data": null
}
+20
View File
@@ -0,0 +1,20 @@
{
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"name": "گوجه‌فرنگی",
"light": "آفتاب کامل",
"watering": "منظم، هفته‌ای ۲ تا ۳ بار",
"soil": "لومی، غنی از مواد آلی",
"temperature": "۲۰ تا ۳۰ درجه سانتی‌گراد",
"planting_season": "بهار",
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
"spacing": "۴۵ تا ۶۰ سانتی‌متر",
"fertilizer": "کود NPK متعادل",
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
]
}
@@ -0,0 +1,4 @@
{
"content_type": "text/plain; charset=utf-8",
"body": "سلام، برای بازیابی رطوبت خاک بهتر است آبیاری صبح‌گاهی را تنظیم کنید."
}
@@ -0,0 +1,4 @@
{
"code": 400,
"msg": "service_id نامعتبر است: unknown_service"
}
@@ -0,0 +1,4 @@
{
"code": 400,
"msg": "پارامتر query الزامی است."
}
@@ -0,0 +1,4 @@
{
"code": 400,
"msg": "برای این service_id، پارامتر user_id الزامی است."
}
@@ -0,0 +1,8 @@
{
"code": 202,
"msg": "تسک توصیه کودهی در صف قرار گرفت.",
"data": {
"task_id": "rag-fert-123",
"status_url": "/api/rag/recommend/fertilization/rag-fert-123/status/"
}
}
@@ -0,0 +1,5 @@
{
"code": 400,
"msg": "پارامتر sensor_uuid الزامی است.",
"data": null
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-fert-123",
"status": "FAILURE",
"error": "خطا در دریافت توصیه کودهی."
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-fert-123",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
@@ -0,0 +1,11 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-fert-123",
"status": "PROGRESS",
"progress": {
"message": "در حال پردازش توصیه کودهی..."
}
}
}
@@ -0,0 +1,19 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-fert-123",
"status": "SUCCESS",
"result": {
"plan": {
"npkRatio": "20-20-20",
"amountPerHectare": "150 kg/ha",
"applicationMethod": "کودآبیاری در دو نوبت",
"applicationInterval": "هر ۱۰ روز",
"reasoning": "نیتروژن و پتاسیم خاک در محدوده متوسط است و گیاه در فاز رویشی نیاز تغذیه‌ای بالاتری دارد."
},
"raw_response": "{\"plan\":{\"npkRatio\":\"20-20-20\"}}",
"status": "completed"
}
}
}
@@ -0,0 +1,8 @@
{
"code": 202,
"msg": "تسک توصیه آبیاری در صف قرار گرفت.",
"data": {
"task_id": "rag-irr-123",
"status_url": "/api/rag/recommend/irrigation/rag-irr-123/status/"
}
}
@@ -0,0 +1,5 @@
{
"code": 400,
"msg": "پارامتر sensor_uuid الزامی است.",
"data": null
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-irr-123",
"status": "FAILURE",
"error": "خطا در دریافت توصیه آبیاری."
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-irr-123",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
@@ -0,0 +1,11 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-irr-123",
"status": "PROGRESS",
"progress": {
"message": "در حال پردازش توصیه آبیاری..."
}
}
}
@@ -0,0 +1,37 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "rag-irr-123",
"status": "SUCCESS",
"result": {
"plan": {
"frequencyPerWeek": 3,
"durationMinutes": 42,
"bestTimeOfDay": "صبح زود",
"moistureLevel": 68,
"warning": "در صورت بارش موثر، نوبت سوم این هفته را حذف کنید."
},
"raw_response": "{\"plan\":{\"frequencyPerWeek\":3,\"durationMinutes\":42}}",
"water_balance": {
"daily": [
{
"forecast_date": "2025-03-25",
"et0_mm": 4.7,
"etc_mm": 5.6,
"effective_rainfall_mm": 0.0,
"gross_irrigation_mm": 6.2,
"irrigation_timing": "06:00-08:00"
}
],
"crop_profile": {
"kc_initial": 0.6,
"kc_mid": 1.15,
"kc_end": 0.8
},
"active_kc": 1.15
},
"status": "completed"
}
}
}
@@ -0,0 +1,12 @@
{
"code": 201,
"msg": "success",
"data": {
"id": 3,
"code": "soil_moisture",
"name_fa": "رطوبت خاک",
"unit": "%",
"created_at": "2025-03-24T10:00:00Z",
"action": "added"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"code": [
"This field is required."
]
}
}
@@ -0,0 +1,20 @@
{
"code": 200,
"msg": "success",
"data": {
"uuid_sensor": "550e8400-e29b-41d4-a716-446655440000",
"location_id": 12,
"soil_moisture": 45.2,
"soil_temperature": 22.5,
"soil_ph": 6.8,
"electrical_conductivity": 1.2,
"nitrogen": 30.0,
"phosphorus": 15.0,
"potassium": 20.0,
"plant_ids": [
1
],
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"location_id": [
"This field is required."
]
}
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "location_id یافت نشد.",
"data": null
}
@@ -0,0 +1,20 @@
{
"code": 200,
"msg": "success",
"data": {
"uuid_sensor": "550e8400-e29b-41d4-a716-446655440000",
"location_id": 12,
"soil_moisture": 45.2,
"soil_temperature": 22.5,
"soil_ph": 6.8,
"electrical_conductivity": 1.2,
"nitrogen": 30.0,
"phosphorus": 15.0,
"potassium": 20.0,
"plant_ids": [
1
],
"created_at": "2025-03-20T10:00:00Z",
"updated_at": "2025-03-24T10:00:00Z"
}
}
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"location_id": [
"This field is required."
]
}
}
@@ -0,0 +1,5 @@
{
"code": 404,
"msg": "location_id یافت نشد.",
"data": null
}
@@ -0,0 +1,63 @@
{
"code": 200,
"msg": "success",
"data": {
"source": "database",
"id": 12,
"lon": "51.389000",
"lat": "35.689200",
"depths": [
{
"depth_label": "0-5cm",
"bdod": 1.31,
"cec": 18.4,
"cfvo": 2.0,
"clay": 24.0,
"nitrogen": 0.18,
"ocd": 32.0,
"ocs": 4.1,
"phh2o": 7.2,
"sand": 34.0,
"silt": 42.0,
"soc": 1.6,
"wv0010": 0.31,
"wv0033": 0.22,
"wv1500": 0.11
},
{
"depth_label": "5-15cm",
"bdod": 1.35,
"cec": 17.2,
"cfvo": 2.3,
"clay": 26.0,
"nitrogen": 0.16,
"ocd": 28.0,
"ocs": 3.7,
"phh2o": 7.1,
"sand": 36.0,
"silt": 38.0,
"soc": 1.4,
"wv0010": 0.29,
"wv0033": 0.2,
"wv1500": 0.1
},
{
"depth_label": "15-30cm",
"bdod": 1.39,
"cec": 15.8,
"cfvo": 2.8,
"clay": 28.0,
"nitrogen": 0.13,
"ocd": 22.0,
"ocs": 3.2,
"phh2o": 7.0,
"sand": 38.0,
"silt": 34.0,
"soc": 1.1,
"wv0010": 0.26,
"wv0033": 0.18,
"wv1500": 0.09
}
]
}
}
@@ -0,0 +1,11 @@
{
"code": 202,
"msg": "تسک در صف. وضعیت را با task_id بررسی کنید.",
"data": {
"source": "task",
"task_id": "soil-task-123",
"lon": 51.389,
"lat": 35.6892,
"status_url": "/api/soil-data/tasks/soil-task-123/status/"
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"lat": [
"This field is required."
],
"lon": [
"This field is required."
]
}
}
@@ -0,0 +1,63 @@
{
"code": 200,
"msg": "success",
"data": {
"source": "database",
"id": 12,
"lon": "51.389000",
"lat": "35.689200",
"depths": [
{
"depth_label": "0-5cm",
"bdod": 1.31,
"cec": 18.4,
"cfvo": 2.0,
"clay": 24.0,
"nitrogen": 0.18,
"ocd": 32.0,
"ocs": 4.1,
"phh2o": 7.2,
"sand": 34.0,
"silt": 42.0,
"soc": 1.6,
"wv0010": 0.31,
"wv0033": 0.22,
"wv1500": 0.11
},
{
"depth_label": "5-15cm",
"bdod": 1.35,
"cec": 17.2,
"cfvo": 2.3,
"clay": 26.0,
"nitrogen": 0.16,
"ocd": 28.0,
"ocs": 3.7,
"phh2o": 7.1,
"sand": 36.0,
"silt": 38.0,
"soc": 1.4,
"wv0010": 0.29,
"wv0033": 0.2,
"wv1500": 0.1
},
{
"depth_label": "15-30cm",
"bdod": 1.39,
"cec": 15.8,
"cfvo": 2.8,
"clay": 28.0,
"nitrogen": 0.13,
"ocd": 22.0,
"ocs": 3.2,
"phh2o": 7.0,
"sand": 38.0,
"silt": 34.0,
"soc": 1.1,
"wv0010": 0.26,
"wv0033": 0.18,
"wv1500": 0.09
}
]
}
}
@@ -0,0 +1,11 @@
{
"code": 202,
"msg": "تسک در صف. وضعیت را با task_id بررسی کنید.",
"data": {
"source": "task",
"task_id": "soil-task-123",
"lon": 51.389,
"lat": 35.6892,
"status_url": "/api/soil-data/tasks/soil-task-123/status/"
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"lat": [
"A valid number is required."
]
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "soil-task-123",
"status": "FAILURE",
"error": "خطا در واکشی داده خاک."
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "soil-task-123",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
@@ -0,0 +1,12 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "soil-task-123",
"status": "PROGRESS",
"progress": {
"step": "fetch",
"percent": 60
}
}
}
@@ -0,0 +1,67 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "soil-task-123",
"status": "SUCCESS",
"result": {
"source": "database",
"id": 12,
"lon": "51.389000",
"lat": "35.689200",
"depths": [
{
"depth_label": "0-5cm",
"bdod": 1.31,
"cec": 18.4,
"cfvo": 2.0,
"clay": 24.0,
"nitrogen": 0.18,
"ocd": 32.0,
"ocs": 4.1,
"phh2o": 7.2,
"sand": 34.0,
"silt": 42.0,
"soc": 1.6,
"wv0010": 0.31,
"wv0033": 0.22,
"wv1500": 0.11
},
{
"depth_label": "5-15cm",
"bdod": 1.35,
"cec": 17.2,
"cfvo": 2.3,
"clay": 26.0,
"nitrogen": 0.16,
"ocd": 28.0,
"ocs": 3.7,
"phh2o": 7.1,
"sand": 36.0,
"silt": 38.0,
"soc": 1.4,
"wv0010": 0.29,
"wv0033": 0.2,
"wv1500": 0.1
},
{
"depth_label": "15-30cm",
"bdod": 1.39,
"cec": 15.8,
"cfvo": 2.8,
"clay": 28.0,
"nitrogen": 0.13,
"ocd": 22.0,
"ocs": 3.2,
"phh2o": 7.0,
"sand": 38.0,
"silt": 34.0,
"soc": 1.1,
"wv0010": 0.26,
"wv0033": 0.18,
"wv1500": 0.09
}
]
}
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "sample-task-123"
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "sample-task-123",
"status": "FAILURE",
"error": "Sample task failed."
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "sample-task-123",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
@@ -0,0 +1,13 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "sample-task-123",
"status": "PROGRESS",
"progress": {
"current": 1,
"total": 3,
"message": "در حال پردازش..."
}
}
}
@@ -0,0 +1,9 @@
{
"code": 200,
"msg": "success",
"data": {
"task_id": "sample-task-123",
"status": "SUCCESS",
"result": "done"
}
}
+17 -12
View File
@@ -1,21 +1,21 @@
"""
Pest Detection API views.
Plain Django only; no DRF. No database. All responses are static mock data.
No database. All responses are static mock data.
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt so frontend can call POST without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from config.swagger import status_response
from .mock_data import ANALYZE_RESPONSE_DATA
@method_decorator(csrf_exempt, name="dispatch")
class AnalyzeView(View):
class AnalyzeView(APIView):
"""
POST endpoint for pest detection analysis.
@@ -36,8 +36,13 @@ class AnalyzeView(View):
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ANALYZE_RESPONSE_DATA},
status=200,
@extend_schema(
tags=["Pest Detection"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("PestDetectionAnalyzeResponse", data=serializers.JSONField())},
)
def post(self, request):
return Response(
{"status": "success", "data": ANALYZE_RESPONSE_DATA},
status=status.HTTP_200_OK,
)
+47 -24
View File
@@ -1,22 +1,22 @@
"""
Plant Simulator API views.
Plain Django only; no DRF. No database. All responses are static mock data.
No database. All responses are static mock data.
Response format: {"status": "success"} or {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt so frontend (e.g. localhost:3000) can call POST/PATCH without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from config.swagger import status_response
from .mock_data import CONFIG_SLIDERS_ONLY, START_RESPONSE_DATA, STATE_RESPONSE_DATA
from .serializers import success_response, success_with_data
@method_decorator(csrf_exempt, name="dispatch")
class ConfigView(View):
class ConfigView(APIView):
"""
GET endpoint for simulator configuration (ورود).
@@ -34,12 +34,15 @@ class ConfigView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Plant Simulator"],
responses={200: status_response("PlantSimulatorConfigResponse", data=serializers.JSONField())},
)
def get(self, request):
return JsonResponse(success_with_data(CONFIG_SLIDERS_ONLY), status=200)
return Response(success_with_data(CONFIG_SLIDERS_ONLY), status=status.HTTP_200_OK)
@method_decorator(csrf_exempt, name="dispatch")
class StateView(View):
class StateView(APIView):
"""
GET endpoint for plant state, progress, and chart history.
@@ -62,12 +65,15 @@ class StateView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Plant Simulator"],
responses={200: status_response("PlantSimulatorStateResponse", data=serializers.JSONField())},
)
def get(self, request):
return JsonResponse(success_with_data(STATE_RESPONSE_DATA), status=200)
return Response(success_with_data(STATE_RESPONSE_DATA), status=status.HTTP_200_OK)
@method_decorator(csrf_exempt, name="dispatch")
class StartView(View):
class StartView(APIView):
"""
POST endpoint to start simulation.
@@ -86,12 +92,16 @@ class StartView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Plant Simulator"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("PlantSimulatorStartResponse", data=serializers.JSONField())},
)
def post(self, request):
return JsonResponse(success_with_data(START_RESPONSE_DATA), status=200)
return Response(success_with_data(START_RESPONSE_DATA), status=status.HTTP_200_OK)
@method_decorator(csrf_exempt, name="dispatch")
class StopView(View):
class StopView(APIView):
"""
POST endpoint to stop simulation.
@@ -109,12 +119,16 @@ class StopView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Plant Simulator"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("PlantSimulatorStopResponse")},
)
def post(self, request):
return JsonResponse(success_response(), status=200)
return Response(success_response(), status=status.HTTP_200_OK)
@method_decorator(csrf_exempt, name="dispatch")
class ResetView(View):
class ResetView(APIView):
"""
POST endpoint to reset simulation.
@@ -132,12 +146,16 @@ class ResetView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Plant Simulator"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("PlantSimulatorResetResponse")},
)
def post(self, request):
return JsonResponse(success_response(), status=200)
return Response(success_response(), status=status.HTTP_200_OK)
@method_decorator(csrf_exempt, name="dispatch")
class EnvironmentView(View):
class EnvironmentView(APIView):
"""
PATCH endpoint to update environment (slider values).
@@ -157,5 +175,10 @@ class EnvironmentView(View):
No processing or validation is performed on inputs.
"""
@extend_schema(
tags=["Plant Simulator"],
request=OpenApiTypes.OBJECT,
responses={200: status_response("PlantSimulatorEnvironmentResponse")},
)
def patch(self, request):
return JsonResponse(success_response(), status=200)
return Response(success_response(), status=status.HTTP_200_OK)
+38
View File
@@ -1,12 +1,50 @@
from rest_framework import status
from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, extend_schema_view
from config.swagger import code_response
from .models import Sensor
from .serializers import SensorCreateSerializer, SensorSerializer
@extend_schema_view(
get=extend_schema(
tags=["Sensor Hub"],
responses={
200: code_response("SensorHubGetResponse", data=serializers.JSONField()),
404: code_response("SensorHubNotFoundResponse"),
},
),
post=extend_schema(
tags=["Sensor Hub"],
request=OpenApiTypes.OBJECT,
responses={
201: code_response("SensorCreateResponse", data=serializers.JSONField()),
200: code_response("SensorToggleResponse"),
400: code_response("SensorToggleValidationResponse"),
404: code_response("SensorToggleNotFoundResponse"),
},
),
patch=extend_schema(
tags=["Sensor Hub"],
request=SensorCreateSerializer,
responses={
200: code_response("SensorUpdateResponse", data=SensorSerializer()),
404: code_response("SensorUpdateNotFoundResponse"),
},
),
delete=extend_schema(
tags=["Sensor Hub"],
responses={
200: code_response("SensorDeleteResponse"),
404: code_response("SensorDeleteNotFoundResponse"),
},
),
)
class SensorHubView(APIView):
"""
Sensor-hub CRUD endpoints connected to the database.