diff --git a/account/views.py b/account/views.py index 19d63be..d23ce0f 100644 --- a/account/views.py +++ b/account/views.py @@ -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). diff --git a/auth/views.py b/auth/views.py index b6d12a0..e476131 100644 --- a/auth/views.py +++ b/auth/views.py @@ -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. diff --git a/config/settings.py b/config/settings.py index 81c54e1..97ea0fe 100644 --- a/config/settings.py +++ b/config/settings.py @@ -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/", } diff --git a/config/swagger.py b/config/swagger.py new file mode 100644 index 0000000..2f79a12 --- /dev/null +++ b/config/swagger.py @@ -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) diff --git a/config/urls.py b/config/urls.py index 6cd3a86..986e659 100644 --- a/config/urls.py +++ b/config/urls.py @@ -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")), diff --git a/crop_zoning/views.py b/crop_zoning/views.py index 12b2d1f..32910a2 100644 --- a/crop_zoning/views.py +++ b/crop_zoning/views.py @@ -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": }. 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, ) diff --git a/dashboard/views.py b/dashboard/views.py index 9621d34..15ab87a 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -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. diff --git a/farm_ai_assistant/views.py b/farm_ai_assistant/views.py index cd2229a..7272547 100644 --- a/farm_ai_assistant/views.py +++ b/farm_ai_assistant/views.py @@ -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": }. 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. """ + @extend_schema( + tags=["Farm AI Assistant"], + request=OpenApiTypes.OBJECT, + responses={200: status_response("FarmAiAssistantChatResponse", data=serializers.JSONField())}, + ) def post(self, request): - return JsonResponse( + return Response( {"status": "success", "data": CHAT_RESPONSE_DATA}, - status=200, + status=status.HTTP_200_OK, ) diff --git a/fertilization_recommendation/views.py b/fertilization_recommendation/views.py index 551de0b..520d7a2 100644 --- a/fertilization_recommendation/views.py +++ b/fertilization_recommendation/views.py @@ -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": }. 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. """ + @extend_schema( + tags=["Fertilization Recommendation"], + request=OpenApiTypes.OBJECT, + responses={200: status_response("FertilizationRecommendResponse", data=serializers.JSONField())}, + ) def post(self, request): - return JsonResponse( + return Response( {"status": "success", "data": RECOMMEND_RESPONSE_DATA}, - status=200, + status=status.HTTP_200_OK, ) diff --git a/irrigation_recommendation/views.py b/irrigation_recommendation/views.py index 95e82ec..f5b2d2a 100644 --- a/irrigation_recommendation/views.py +++ b/irrigation_recommendation/views.py @@ -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": }. 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. """ + @extend_schema( + tags=["Irrigation Recommendation"], + request=OpenApiTypes.OBJECT, + responses={200: status_response("IrrigationRecommendResponse", data=serializers.JSONField())}, + ) def post(self, request): - return JsonResponse( + return Response( {"status": "success", "data": RECOMMEND_RESPONSE_DATA}, - status=200, + status=status.HTTP_200_OK, ) diff --git a/json/mock_data/dashboard-data/generate/post_202.json b/json/mock_data/dashboard-data/generate/post_202.json new file mode 100644 index 0000000..34b0f71 --- /dev/null +++ b/json/mock_data/dashboard-data/generate/post_202.json @@ -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/" + } +} diff --git a/json/mock_data/dashboard-data/generate/post_400.json b/json/mock_data/dashboard-data/generate/post_400.json new file mode 100644 index 0000000..5df03b8 --- /dev/null +++ b/json/mock_data/dashboard-data/generate/post_400.json @@ -0,0 +1,5 @@ +{ + "code": 400, + "msg": "پارامتر sensor_id الزامی است.", + "data": null +} diff --git a/json/mock_data/dashboard-data/status/get_200_failure.json b/json/mock_data/dashboard-data/status/get_200_failure.json new file mode 100644 index 0000000..bc4f2eb --- /dev/null +++ b/json/mock_data/dashboard-data/status/get_200_failure.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "dashboard-task-123", + "status": "FAILURE", + "error": "خطا در ساخت کارت‌های داشبورد." + } +} diff --git a/json/mock_data/dashboard-data/status/get_200_pending.json b/json/mock_data/dashboard-data/status/get_200_pending.json new file mode 100644 index 0000000..74dafc6 --- /dev/null +++ b/json/mock_data/dashboard-data/status/get_200_pending.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "dashboard-task-123", + "status": "PENDING", + "message": "تسک در صف یا یافت نشد." + } +} diff --git a/json/mock_data/dashboard-data/status/get_200_progress.json b/json/mock_data/dashboard-data/status/get_200_progress.json new file mode 100644 index 0000000..e3515e5 --- /dev/null +++ b/json/mock_data/dashboard-data/status/get_200_progress.json @@ -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" + } + } +} diff --git a/json/mock_data/dashboard-data/status/get_200_success.json b/json/mock_data/dashboard-data/status/get_200_success.json new file mode 100644 index 0000000..4de043c --- /dev/null +++ b/json/mock_data/dashboard-data/status/get_200_success.json @@ -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" + } + ] + } + } + } + } +} diff --git a/json/mock_data/fertilization/recommend/post_202.json b/json/mock_data/fertilization/recommend/post_202.json new file mode 100644 index 0000000..e8dc23f --- /dev/null +++ b/json/mock_data/fertilization/recommend/post_202.json @@ -0,0 +1,8 @@ +{ + "code": 202, + "msg": "تسک توصیه کودهی در صف قرار گرفت.", + "data": { + "task_id": "fert-task-123", + "status_url": "/api/fertilization/recommend/fert-task-123/status/" + } +} diff --git a/json/mock_data/fertilization/recommend/post_400.json b/json/mock_data/fertilization/recommend/post_400.json new file mode 100644 index 0000000..9fdc597 --- /dev/null +++ b/json/mock_data/fertilization/recommend/post_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "sensor_uuid": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/fertilization/status/get_200_failure.json b/json/mock_data/fertilization/status/get_200_failure.json new file mode 100644 index 0000000..fb8bad0 --- /dev/null +++ b/json/mock_data/fertilization/status/get_200_failure.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "fert-task-123", + "status": "FAILURE", + "error": "خطا در دریافت توصیه کودهی." + } +} diff --git a/json/mock_data/fertilization/status/get_200_pending.json b/json/mock_data/fertilization/status/get_200_pending.json new file mode 100644 index 0000000..110be1a --- /dev/null +++ b/json/mock_data/fertilization/status/get_200_pending.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "fert-task-123", + "status": "PENDING", + "message": "تسک در صف یا یافت نشد." + } +} diff --git a/json/mock_data/fertilization/status/get_200_progress.json b/json/mock_data/fertilization/status/get_200_progress.json new file mode 100644 index 0000000..ffdf909 --- /dev/null +++ b/json/mock_data/fertilization/status/get_200_progress.json @@ -0,0 +1,11 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "fert-task-123", + "status": "PROGRESS", + "progress": { + "message": "در حال پردازش توصیه کودهی..." + } + } +} diff --git a/json/mock_data/fertilization/status/get_200_success.json b/json/mock_data/fertilization/status/get_200_success.json new file mode 100644 index 0000000..7b46ba1 --- /dev/null +++ b/json/mock_data/fertilization/status/get_200_success.json @@ -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" + } + } +} diff --git a/json/mock_data/index.json b/json/mock_data/index.json new file mode 100644 index 0000000..d051fb6 --- /dev/null +++ b/json/mock_data/index.json @@ -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" + } +] diff --git a/json/mock_data/irrigation/method-detail/delete_200.json b/json/mock_data/irrigation/method-detail/delete_200.json new file mode 100644 index 0000000..ed52092 --- /dev/null +++ b/json/mock_data/irrigation/method-detail/delete_200.json @@ -0,0 +1,5 @@ +{ + "code": 200, + "msg": "روش آبیاری با موفقیت حذف شد.", + "data": null +} diff --git a/json/mock_data/irrigation/method-detail/delete_404.json b/json/mock_data/irrigation/method-detail/delete_404.json new file mode 100644 index 0000000..54dcfff --- /dev/null +++ b/json/mock_data/irrigation/method-detail/delete_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "روش آبیاری یافت نشد.", + "data": null +} diff --git a/json/mock_data/irrigation/method-detail/get_200.json b/json/mock_data/irrigation/method-detail/get_200.json new file mode 100644 index 0000000..5988c67 --- /dev/null +++ b/json/mock_data/irrigation/method-detail/get_200.json @@ -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" + } +} diff --git a/json/mock_data/irrigation/method-detail/get_404.json b/json/mock_data/irrigation/method-detail/get_404.json new file mode 100644 index 0000000..54dcfff --- /dev/null +++ b/json/mock_data/irrigation/method-detail/get_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "روش آبیاری یافت نشد.", + "data": null +} diff --git a/json/mock_data/irrigation/method-detail/patch_200.json b/json/mock_data/irrigation/method-detail/patch_200.json new file mode 100644 index 0000000..5988c67 --- /dev/null +++ b/json/mock_data/irrigation/method-detail/patch_200.json @@ -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" + } +} diff --git a/json/mock_data/irrigation/method-detail/patch_400.json b/json/mock_data/irrigation/method-detail/patch_400.json new file mode 100644 index 0000000..2e4dd22 --- /dev/null +++ b/json/mock_data/irrigation/method-detail/patch_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "name": [ + "This field may not be blank." + ] + } +} diff --git a/json/mock_data/irrigation/method-detail/patch_404.json b/json/mock_data/irrigation/method-detail/patch_404.json new file mode 100644 index 0000000..54dcfff --- /dev/null +++ b/json/mock_data/irrigation/method-detail/patch_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "روش آبیاری یافت نشد.", + "data": null +} diff --git a/json/mock_data/irrigation/method-detail/put_200.json b/json/mock_data/irrigation/method-detail/put_200.json new file mode 100644 index 0000000..5988c67 --- /dev/null +++ b/json/mock_data/irrigation/method-detail/put_200.json @@ -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" + } +} diff --git a/json/mock_data/irrigation/method-detail/put_400.json b/json/mock_data/irrigation/method-detail/put_400.json new file mode 100644 index 0000000..2e4dd22 --- /dev/null +++ b/json/mock_data/irrigation/method-detail/put_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "name": [ + "This field may not be blank." + ] + } +} diff --git a/json/mock_data/irrigation/method-detail/put_404.json b/json/mock_data/irrigation/method-detail/put_404.json new file mode 100644 index 0000000..54dcfff --- /dev/null +++ b/json/mock_data/irrigation/method-detail/put_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "روش آبیاری یافت نشد.", + "data": null +} diff --git a/json/mock_data/irrigation/methods/get_200.json b/json/mock_data/irrigation/methods/get_200.json new file mode 100644 index 0000000..a2e511e --- /dev/null +++ b/json/mock_data/irrigation/methods/get_200.json @@ -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" + } + ] +} diff --git a/json/mock_data/irrigation/methods/post_201.json b/json/mock_data/irrigation/methods/post_201.json new file mode 100644 index 0000000..2fba4f5 --- /dev/null +++ b/json/mock_data/irrigation/methods/post_201.json @@ -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" + } +} diff --git a/json/mock_data/irrigation/methods/post_400.json b/json/mock_data/irrigation/methods/post_400.json new file mode 100644 index 0000000..f72f28c --- /dev/null +++ b/json/mock_data/irrigation/methods/post_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "name": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/irrigation/recommend/post_202.json b/json/mock_data/irrigation/recommend/post_202.json new file mode 100644 index 0000000..a5f230d --- /dev/null +++ b/json/mock_data/irrigation/recommend/post_202.json @@ -0,0 +1,8 @@ +{ + "code": 202, + "msg": "تسک توصیه آبیاری در صف قرار گرفت.", + "data": { + "task_id": "irr-task-123", + "status_url": "/api/irrigation/recommend/irr-task-123/status/" + } +} diff --git a/json/mock_data/irrigation/recommend/post_400.json b/json/mock_data/irrigation/recommend/post_400.json new file mode 100644 index 0000000..9fdc597 --- /dev/null +++ b/json/mock_data/irrigation/recommend/post_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "sensor_uuid": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/irrigation/recommend/status/get_200_failure.json b/json/mock_data/irrigation/recommend/status/get_200_failure.json new file mode 100644 index 0000000..5f4e1f7 --- /dev/null +++ b/json/mock_data/irrigation/recommend/status/get_200_failure.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "irr-task-123", + "status": "FAILURE", + "error": "خطا در دریافت توصیه آبیاری." + } +} diff --git a/json/mock_data/irrigation/recommend/status/get_200_pending.json b/json/mock_data/irrigation/recommend/status/get_200_pending.json new file mode 100644 index 0000000..498b382 --- /dev/null +++ b/json/mock_data/irrigation/recommend/status/get_200_pending.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "irr-task-123", + "status": "PENDING", + "message": "تسک در صف یا یافت نشد." + } +} diff --git a/json/mock_data/irrigation/recommend/status/get_200_progress.json b/json/mock_data/irrigation/recommend/status/get_200_progress.json new file mode 100644 index 0000000..e8837dd --- /dev/null +++ b/json/mock_data/irrigation/recommend/status/get_200_progress.json @@ -0,0 +1,11 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "irr-task-123", + "status": "PROGRESS", + "progress": { + "message": "در حال پردازش توصیه آبیاری..." + } + } +} diff --git a/json/mock_data/irrigation/recommend/status/get_200_success.json b/json/mock_data/irrigation/recommend/status/get_200_success.json new file mode 100644 index 0000000..46f9c90 --- /dev/null +++ b/json/mock_data/irrigation/recommend/status/get_200_success.json @@ -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" + } + } +} diff --git a/json/mock_data/plant/create-post_201.json b/json/mock_data/plant/create-post_201.json new file mode 100644 index 0000000..32c4614 --- /dev/null +++ b/json/mock_data/plant/create-post_201.json @@ -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" + } +} diff --git a/json/mock_data/plant/create-post_400.json b/json/mock_data/plant/create-post_400.json new file mode 100644 index 0000000..f72f28c --- /dev/null +++ b/json/mock_data/plant/create-post_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "name": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/plant/detail-delete_200.json b/json/mock_data/plant/detail-delete_200.json new file mode 100644 index 0000000..b127160 --- /dev/null +++ b/json/mock_data/plant/detail-delete_200.json @@ -0,0 +1,5 @@ +{ + "code": 200, + "msg": "گیاه با موفقیت حذف شد.", + "data": null +} diff --git a/json/mock_data/plant/detail-delete_404.json b/json/mock_data/plant/detail-delete_404.json new file mode 100644 index 0000000..497519d --- /dev/null +++ b/json/mock_data/plant/detail-delete_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "گیاه یافت نشد.", + "data": null +} diff --git a/json/mock_data/plant/detail-get_200.json b/json/mock_data/plant/detail-get_200.json new file mode 100644 index 0000000..af5f8ad --- /dev/null +++ b/json/mock_data/plant/detail-get_200.json @@ -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" + } +} diff --git a/json/mock_data/plant/detail-get_404.json b/json/mock_data/plant/detail-get_404.json new file mode 100644 index 0000000..497519d --- /dev/null +++ b/json/mock_data/plant/detail-get_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "گیاه یافت نشد.", + "data": null +} diff --git a/json/mock_data/plant/detail-patch_200.json b/json/mock_data/plant/detail-patch_200.json new file mode 100644 index 0000000..af5f8ad --- /dev/null +++ b/json/mock_data/plant/detail-patch_200.json @@ -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" + } +} diff --git a/json/mock_data/plant/detail-patch_400.json b/json/mock_data/plant/detail-patch_400.json new file mode 100644 index 0000000..2e4dd22 --- /dev/null +++ b/json/mock_data/plant/detail-patch_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "name": [ + "This field may not be blank." + ] + } +} diff --git a/json/mock_data/plant/detail-patch_404.json b/json/mock_data/plant/detail-patch_404.json new file mode 100644 index 0000000..497519d --- /dev/null +++ b/json/mock_data/plant/detail-patch_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "گیاه یافت نشد.", + "data": null +} diff --git a/json/mock_data/plant/detail-put_200.json b/json/mock_data/plant/detail-put_200.json new file mode 100644 index 0000000..af5f8ad --- /dev/null +++ b/json/mock_data/plant/detail-put_200.json @@ -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" + } +} diff --git a/json/mock_data/plant/detail-put_400.json b/json/mock_data/plant/detail-put_400.json new file mode 100644 index 0000000..2e4dd22 --- /dev/null +++ b/json/mock_data/plant/detail-put_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "name": [ + "This field may not be blank." + ] + } +} diff --git a/json/mock_data/plant/detail-put_404.json b/json/mock_data/plant/detail-put_404.json new file mode 100644 index 0000000..497519d --- /dev/null +++ b/json/mock_data/plant/detail-put_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "گیاه یافت نشد.", + "data": null +} diff --git a/json/mock_data/plant/fetch-info-post_200.json b/json/mock_data/plant/fetch-info-post_200.json new file mode 100644 index 0000000..af5f8ad --- /dev/null +++ b/json/mock_data/plant/fetch-info-post_200.json @@ -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" + } +} diff --git a/json/mock_data/plant/fetch-info-post_400.json b/json/mock_data/plant/fetch-info-post_400.json new file mode 100644 index 0000000..e4bbdd2 --- /dev/null +++ b/json/mock_data/plant/fetch-info-post_400.json @@ -0,0 +1,5 @@ +{ + "code": 400, + "msg": "نام گیاه الزامی است.", + "data": null +} diff --git a/json/mock_data/plant/fetch-info-post_503.json b/json/mock_data/plant/fetch-info-post_503.json new file mode 100644 index 0000000..f4911a7 --- /dev/null +++ b/json/mock_data/plant/fetch-info-post_503.json @@ -0,0 +1,5 @@ +{ + "code": 503, + "msg": "سرویس API هنوز پیاده‌سازی نشده است.", + "data": null +} diff --git a/json/mock_data/plant/list-get_200.json b/json/mock_data/plant/list-get_200.json new file mode 100644 index 0000000..e6586a2 --- /dev/null +++ b/json/mock_data/plant/list-get_200.json @@ -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" + } + ] +} diff --git a/json/mock_data/rag/chat-post_200_stream.json b/json/mock_data/rag/chat-post_200_stream.json new file mode 100644 index 0000000..ead2147 --- /dev/null +++ b/json/mock_data/rag/chat-post_200_stream.json @@ -0,0 +1,4 @@ +{ + "content_type": "text/plain; charset=utf-8", + "body": "سلام، برای بازیابی رطوبت خاک بهتر است آبیاری صبح‌گاهی را تنظیم کنید." +} diff --git a/json/mock_data/rag/chat-post_400_invalid_service.json b/json/mock_data/rag/chat-post_400_invalid_service.json new file mode 100644 index 0000000..c484816 --- /dev/null +++ b/json/mock_data/rag/chat-post_400_invalid_service.json @@ -0,0 +1,4 @@ +{ + "code": 400, + "msg": "service_id نامعتبر است: unknown_service" +} diff --git a/json/mock_data/rag/chat-post_400_missing_query.json b/json/mock_data/rag/chat-post_400_missing_query.json new file mode 100644 index 0000000..b491272 --- /dev/null +++ b/json/mock_data/rag/chat-post_400_missing_query.json @@ -0,0 +1,4 @@ +{ + "code": 400, + "msg": "پارامتر query الزامی است." +} diff --git a/json/mock_data/rag/chat-post_400_missing_user.json b/json/mock_data/rag/chat-post_400_missing_user.json new file mode 100644 index 0000000..3615785 --- /dev/null +++ b/json/mock_data/rag/chat-post_400_missing_user.json @@ -0,0 +1,4 @@ +{ + "code": 400, + "msg": "برای این service_id، پارامتر user_id الزامی است." +} diff --git a/json/mock_data/rag/fertilization/post_202.json b/json/mock_data/rag/fertilization/post_202.json new file mode 100644 index 0000000..8fe7cc7 --- /dev/null +++ b/json/mock_data/rag/fertilization/post_202.json @@ -0,0 +1,8 @@ +{ + "code": 202, + "msg": "تسک توصیه کودهی در صف قرار گرفت.", + "data": { + "task_id": "rag-fert-123", + "status_url": "/api/rag/recommend/fertilization/rag-fert-123/status/" + } +} diff --git a/json/mock_data/rag/fertilization/post_400.json b/json/mock_data/rag/fertilization/post_400.json new file mode 100644 index 0000000..6bea070 --- /dev/null +++ b/json/mock_data/rag/fertilization/post_400.json @@ -0,0 +1,5 @@ +{ + "code": 400, + "msg": "پارامتر sensor_uuid الزامی است.", + "data": null +} diff --git a/json/mock_data/rag/fertilization/status/get_200_failure.json b/json/mock_data/rag/fertilization/status/get_200_failure.json new file mode 100644 index 0000000..971dd04 --- /dev/null +++ b/json/mock_data/rag/fertilization/status/get_200_failure.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "rag-fert-123", + "status": "FAILURE", + "error": "خطا در دریافت توصیه کودهی." + } +} diff --git a/json/mock_data/rag/fertilization/status/get_200_pending.json b/json/mock_data/rag/fertilization/status/get_200_pending.json new file mode 100644 index 0000000..9c9eca6 --- /dev/null +++ b/json/mock_data/rag/fertilization/status/get_200_pending.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "rag-fert-123", + "status": "PENDING", + "message": "تسک در صف یا یافت نشد." + } +} diff --git a/json/mock_data/rag/fertilization/status/get_200_progress.json b/json/mock_data/rag/fertilization/status/get_200_progress.json new file mode 100644 index 0000000..4cd6d57 --- /dev/null +++ b/json/mock_data/rag/fertilization/status/get_200_progress.json @@ -0,0 +1,11 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "rag-fert-123", + "status": "PROGRESS", + "progress": { + "message": "در حال پردازش توصیه کودهی..." + } + } +} diff --git a/json/mock_data/rag/fertilization/status/get_200_success.json b/json/mock_data/rag/fertilization/status/get_200_success.json new file mode 100644 index 0000000..cea132a --- /dev/null +++ b/json/mock_data/rag/fertilization/status/get_200_success.json @@ -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" + } + } +} diff --git a/json/mock_data/rag/irrigation/post_202.json b/json/mock_data/rag/irrigation/post_202.json new file mode 100644 index 0000000..bcc6db9 --- /dev/null +++ b/json/mock_data/rag/irrigation/post_202.json @@ -0,0 +1,8 @@ +{ + "code": 202, + "msg": "تسک توصیه آبیاری در صف قرار گرفت.", + "data": { + "task_id": "rag-irr-123", + "status_url": "/api/rag/recommend/irrigation/rag-irr-123/status/" + } +} diff --git a/json/mock_data/rag/irrigation/post_400.json b/json/mock_data/rag/irrigation/post_400.json new file mode 100644 index 0000000..6bea070 --- /dev/null +++ b/json/mock_data/rag/irrigation/post_400.json @@ -0,0 +1,5 @@ +{ + "code": 400, + "msg": "پارامتر sensor_uuid الزامی است.", + "data": null +} diff --git a/json/mock_data/rag/irrigation/status/get_200_failure.json b/json/mock_data/rag/irrigation/status/get_200_failure.json new file mode 100644 index 0000000..bffa098 --- /dev/null +++ b/json/mock_data/rag/irrigation/status/get_200_failure.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "rag-irr-123", + "status": "FAILURE", + "error": "خطا در دریافت توصیه آبیاری." + } +} diff --git a/json/mock_data/rag/irrigation/status/get_200_pending.json b/json/mock_data/rag/irrigation/status/get_200_pending.json new file mode 100644 index 0000000..1074e86 --- /dev/null +++ b/json/mock_data/rag/irrigation/status/get_200_pending.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "rag-irr-123", + "status": "PENDING", + "message": "تسک در صف یا یافت نشد." + } +} diff --git a/json/mock_data/rag/irrigation/status/get_200_progress.json b/json/mock_data/rag/irrigation/status/get_200_progress.json new file mode 100644 index 0000000..3b88990 --- /dev/null +++ b/json/mock_data/rag/irrigation/status/get_200_progress.json @@ -0,0 +1,11 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "rag-irr-123", + "status": "PROGRESS", + "progress": { + "message": "در حال پردازش توصیه آبیاری..." + } + } +} diff --git a/json/mock_data/rag/irrigation/status/get_200_success.json b/json/mock_data/rag/irrigation/status/get_200_success.json new file mode 100644 index 0000000..5679648 --- /dev/null +++ b/json/mock_data/rag/irrigation/status/get_200_success.json @@ -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" + } + } +} diff --git a/json/mock_data/sensor-data/parameters-post_201.json b/json/mock_data/sensor-data/parameters-post_201.json new file mode 100644 index 0000000..9de5e9d --- /dev/null +++ b/json/mock_data/sensor-data/parameters-post_201.json @@ -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" + } +} diff --git a/json/mock_data/sensor-data/parameters-post_400.json b/json/mock_data/sensor-data/parameters-post_400.json new file mode 100644 index 0000000..98b89aa --- /dev/null +++ b/json/mock_data/sensor-data/parameters-post_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "code": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/sensor-data/update-patch_200.json b/json/mock_data/sensor-data/update-patch_200.json new file mode 100644 index 0000000..5bc26e3 --- /dev/null +++ b/json/mock_data/sensor-data/update-patch_200.json @@ -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" + } +} diff --git a/json/mock_data/sensor-data/update-patch_400.json b/json/mock_data/sensor-data/update-patch_400.json new file mode 100644 index 0000000..cf343c5 --- /dev/null +++ b/json/mock_data/sensor-data/update-patch_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "location_id": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/sensor-data/update-patch_404.json b/json/mock_data/sensor-data/update-patch_404.json new file mode 100644 index 0000000..107ea33 --- /dev/null +++ b/json/mock_data/sensor-data/update-patch_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "location_id یافت نشد.", + "data": null +} diff --git a/json/mock_data/sensor-data/update-put_200.json b/json/mock_data/sensor-data/update-put_200.json new file mode 100644 index 0000000..5bc26e3 --- /dev/null +++ b/json/mock_data/sensor-data/update-put_200.json @@ -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" + } +} diff --git a/json/mock_data/sensor-data/update-put_400.json b/json/mock_data/sensor-data/update-put_400.json new file mode 100644 index 0000000..cf343c5 --- /dev/null +++ b/json/mock_data/sensor-data/update-put_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "location_id": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/sensor-data/update-put_404.json b/json/mock_data/sensor-data/update-put_404.json new file mode 100644 index 0000000..107ea33 --- /dev/null +++ b/json/mock_data/sensor-data/update-put_404.json @@ -0,0 +1,5 @@ +{ + "code": 404, + "msg": "location_id یافت نشد.", + "data": null +} diff --git a/json/mock_data/soil-data/get_200_database.json b/json/mock_data/soil-data/get_200_database.json new file mode 100644 index 0000000..87becb1 --- /dev/null +++ b/json/mock_data/soil-data/get_200_database.json @@ -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 + } + ] + } +} diff --git a/json/mock_data/soil-data/get_202_queued.json b/json/mock_data/soil-data/get_202_queued.json new file mode 100644 index 0000000..5a06843 --- /dev/null +++ b/json/mock_data/soil-data/get_202_queued.json @@ -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/" + } +} diff --git a/json/mock_data/soil-data/get_400.json b/json/mock_data/soil-data/get_400.json new file mode 100644 index 0000000..bfc19ab --- /dev/null +++ b/json/mock_data/soil-data/get_400.json @@ -0,0 +1,12 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "lat": [ + "This field is required." + ], + "lon": [ + "This field is required." + ] + } +} diff --git a/json/mock_data/soil-data/post_200_database.json b/json/mock_data/soil-data/post_200_database.json new file mode 100644 index 0000000..87becb1 --- /dev/null +++ b/json/mock_data/soil-data/post_200_database.json @@ -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 + } + ] + } +} diff --git a/json/mock_data/soil-data/post_202_queued.json b/json/mock_data/soil-data/post_202_queued.json new file mode 100644 index 0000000..5a06843 --- /dev/null +++ b/json/mock_data/soil-data/post_202_queued.json @@ -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/" + } +} diff --git a/json/mock_data/soil-data/post_400.json b/json/mock_data/soil-data/post_400.json new file mode 100644 index 0000000..4d32daf --- /dev/null +++ b/json/mock_data/soil-data/post_400.json @@ -0,0 +1,9 @@ +{ + "code": 400, + "msg": "داده نامعتبر.", + "data": { + "lat": [ + "A valid number is required." + ] + } +} diff --git a/json/mock_data/soil-data/status/get_200_failure.json b/json/mock_data/soil-data/status/get_200_failure.json new file mode 100644 index 0000000..a0c0697 --- /dev/null +++ b/json/mock_data/soil-data/status/get_200_failure.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "soil-task-123", + "status": "FAILURE", + "error": "خطا در واکشی داده خاک." + } +} diff --git a/json/mock_data/soil-data/status/get_200_pending.json b/json/mock_data/soil-data/status/get_200_pending.json new file mode 100644 index 0000000..b4965ce --- /dev/null +++ b/json/mock_data/soil-data/status/get_200_pending.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "soil-task-123", + "status": "PENDING", + "message": "تسک در صف یا یافت نشد." + } +} diff --git a/json/mock_data/soil-data/status/get_200_progress.json b/json/mock_data/soil-data/status/get_200_progress.json new file mode 100644 index 0000000..29ecfe5 --- /dev/null +++ b/json/mock_data/soil-data/status/get_200_progress.json @@ -0,0 +1,12 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "soil-task-123", + "status": "PROGRESS", + "progress": { + "step": "fetch", + "percent": 60 + } + } +} diff --git a/json/mock_data/soil-data/status/get_200_success.json b/json/mock_data/soil-data/status/get_200_success.json new file mode 100644 index 0000000..04d0827 --- /dev/null +++ b/json/mock_data/soil-data/status/get_200_success.json @@ -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 + } + ] + } + } +} diff --git a/json/mock_data/tasks/post_200.json b/json/mock_data/tasks/post_200.json new file mode 100644 index 0000000..8fc9fe2 --- /dev/null +++ b/json/mock_data/tasks/post_200.json @@ -0,0 +1,7 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "sample-task-123" + } +} diff --git a/json/mock_data/tasks/status/get_200_failure.json b/json/mock_data/tasks/status/get_200_failure.json new file mode 100644 index 0000000..2c34884 --- /dev/null +++ b/json/mock_data/tasks/status/get_200_failure.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "sample-task-123", + "status": "FAILURE", + "error": "Sample task failed." + } +} diff --git a/json/mock_data/tasks/status/get_200_pending.json b/json/mock_data/tasks/status/get_200_pending.json new file mode 100644 index 0000000..c6ce7cb --- /dev/null +++ b/json/mock_data/tasks/status/get_200_pending.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "sample-task-123", + "status": "PENDING", + "message": "تسک در صف یا یافت نشد." + } +} diff --git a/json/mock_data/tasks/status/get_200_progress.json b/json/mock_data/tasks/status/get_200_progress.json new file mode 100644 index 0000000..abb1b76 --- /dev/null +++ b/json/mock_data/tasks/status/get_200_progress.json @@ -0,0 +1,13 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "sample-task-123", + "status": "PROGRESS", + "progress": { + "current": 1, + "total": 3, + "message": "در حال پردازش..." + } + } +} diff --git a/json/mock_data/tasks/status/get_200_success.json b/json/mock_data/tasks/status/get_200_success.json new file mode 100644 index 0000000..5b55965 --- /dev/null +++ b/json/mock_data/tasks/status/get_200_success.json @@ -0,0 +1,9 @@ +{ + "code": 200, + "msg": "success", + "data": { + "task_id": "sample-task-123", + "status": "SUCCESS", + "result": "done" + } +} diff --git a/pest_detection/views.py b/pest_detection/views.py index f7a340a..2003848 100644 --- a/pest_detection/views.py +++ b/pest_detection/views.py @@ -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": }. 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. """ + @extend_schema( + tags=["Pest Detection"], + request=OpenApiTypes.OBJECT, + responses={200: status_response("PestDetectionAnalyzeResponse", data=serializers.JSONField())}, + ) def post(self, request): - return JsonResponse( + return Response( {"status": "success", "data": ANALYZE_RESPONSE_DATA}, - status=200, + status=status.HTTP_200_OK, ) diff --git a/plant_simulator/views.py b/plant_simulator/views.py index acd18a4..6408aae 100644 --- a/plant_simulator/views.py +++ b/plant_simulator/views.py @@ -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": }. 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) diff --git a/sensor_hub/views.py b/sensor_hub/views.py index 3320c55..f306101 100644 --- a/sensor_hub/views.py +++ b/sensor_hub/views.py @@ -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.