1. Home
  2. Sms Campaign Project
  3. Project Setup
  4. 02. Authentication Api

02. Authentication Api

🥈 Part 2: APIs বানানো – সবচেয়ে কম কোড দিয়ে, সহজে এবং ধাপে ধাপে

এখন আমরা Part 1-এর উপর ভিত্তি করে সব API বানাবো।
লক্ষ্য:

  • কোড যতটা সম্ভব কম রাখা
  • যেখানে পারি ModelViewSet + GenericAPIView ব্যবহার করা
  • Serializers ও Views শুধু campaign_app/api/ ফোল্ডারে
  • Register, Login, Profile, Password Change/Reset, Photo Delete – সবই কভার
  • Functionality তোমার আগের প্রজেক্টের মতোই থাকবে

Step 1: api/urls.py বানাও (App level routing)

campaign_app/api/urls.py

from django.urls import path
from .views import (
    RegisterView,
    LoginView,
    ProfileView,
    ProfileUpdateView,
    ProfilePhotoDeleteView,
    ChangePasswordView,
    PasswordResetRequestView,
    PasswordResetConfirmView,
)

urlpatterns = [
    path('auth/register/', RegisterView.as_view(), name='register'),
    path('auth/login/', LoginView.as_view(), name='login'),
    path('auth/profile/', ProfileView.as_view(), name='profile-view'),
    path('auth/profile/update/', ProfileUpdateView.as_view(), name='profile-update'),
    path('auth/profile/photo-delete/', ProfilePhotoDeleteView.as_view(), name='photo-delete'),
    path('auth/change-password/', ChangePasswordView.as_view(), name='change-password'),
    path('auth/password-reset/', PasswordResetRequestView.as_view(), name='password-reset'),
    path('auth/password-reset/<str:token>/', PasswordResetConfirmView.as_view(), name='password-reset-confirm'),
]

কী হবে?
সব API /api/auth/... পাথে আসবে। Clean এবং organized।

Step 2: api/serializers.py

campaign_app/api/serializers.py

from rest_framework import serializers
from campaign_app.models import User, PasswordResetToken
from django.contrib.auth.password_validation import validate_password
from rest_framework_simplejwt.tokens import RefreshToken

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, validators=[validate_password])
    confirm_password = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = [
            'email', 'password', 'confirm_password',
            'business_owner_name', 'business_name',
            'industry_type', 'mobile_number',
            'address', 'address_line_2', 'city', 'state', 'zipcode'
        ]

    def validate(self, attrs):
        if attrs['password'] != attrs['confirm_password']:
            raise serializers.ValidationError("Passwords do not match")
        return attrs

    def create(self, validated_data):
        validated_data.pop('confirm_password')
        user = User.objects.create_user(
            email=validated_data['email'],
            password=validated_data['password'],
            **{k: v for k, v in validated_data.items() if k != 'email' and k != 'password'}
        )
        return user


class LoginSerializer(serializers.Serializer):
    email = serializers.EmailField()
    password = serializers.CharField(write_only=True)


class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = [
            'id', 'email', 'business_owner_name', 'business_name',
            'industry_type', 'mobile_number', 'address', 'address_line_2',
            'city', 'state', 'zipcode', 'profile_photo', 'created_at'
        ]
        read_only_fields = ['email', 'id', 'created_at']


class ChangePasswordSerializer(serializers.Serializer):
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True, validators=[validate_password])
    confirm_password = serializers.CharField(required=True)

    def validate(self, attrs):
        if attrs['new_password'] != attrs['confirm_password']:
            raise serializers.ValidationError("New passwords do not match")
        return attrs


class PasswordResetRequestSerializer(serializers.Serializer):
    email = serializers.EmailField()


class PasswordResetConfirmSerializer(serializers.Serializer):
    new_password = serializers.CharField(validators=[validate_password])
    confirm_password = serializers.CharField()

ব্যাখ্যা:

  • Register এ সব required + optional fields আছে
  • Profile এ photo URL automatically আসবে (Django handle করে)
  • Password validation Django এর built-in দিয়ে

Step 3: api/views.py – মেইন কাজ এখানে (কম কোড!)

campaign_app/api/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework import status, generics
from .serializers import *
from campaign_app.models import User, PasswordResetToken
from django.contrib.auth import authenticate
from rest_framework_simplejwt.tokens import RefreshToken
from django.core.mail import send_mail
from django.utils import timezone
from datetime import timedelta
import uuid
import os

# Helper: JWT Token generate
def get_tokens_for_user(user):
    refresh = RefreshToken.for_user(user)
    return {
        'refresh': str(refresh),
        'access': str(refresh.access_token),
    }

