diff --git a/file.txt b/file.txt new file mode 100644 index 00000000..c57eff55 --- /dev/null +++ b/file.txt @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/server/farmapp/models.py b/server/farmapp/models.py index bbb681c1..8592d037 100644 --- a/server/farmapp/models.py +++ b/server/farmapp/models.py @@ -1,10 +1,8 @@ from django.db import models - -# Create your models here. -from django.db import models from django.contrib.auth.models import AbstractBaseUser from django.utils import timezone + # 1. User Model class User(AbstractBaseUser): ROLE_CHOICES = [ @@ -17,12 +15,16 @@ class User(AbstractBaseUser): name = models.CharField(max_length=255) email = models.EmailField(unique=True) password = models.CharField(max_length=255) # Password field + password = models.CharField(max_length=255) # Password field role = models.CharField(max_length=10, choices=ROLE_CHOICES) phone = models.CharField(max_length=15, null=True, blank=True) address = models.CharField(max_length=255, null=True, blank=True) createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) - last_login = models.DateTimeField(null=True, blank=True) + reset_password_token = models.CharField(max_length=36, null=True, blank=True) # UUID token + reset_token_created_at = models.DateTimeField(null=True, blank=True) # Token timestamp + last_login = models.DateTimeField(null=True, blank=True) # Last login timestamp + is_active = models.BooleanField(default=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['name'] @@ -30,10 +32,10 @@ class User(AbstractBaseUser): def __str__(self): return self.name + class Meta: db_table = 'User' - managed = True # Set to True or remove to allow migrations - + managed = True # 2. Farmer Model class Farmer(models.Model): @@ -44,7 +46,6 @@ class Farmer(models.Model): ] farmerId = models.AutoField(primary_key=True) - # user = models.OneToOneField(User, on_delete=models.CASCADE, limit_choices_to={'role': 'farmer'}) userId = models.OneToOneField(User, on_delete=models.CASCADE, limit_choices_to={'role': 'farmer'}, related_name='farmer', db_column='userId') farmName = models.CharField(max_length=255) location = models.CharField(max_length=255) @@ -59,7 +60,7 @@ def __str__(self): class Meta: db_table = 'Farmer' - managed = False + managed = True # 3. Product Model class Product(models.Model): @@ -70,7 +71,6 @@ class Product(models.Model): ] productId = models.AutoField(primary_key=True) - # farmer = models.ForeignKey(Farmer, on_delete=models.CASCADE) farmer = models.ForeignKey(Farmer, on_delete=models.CASCADE, db_column='farmerId') productName = models.CharField(max_length=255) description = models.TextField() @@ -87,7 +87,7 @@ def __str__(self): class Meta: db_table = 'Product' - managed = False + managed = True # 4. Order Model class Order(models.Model): @@ -99,7 +99,7 @@ class Order(models.Model): ] # orderId = models.AutoField(primary_key=True) - # customer = models.ForeignKey(User, on_delete=models.CASCADE, limit_choices_to={'role': 'customer'}) + # customer = models.ForeignKey(User, on_delete=models.CASCADE, db_column='customerId') orderId = models.AutoField(primary_key=True) # Primary key field customer = models.ForeignKey(User, on_delete=models.CASCADE, db_column='customerId') # ForeignKey to User model orderItems = models.JSONField() # Store an array of products, including productId, quantity, and price @@ -108,13 +108,14 @@ class Order(models.Model): createdAt = models.DateTimeField(auto_now_add=True) updatedAt = models.DateTimeField(auto_now=True) deliveryDate = models.DateTimeField(null=True, blank=True) + deliveryDate = models.DateTimeField(null=True, blank=True) def __str__(self): - return self.orderId + return str(self.orderId) class Meta: db_table = 'Order' - managed = False + managed = True # 5. Cart Model (for Customers) class Cart(models.Model): @@ -126,11 +127,11 @@ class Cart(models.Model): updatedAt = models.DateTimeField(auto_now=True) def __str__(self): - return self.customer + return str(self.cartId) class Meta: db_table = 'Cart' - managed = False + managed = True # 6. Review Model class Review(models.Model): @@ -143,11 +144,11 @@ class Review(models.Model): updatedAt = models.DateTimeField(auto_now=True) def __str__(self): - return self.reviewId + return str(self.reviewId) class Meta: db_table = 'Review' - managed = False + managed = True # 7. Admin Activity Log Model class AdminActivityLog(models.Model): @@ -162,4 +163,4 @@ def __str__(self): class Meta: db_table = 'AdminActivityLog' - managed = False + managed = True diff --git a/server/farmapp/serializers.py b/server/farmapp/serializers.py index e8711c2a..812d30d4 100644 --- a/server/farmapp/serializers.py +++ b/server/farmapp/serializers.py @@ -1,21 +1,43 @@ from rest_framework import serializers -from .models import Order, Product +from django.contrib.auth.hashers import make_password +from .models import User, Order, Product +# User Management Serializers +class RegisterSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True) + + class Meta: + model = User + fields = ['name', 'email', 'password', 'role', 'phone', 'address'] + + def create(self, validated_data): + validated_data['password'] = make_password(validated_data['password']) + return super().create(validated_data) + +class LoginSerializer(serializers.Serializer): + email = serializers.EmailField() + password = serializers.CharField(write_only=True) + +class ChangePasswordSerializer(serializers.Serializer): + old_password = serializers.CharField(write_only=True) + new_password = serializers.CharField(write_only=True) + +class ForgotPasswordSerializer(serializers.Serializer): + email = serializers.EmailField() + +class ResetPasswordSerializer(serializers.Serializer): + token = serializers.CharField() + new_password = serializers.CharField(write_only=True) + +class UserStatusSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['userId', 'name', 'email', 'is_active', 'role'] + +# Order Management Serializers class OrderDetailSerializer(serializers.ModelSerializer): """ Serializer for displaying detailed information about an order. - - Fields: - - orderId: The unique ID of the order. - - customerName: Name of the customer who placed the order. - - productName: Name of the product in the order. - - quantity: Quantity of the product ordered. - - totalPrice: Total price of the order. - - orderStatus: Status of the order. - - dateOrdered: The date the order was placed. - - deliveryDate: The expected delivery date for the order. - - address: Address of the customer. - - phone: Contact phone number of the customer. """ orderId = serializers.IntegerField() # Removed 'source' argument customerName = serializers.CharField(source='customer.name') @@ -31,7 +53,6 @@ class OrderDetailSerializer(serializers.ModelSerializer): def get_productName(self, obj): """ Retrieves the name of the product from the Product model based on productId. - Assumes that each order item is structured to include 'productId' in orderItems. """ product_ids = [item['productId'] for item in obj.orderItems] products = Product.objects.filter(productId__in=product_ids) @@ -40,7 +61,6 @@ def get_productName(self, obj): def get_quantity(self, obj): """ Retrieves the quantity of the product ordered. - Assumes 'quantity' is part of each entry in orderItems. """ return sum(item.get('quantity', 0) for item in obj.orderItems) @@ -48,17 +68,10 @@ class Meta: model = Order fields = ['orderId', 'customerName', 'productName', 'phone', 'orderItems', 'totalPrice', 'orderStatus', 'address', 'quantity', 'dateOrdered', 'deliveryDate'] - - - class OrderStatusUpdateSerializer(serializers.ModelSerializer): """ Serializer for updating the status of an order. - - Fields: - - status: New status for the order, validated against allowed transitions. """ - status = serializers.ChoiceField(choices=Order.ORDER_STATUS_CHOICES) def validate_status(self, value): @@ -80,4 +93,4 @@ def validate_status(self, value): class Meta: model = Order - fields = ['status'] \ No newline at end of file + fields = ['status'] diff --git a/server/farmapp/urls.py b/server/farmapp/urls.py index eae7572a..b9473d84 100644 --- a/server/farmapp/urls.py +++ b/server/farmapp/urls.py @@ -1,16 +1,33 @@ from django.urls import path -from .views import DeleteReviewView, OrderDetailView, UpdateOrderStatusView, DeleteOrderView +from .views import ( + RegisterView, + LoginView, + ForgotPasswordView, + ResetPasswordView, + ChangePasswordView, + CheckStatusView, + SignoutView, + DeleteReviewView, + OrderDetailView, + UpdateOrderStatusView, + DeleteOrderView, +) urlpatterns = [ - # URL for retrieving order details + # Auth-related endpoints + path('farmer/auth/register/', RegisterView.as_view(), name='register'), + path('farmer/auth/login/', LoginView.as_view(), name='login'), + path('farmer/auth/forgot-password/', ForgotPasswordView.as_view(), name='forgot-password'), + path('farmer/auth/reset-password/', ResetPasswordView.as_view(), name='reset-password'), + path('farmer/auth/change-password/', ChangePasswordView.as_view(), name='change-password'), + path('farmer/auth/check-status/', CheckStatusView.as_view(), name='check-status'), + path('farmer/auth/signout/', SignoutView.as_view(), name='signout'), + + # Order-related endpoints path("api/farmer//orders/", OrderDetailView.as_view(), name="order-detail"), - - # URL for updating order status path("api/farmer//orders//status", UpdateOrderStatusView.as_view(), name="update-order-status"), - - # URL for deleting an order (or marking it as cancelled, depending on logic) path("api/farmer//orders//delete", DeleteOrderView.as_view(), name="delete-order"), - # URL for deleting a review + # Review-related endpoints path("api/product//reviews/", DeleteReviewView.as_view(), name="delete-review"), ] diff --git a/server/farmapp/views.py b/server/farmapp/views.py index a7581b0a..cff84773 100644 --- a/server/farmapp/views.py +++ b/server/farmapp/views.py @@ -1,182 +1,209 @@ -from django.db import DatabaseError -from django.shortcuts import get_object_or_404 +from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status, views -from .serializers import OrderDetailSerializer, OrderStatusUpdateSerializer -from .models import Farmer, Order, Product, Review, User -from rest_framework.views import APIView - - -class OrderDetailView(APIView): - """ - Retrieve detailed information of a specific order for a given farmer. - - URL Parameters: - - farmerId (int): ID of the farmer. - - orderId (int): ID of the order. +from django.contrib.auth import authenticate +from django.contrib.auth.hashers import check_password, make_password +from django.core.mail import send_mail +from django.utils.timezone import now +from django.shortcuts import get_object_or_404 +from datetime import timedelta +import jwt +import uuid +from django.conf import settings +from django.db import DatabaseError +from rest_framework.permissions import IsAuthenticated + +from .models import User, Farmer, Order, Product, Review +from .serializers import ( + RegisterSerializer, LoginSerializer, ChangePasswordSerializer, + ForgotPasswordSerializer, ResetPasswordSerializer, OrderDetailSerializer, + OrderStatusUpdateSerializer, UserStatusSerializer +) + +# JWT Utility +def generate_jwt(user): + payload = {'user_id': user.userId, 'email': user.email} + token = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256') + return token + +# Authentication Views +class RegisterView(APIView): + + def post(self, request): + serializer = RegisterSerializer(data=request.data) + if serializer.is_valid(): + user = serializer.save() + return Response({"message": "User registered successfully."}, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - Validations: - - Ensures the farmer exists. - - Checks if the order exists and contains products from this farmer. +from rest_framework_simplejwt.tokens import RefreshToken - Returns: - - 200 OK: Detailed information of the order. - - 404 Not Found: If the farmer or order is not found or not associated with the farmer. - """ +class LoginView(APIView): + def post(self, request): + serializer = LoginSerializer(data=request.data) + if serializer.is_valid(): + email = serializer.validated_data['email'] + password = serializer.validated_data['password'] + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + return Response({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED) + + if not check_password(password, user.password): + return Response({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED) + + if not user.is_active: + return Response({"error": "Email not verified."}, status=status.HTTP_403_FORBIDDEN) + + refresh = RefreshToken.for_user(user) + return Response({ + 'access': str(refresh.access_token), + 'refresh': str(refresh), + }, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class ChangePasswordView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + user = request.user + serializer = ChangePasswordSerializer(data=request.data) + if serializer.is_valid(): + if check_password(serializer.validated_data['old_password'], user.password): + user.password = make_password(serializer.validated_data['new_password']) + user.save() + return Response({"message": "Password changed successfully."}, status=status.HTTP_200_OK) + return Response({"error": "Old password is incorrect."}, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class ForgotPasswordView(APIView): + def post(self, request): + serializer = ForgotPasswordSerializer(data=request.data) + if serializer.is_valid(): + email = serializer.validated_data['email'] + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + return Response({"error": "No user found with this email address."}, status=status.HTTP_404_NOT_FOUND) + + reset_token = str(uuid.uuid4()) + user.reset_password_token = reset_token + user.reset_token_created_at = now() + user.save() + + reset_url = f"http://your-frontend-url.com/reset-password?token={reset_token}" + send_mail( + subject="Password Reset Request", + message=f"Hi {user.name},\n\nClick the link to reset your password:\n\n{reset_url}", + from_email="no-reply@yourdomain.com", + recipient_list=[email] + ) + return Response({"message": "Password reset link sent to your email."}, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class ResetPasswordView(APIView): + def post(self, request): + serializer = ResetPasswordSerializer(data=request.data) + if serializer.is_valid(): + token = serializer.validated_data['token'] + new_password = serializer.validated_data['new_password'] + try: + user = User.objects.get(reset_password_token=token) + if now() - user.reset_token_created_at > timedelta(hours=1): + return Response({"error": "Token has expired."}, status=status.HTTP_400_BAD_REQUEST) + + user.password = make_password(new_password) + user.reset_password_token = None + user.reset_token_created_at = None + user.save() + return Response({"message": "Password reset successfully."}, status=status.HTTP_200_OK) + except User.DoesNotExist: + return Response({"error": "Invalid token."}, status=status.HTTP_400_BAD_REQUEST) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class CheckStatusView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + user = request.user + if not user.is_active: + return Response({"error": "User account is Inactive!."}, status=403) + + serializer = UserStatusSerializer(user) + return Response(serializer.data, status=status.HTTP_200_OK) + + + +class SignoutView(APIView): + def post(self, request): + return Response({"message": "Successfully signed out."}, status=status.HTTP_200_OK) +# Order and Review Management Views +class OrderDetailView(APIView): def get(self, request, farmerId, orderId): - # Validate Farmer try: farmer = Farmer.objects.get(farmerId=farmerId) - except Farmer.DoesNotExist: - return Response({"error": "Farmer not found"}, status=status.HTTP_404_NOT_FOUND) - - # Retrieve Order and Validate Association with Farmer's Products - try: order = Order.objects.get(orderId=orderId) product_ids = [item['productId'] for item in order.orderItems] - farmer_products = Product.objects.filter(productId__in=product_ids, farmer=farmer) + if not Product.objects.filter(productId__in=product_ids, farmer=farmer).exists(): + return Response({"error": "Order not associated with this farmer"}, status=status.HTTP_404_NOT_FOUND) - if not farmer_products.exists(): - return Response({"error": "Order does not contain products from this farmer"}, status=status.HTTP_404_NOT_FOUND) + serializer = OrderDetailSerializer(order) + return Response(serializer.data, status=status.HTTP_200_OK) + except Farmer.DoesNotExist: + return Response({"error": "Farmer not found"}, status=status.HTTP_404_NOT_FOUND) except Order.DoesNotExist: return Response({"error": "Order not found"}, status=status.HTTP_404_NOT_FOUND) - # Serialize the order details - serializer = OrderDetailSerializer(order) - - # Return the serialized data - return Response(serializer.data, status=status.HTTP_200_OK) - - - -class UpdateOrderStatusView(views.APIView): - """ - Update the status of an order for a specific farmer, with validation for allowed transitions. - - URL Parameters: - - farmerId (int): ID of the farmer. - - orderId (int): ID of the order. - - Request Body: - - status (str): New status of the order. - """ - +class UpdateOrderStatusView(APIView): def put(self, request, farmerId, orderId): - # Validate Farmer and Order try: - # Check if the farmer exists farmer = Farmer.objects.get(farmerId=farmerId) - # Check if the order exists order = Order.objects.get(orderId=orderId) - - # Check if the order contains products from this farmer product_ids = [item['productId'] for item in order.orderItems] if not Product.objects.filter(productId__in=product_ids, farmer=farmer).exists(): - return Response({"error": "Order does not contain products from this farmer"}, status=status.HTTP_404_NOT_FOUND) + return Response({"error": "Order not associated with this farmer"}, status=status.HTTP_404_NOT_FOUND) + serializer = OrderStatusUpdateSerializer(order, data=request.data, partial=True) + if serializer.is_valid(): + order.status = serializer.validated_data['status'] + order.save() + return Response({"message": "Order status updated successfully", "status": order.status}, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) except Farmer.DoesNotExist: return Response({"error": "Farmer not found"}, status=status.HTTP_404_NOT_FOUND) except Order.DoesNotExist: return Response({"error": "Order not found"}, status=status.HTTP_404_NOT_FOUND) - # Use the serializer for status validation - serializer = OrderStatusUpdateSerializer(order, data=request.data, partial=True) - - # Validate the serializer - if serializer.is_valid(): - # Update and save status - order.status = serializer.validated_data['status'] - order.save() - - return Response({"message": "Order status updated successfully", "status": order.status}, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - -class DeleteOrderView(views.APIView): - """ - Delete (or cancel) an order for a specific farmer. Allows for soft deletion where - the order status is changed to "Cancelled" instead of being permanently deleted. - - URL Parameters: - - farmerId (int): ID of the farmer. - - orderId (int): ID of the order. - - Validations: - - Checks if the farmer and order exist. - - Ensures the order is linked to the specified farmer. - - Restricts deletion for orders in "Completed" or "Delivered" status. - - Returns: - - 200 OK: Order cancelled. - - 400 Bad Request: If the order cannot be deleted. - - 404 Not Found: If the farmer or order is not found. - """ - +class DeleteOrderView(APIView): def delete(self, request, farmerId, orderId): - # Validate Farmer and Order try: - # Verify farmer existence farmer = Farmer.objects.get(farmerId=farmerId) - - # Verify order existence and ownership order = Order.objects.get(orderId=orderId) product_ids = [item['productId'] for item in order.orderItems] - - # Check if the order includes products that belong to the specified farmer if not Product.objects.filter(productId__in=product_ids, farmer=farmer).exists(): return Response({"error": "Order not associated with this farmer"}, status=status.HTTP_404_NOT_FOUND) + if order.status not in ["pending", "cancelled"]: + return Response({"error": "Cannot delete a completed or delivered order"}, status=status.HTTP_400_BAD_REQUEST) + + order.status = "cancelled" + order.save() + return Response({"message": "Order cancelled successfully"}, status=status.HTTP_200_OK) except Farmer.DoesNotExist: return Response({"error": "Farmer not found"}, status=status.HTTP_404_NOT_FOUND) except Order.DoesNotExist: return Response({"error": "Order not found"}, status=status.HTTP_404_NOT_FOUND) - # Restrict Deletion Based on Order Status - if order.status not in ["pending", "cancelled"]: - return Response({"error": "Cannot delete a completed or delivered order"}, status=status.HTTP_400_BAD_REQUEST) - - # Soft Delete by Setting Status to "Cancelled" - order.status = "cancelled" - order.save() - - # Return Success Response - return Response({"message": "Order cancelled successfully"}, status=status.HTTP_200_OK) - - - -class DeleteReviewView(views.APIView): - """ - Endpoint: DELETE /api/product/:productId/reviews/:reviewId - Allows a customer to delete their review for a specific product. - - Validations: - - Ensure that productId and reviewId are valid. - - Check that the review belongs to the customer making the request. - - Responses: - - 204 No Content: Review successfully deleted. - - 403 Forbidden: Review does not belong to the customer making the request. - - 404 Not Found: Product or review not found. - - 500 Internal Server Error: Database error during deletion. - """ - +class DeleteReviewView(APIView): def delete(self, request, productId, reviewId): - # Validate Product and Review IDs try: - # Check if the review exists and is associated with the specified product review = get_object_or_404(Review, reviewId=reviewId, product__id=productId) - - # Check if the review belongs to the requesting user if review.customer != request.user: return Response({"error": "You are not authorized to delete this review."}, status=status.HTTP_403_FORBIDDEN) - # Attempt to delete the review from the database review.delete() return Response({"review": {}}, status=status.HTTP_204_NO_CONTENT) - except DatabaseError: - # Step 4: Handle potential database errors during deletion - return Response({"error": "An error occurred while attempting to delete the review."}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file + return Response({"error": "An error occurred while deleting the review."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/server/farmsales/settings.py b/server/farmsales/settings.py index e8f98fbb..2bbf36ca 100644 --- a/server/farmsales/settings.py +++ b/server/farmsales/settings.py @@ -11,25 +11,27 @@ """ from pathlib import Path +import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-h$tn#ey-^dnq4!+3n!yn+w3-pc_*=nej@wc8b+-i0b*!=p64+h' +SECRET_KEY = os.getenv('SECRET_KEY', 'default-secret-key') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] +DEBUG = os.getenv('DEBUG', 'False').lower() in ('true', '1', 't') +ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost').split(',') # Application definition - INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -38,7 +40,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', - 'farmapp', + 'farmapp', # Custom app ] MIDDLEWARE = [ @@ -71,44 +73,30 @@ WSGI_APPLICATION = 'farmsales.wsgi.application' - # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'CropCircle', - 'USER': 'backend_team', - 'PASSWORD': 'Akogo660221.', - 'HOST': 'localhost', - 'PORT': '3306', + 'NAME': os.getenv('DB_NAME', 'Cropsdb'), + 'USER': os.getenv('DB_USER', 'root'), + 'PASSWORD': os.getenv('DB_PASSWORD', 'Archilles5522'), + 'HOST': os.getenv('DB_HOST', 'localhost'), + 'PORT': os.getenv('DB_PORT', '3306'), } } - # Password validation # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators - AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, + {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, + {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, + {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, + {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, ] - # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ - LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' @@ -117,13 +105,59 @@ USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ - STATIC_URL = 'static/' # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field - DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Email settings +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_HOST_USER = os.getenv('EMAIL_USER', 'example@gmail.com') +EMAIL_HOST_PASSWORD = os.getenv('EMAIL_PASSWORD', 'password') +EMAIL_USE_TLS = True + +# Logging configuration +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + }, + }, + 'root': { + 'handlers': ['console'], + 'level': 'WARNING', + }, + 'django': { + 'handlers': ['console'], + 'level': 'INFO', + 'propagate': True, + }, +} + +ALLOWED_HOSTS=['127.0.0.1'] + +from datetime import timedelta + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': True, + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + 'AUTH_HEADER_TYPES': ('Bearer',), + 'USER_ID_FIELD': 'userId', + 'USER_ID_CLAIM': 'userId', +} + + + + diff --git a/server/farmsales/urls.py b/server/farmsales/urls.py index 0ce13d95..97936cc5 100644 --- a/server/farmsales/urls.py +++ b/server/farmsales/urls.py @@ -14,10 +14,12 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.contrib import admin from django.urls import include, path +from django.urls import include, path urlpatterns = [ path('admin/', admin.site.urls), - path('farmapp/', include('farmapp.urls')) + path('api/', include('farmapp.urls')), # API routes for farmapp ]