Compare commits

..

No commits in common. "4b86761ddcaab58e313ed88d5b17ee374441c5ea" and "61a703c7c2684c93aa6eae725ede158eadf36193" have entirely different histories.

6 changed files with 128 additions and 143 deletions

View File

@ -12,7 +12,7 @@ SECRET_KEY = os.getenv('JWT_SECRET', 'django-insecure-fallback-secret-key')
DEBUG = os.getenv('DEBUG')
ALLOWED_HOSTS = ["*"]
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '*').split(',')
CORS_ALLOWED_ORIGINS = os.getenv(
'CORS_ALLOWED_ORIGINS',
@ -33,7 +33,6 @@ INSTALLED_APPS = [
'rest_framework',
'rest_framework_simplejwt',
'corsheaders',
'drf_spectacular',
'users',
'meetings',
@ -143,7 +142,6 @@ DEFAULT_FROM_EMAIL = os.getenv('SMTP_FROM', 'hello@attunehearttherapy.com')
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
@ -155,14 +153,6 @@ REST_FRAMEWORK = {
),
}
# Configure Spectacular settings
SPECTACULAR_SETTINGS = {
'TITLE': 'Blog API',
'DESCRIPTION': 'API for managing users, meetings, and more.',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",

View File

@ -1,15 +1,10 @@
from django.urls import path, include
from django.contrib import admin
from .views import api_root
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/auth/', include('users.urls')),
path('api/meetings/', include('meetings.urls')),
path('', api_root, name='api-root'),
# Swagger UI endpoints
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]

View File

@ -4,12 +4,12 @@ from .views import (
AppointmentRequestListView,
AppointmentRequestCreateView,
AppointmentRequestDetailView,
ScheduleAppointmentView,
RejectAppointmentView,
AvailableDatesView,
UserAppointmentsView,
AppointmentStatsView,
UserAppointmentStatsView
schedule_appointment,
reject_appointment,
available_dates,
user_appointments,
appointment_stats,
user_apointment_stats
)
urlpatterns = [
@ -19,12 +19,12 @@ urlpatterns = [
path('appointments/create/', AppointmentRequestCreateView.as_view(), name='appointment-create'),
path('appointments/<uuid:pk>/', AppointmentRequestDetailView.as_view(), name='appointment-detail'),
path('appointments/<uuid:pk>/schedule/', ScheduleAppointmentView.as_view(), name='appointment-schedule'),
path('appointments/<uuid:pk>/reject/', RejectAppointmentView.as_view(), name='appointment-reject'),
path('appointments/<uuid:pk>/schedule/', schedule_appointment, name='appointment-schedule'),
path('appointments/<uuid:pk>/reject/', reject_appointment, name='appointment-reject'),
path('appointments/available-dates/', AvailableDatesView.as_view(), name='available-dates'),
path('user/appointments/', UserAppointmentsView.as_view(), name='user-appointments'),
path('appointments/available-dates/', available_dates, name='available-dates'),
path('user/appointments/', user_appointments, name='user-appointments'),
path('appointments/stats/', AppointmentStatsView.as_view(), name='appointment-stats'),
path('user/appointments/stats/', UserAppointmentStatsView.as_view(), name='user-appointment-stats'),
path('appointments/stats/', appointment_stats, name='appointment-stats'),
path('user/appointments/stats/', user_apointment_stats, name='user-appointment-stats'),
]

View File

@ -1,7 +1,7 @@
from rest_framework import generics, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny,IsAdminUser
from rest_framework.permissions import IsAuthenticated, AllowAny
from django.utils import timezone
from datetime import datetime, timedelta
from .models import AdminWeeklyAvailability, AppointmentRequest
@ -14,11 +14,9 @@ from .serializers import (
)
from .email_service import EmailService
from users.models import CustomUser
from django.db.models import Count, Q
class AdminAvailabilityView(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated, IsAdminUser]
permission_classes = [IsAuthenticated]
serializer_class = AdminWeeklyAvailabilitySerializer
def get_object(self):
@ -65,24 +63,22 @@ class AppointmentRequestDetailView(generics.RetrieveAPIView):
serializer_class = AppointmentRequestSerializer
lookup_field = 'pk'
class ScheduleAppointmentView(generics.GenericAPIView):
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = AppointmentScheduleSerializer
queryset = AppointmentRequest.objects.all()
lookup_field = 'pk'
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def schedule_appointment(request, pk):
try:
appointment = AppointmentRequest.objects.get(pk=pk)
except AppointmentRequest.DoesNotExist:
return Response({'error': 'Appointment not found'}, status=status.HTTP_404_NOT_FOUND)
def post(self, request, pk):
appointment = self.get_object()
if appointment.status != 'pending_review':
return Response(
{'error': 'Only pending review appointments can be scheduled.'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if appointment.status != 'pending_review':
return Response(
{'error': 'Only pending review appointments can be scheduled.'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = AppointmentScheduleSerializer(data=request.data)
if serializer.is_valid():
appointment.schedule_appointment(
datetime_obj=serializer.validated_data['scheduled_datetime'],
duration=serializer.validated_data['scheduled_duration']
@ -96,106 +92,104 @@ class ScheduleAppointmentView(generics.GenericAPIView):
'message': 'Appointment scheduled successfully. Jitsi meeting created.',
'jitsi_meeting_created': appointment.has_jitsi_meeting
})
class RejectAppointmentView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = AppointmentRejectSerializer
queryset = AppointmentRequest.objects.all()
lookup_field = 'pk'
def post(self, request, pk):
appointment = self.get_object()
if appointment.status != 'pending_review':
return Response(
{'error': 'Only pending appointments can be rejected'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
appointment.reject_appointment(
serializer.validated_data.get('rejection_reason', '')
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def reject_appointment(request, pk):
try:
appointment = AppointmentRequest.objects.get(pk=pk)
except AppointmentRequest.DoesNotExist:
return Response({'error': 'Appointment not found'}, status=status.HTTP_404_NOT_FOUND)
if appointment.status != 'pending_review':
return Response(
{'error': 'Only pending appointments can be rejected'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = AppointmentRejectSerializer(data=request.data)
if serializer.is_valid():
appointment.reject_appointment(serializer.validated_data.get('rejection_reason', ''))
EmailService.send_appointment_rejected(appointment)
response_serializer = AppointmentRequestSerializer(appointment)
return Response(response_serializer.data)
return Response(AppointmentRequestSerializer(appointment).data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class AvailableDatesView(generics.GenericAPIView):
permission_classes = [AllowAny]
@api_view(['GET'])
@permission_classes([AllowAny])
def available_dates(request):
availability = AdminWeeklyAvailability.objects.first()
if not availability:
return Response([])
def get(self, request):
availability = AdminWeeklyAvailability.objects.first()
if not availability:
return Response([])
available_days = availability.available_days
today = timezone.now().date()
available_dates = []
for i in range(1, 31):
date = today + timedelta(days=i)
if date.weekday() in available_days:
available_dates.append(date.strftime('%Y-%m-%d'))
return Response(available_dates)
available_days = availability.available_days
today = timezone.now().date()
available_dates = []
for i in range(1, 31):
date = today + timedelta(days=i)
if date.weekday() in available_days:
available_dates.append(date.strftime('%Y-%m-%d'))
return Response(available_dates)
class UserAppointmentsView(generics.ListAPIView):
permission_classes = [IsAuthenticated]
serializer_class = AppointmentRequestSerializer
def get_queryset(self):
return AppointmentRequest.objects.filter(
email=self.request.user.email
).order_by('-created_at')
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_appointments(request):
appointments = AppointmentRequest.objects.filter(
email=request.user.email
).order_by('-created_at')
serializer = AppointmentRequestSerializer(appointments, many=True)
return Response(serializer.data)
class AppointmentStatsView(generics.GenericAPIView):
permission_classes = [IsAuthenticated, IsAdminUser]
def get(self, request):
total = AppointmentRequest.objects.count()
users = CustomUser.objects.filter(is_staff=False).count()
pending = AppointmentRequest.objects.filter(status='pending_review').count()
scheduled = AppointmentRequest.objects.filter(status='scheduled').count()
rejected = AppointmentRequest.objects.filter(status='rejected').count()
return Response({
'total_requests': total,
'pending_review': pending,
'scheduled': scheduled,
'rejected': rejected,
'users': users,
'completion_rate': round((scheduled / total * 100), 2) if total > 0 else 0
})
class UserAppointmentStatsView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
def get(self, request):
stats = AppointmentRequest.objects.filter(
email=request.user.email
).aggregate(
total=Count('id'),
pending=Count('id', filter=Q(status='pending_review')),
scheduled=Count('id', filter=Q(status='scheduled')),
rejected=Count('id', filter=Q(status='rejected')),
completed=Count('id', filter=Q(status='completed'))
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def appointment_stats(request):
if not request.user.is_staff:
return Response(
{'error': 'Unauthorized'},
status=status.HTTP_403_FORBIDDEN
)
total = stats['total']
scheduled = stats['scheduled']
completion_rate = round((scheduled / total * 100), 2) if total > 0 else 0
return Response({
'total_requests': total,
'pending_review': stats['pending'],
'scheduled': scheduled,
'rejected': stats['rejected'],
'completed': stats['completed'],
'completion_rate': completion_rate
})
total = AppointmentRequest.objects.count()
users = CustomUser.objects.filter(is_staff=False).count()
pending = AppointmentRequest.objects.filter(status='pending_review').count()
scheduled = AppointmentRequest.objects.filter(status='scheduled').count()
rejected = AppointmentRequest.objects.filter(status='rejected').count()
return Response({
'total_requests': total,
'pending_review': pending,
'scheduled': scheduled,
'rejected': rejected,
'users': users,
'completion_rate': round((scheduled / total * 100), 2) if total > 0 else 0
})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_apointment_stats(request):
if not request.user.is_staff:
return Response(
{'error': 'Unauthorized'},
status=status.HTTP_403_FORBIDDEN
)
total = AppointmentRequest.objects.filter(email=request.user.email).count()
pending = AppointmentRequest.objects.filter(email=request.user.email, status='pending_review').count()
scheduled = AppointmentRequest.objects.filter(email=request.user.email, status='scheduled').count()
rejected = AppointmentRequest.objects.filter(email=request.user.email, status='rejected').count()
completed = AppointmentRequest.objects.filter(email=request.user.email, status='completed').count()
return Response({
'total_requests': total,
'pending_review': pending,
'scheduled': scheduled,
'rejected': rejected,
'completed': completed,
'completion_rate': round((scheduled / total * 100), 2) if total > 0 else 0
})

Binary file not shown.

View File

@ -1,7 +1,7 @@
from rest_framework import status, generics
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import authenticate
from .models import CustomUser, UserProfile
@ -359,6 +359,10 @@ class UserDetailView(generics.RetrieveAPIView):
def get_object(self):
return self.request.user
def IsAdminUser(user):
return user.is_staff
class GetAllUsersView(generics.ListAPIView):
serializer_class = UserSerializer
permission_classes = [IsAdminUser, IsAuthenticated]
@ -385,8 +389,10 @@ class DeleteUserView(generics.DestroyAPIView):
def perform_destroy(self, instance):
instance.delete()
# Delete associated UserProfile
UserProfile.objects.filter(user=instance).delete()
# Delete associated AppointmentRequests
AppointmentRequest.objects.filter(email=instance.email).delete()
class ActivateOrDeactivateUserView(generics.UpdateAPIView):