class RegisterView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()
            token = get_tokens_for_user(user)
            return Response({
                'success': True,
                'message': 'Registration successful',
                'token': token
            }, status=status.HTTP_201_CREATED)
        return Response({
            'success': False,
            'errors': serializer.errors
        }, status=status.HTTP_400_BAD_REQUEST)


class LoginView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        if serializer.is_valid():
            email = serializer.validated_data['email'].lower()
            password = serializer.validated_data['password']
            user = authenticate(email=email, password=password)
            if user:
                token = get_tokens_for_user(user)
                profile_serializer = ProfileSerializer(user)
                return Response({
                    'success': True,
                    'token': token,
                    'user': profile_serializer.data
                })
            return Response({
                'success': False,
                'message': 'Invalid credentials'
            }, status=status.HTTP_401_UNAUTHORIZED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class ProfileView(generics.RetrieveAPIView):
    permission_classes = [IsAuthenticated]
    serializer_class = ProfileSerializer

    def get_object(self):
        return self.request.user


class ProfileUpdateView(generics.UpdateAPIView):
    permission_classes = [IsAuthenticated]
    serializer_class = ProfileSerializer

    def get_object(self):
        return self.request.user

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', True)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response({
            'success': True,
            'message': 'Profile updated',
            'data': serializer.data
        })


class ProfilePhotoDeleteView(APIView):
    permission_classes = [IsAuthenticated]

    def delete(self, request):
        user = request.user
        if user.profile_photo:
            user.profile_photo.delete()
            user.save()
            return Response({'success': True, 'message': 'Photo deleted'})
        return Response({'success': False, 'message': 'No photo to delete'}, status=400)


class ChangePasswordView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = ChangePasswordSerializer(data=request.data)
        if serializer.is_valid():
            user = request.user
            if not user.check_password(serializer.validated_data['old_password']):
                return Response({'message': 'Old password is incorrect'}, status=400)
            user.set_password(serializer.validated_data['new_password'])
            user.save()
            return Response({'success': True, 'message': 'Password changed successfully'})
        return Response(serializer.errors, status=400)


class PasswordResetRequestView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        serializer = PasswordResetRequestSerializer(data=request.data)
        if serializer.is_valid():
            email = serializer.validated_data['email'].lower()
            user = User.objects.filter(email=email).first()
            if user:
                token = uuid.uuid4()
                PasswordResetToken.objects.update_or_create(
                    user=user,
                    defaults={'token': token, 'created_at': timezone.now()}
                )
                reset_url = f"https://your-frontend.com/reset-password/{token}"  # তোমার frontend URL দাও
                send_mail(
                    'Password Reset Request',
                    f'Click here to reset: {reset_url}',
                    os.getenv('EMAIL_HOST_USER'),
                    [email],
                    fail_silently=False,
                )
            return Response({'success': True, 'message': 'Reset link sent if email exists'})
        return Response(serializer.errors, status=400)


class PasswordResetConfirmView(APIView):
    permission_classes = [AllowAny]

    def post(self, request, token):
        serializer = PasswordResetConfirmSerializer(data=request.data)
        if serializer.is_valid():
            try:
                reset_token = PasswordResetToken.objects.get(token=token)
                if (timezone.now() - reset_token.created_at) > timedelta(hours=1):
                    raise Exception("Expired")
                user = reset_token.user
                if serializer.validated_data['new_password'] != serializer.validated_data['confirm_password']:
                    return Response({'message': 'Passwords do not match'}, status=400)
                user.set_password(serializer.validated_data['new_password'])
                user.save()
                reset_token.delete()
                return Response({'success': True, 'message': 'Password reset successful'})
            except PasswordResetToken.DoesNotExist:
                return Response({'message': 'Invalid token'}, status=400)
            except:
                return Response({'message': 'Expired or invalid token'}, status=400)
        return Response(serializer.errors, status=400)

Step 4: Migration & Run

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

API List (সংক্ষেপে)

EndpointMethodAuth?Purpose
/api/auth/register/POSTNoRegistration + JWT
/api/auth/login/POSTNoLogin + JWT + Profile
/api/auth/profile/GETYesView profile
/api/auth/profile/update/PUT/PATCHYesUpdate profile + photo
/api/auth/profile/photo-delete/DELETEYesDelete photo
/api/auth/change-password/POSTYesChange password
/api/auth/password-reset/POSTNoSend reset email
/api/auth/password-reset/<token>/POSTNoConfirm new password

How can we help?