Files
smoothschedule/smoothschedule/CALENDAR_SYNC_INTEGRATION.md
poduck e4ad7fca87 feat: Plan-based feature permissions and quota enforcement
Backend:
- Add HasQuota() permission factory for quota limits (resources, users, services, appointments, email templates, automated tasks)
- Add HasFeaturePermission() factory for feature-based permissions (SMS, masked calling, custom domains, white label, plugins, webhooks, calendar sync, analytics)
- Add has_feature() method to Tenant model for flexible permission checking
- Add new tenant permission fields: can_create_plugins, can_use_webhooks, can_use_calendar_sync, can_export_data
- Create Data Export API with CSV/JSON support for appointments, customers, resources, services
- Create Analytics API with dashboard, appointments, revenue endpoints
- Add calendar sync views and URL configuration

Frontend:
- Add usePlanFeatures hook for checking feature availability
- Add UpgradePrompt components (inline, banner, overlay variants)
- Add LockedSection wrapper and LockedButton for feature gating
- Update settings pages with permission checks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 11:21:11 -05:00

9.4 KiB

Calendar Sync Feature Integration Guide

This document explains how the calendar sync feature permission system works and provides examples for implementation.

Overview

The calendar sync feature allows tenants to:

  1. Connect to Google Calendar or Outlook Calendar via OAuth
  2. Sync calendar events into the scheduling system
  3. Manage multiple calendar integrations

The feature is gated by the can_use_calendar_sync permission that must be enabled at the tenant level.

Architecture

1. Database Model (Tenant)

Added to /home/poduck/Desktop/smoothschedule2/smoothschedule/core/models.py:

can_use_calendar_sync = models.BooleanField(
    default=False,
    help_text="Whether this business can sync Google Calendar and other calendar providers"
)

2. Permission Check (HasFeaturePermission)

Updated in /home/poduck/Desktop/smoothschedule2/smoothschedule/core/permissions.py:

FEATURE_NAMES = {
    # ... other features ...
    'can_use_calendar_sync': 'Calendar Sync',
}

The HasFeaturePermission factory function creates a DRF permission class:

from core.permissions import HasFeaturePermission

permission_classes = [IsAuthenticated, HasFeaturePermission('can_use_calendar_sync')]

3. OAuth Integration

Modified in /home/poduck/Desktop/smoothschedule2/smoothschedule/core/oauth_views.py:

  • GoogleOAuthInitiateView: Checks permission when purpose is "calendar"
  • MicrosoftOAuthInitiateView: Checks permission when purpose is "calendar"
def post(self, request):
    purpose = request.data.get('purpose', 'email')

    # Check calendar sync permission if purpose is calendar
    if purpose == 'calendar':
        calendar_permission = HasFeaturePermission('can_use_calendar_sync')
        if not calendar_permission().has_permission(request, self):
            return Response({
                'success': False,
                'error': 'Your current plan does not include Calendar Sync.',
            }, status=status.HTTP_403_FORBIDDEN)

    # Continue with OAuth flow...

4. Calendar Sync Views

Created in /home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/calendar_sync_views.py:

  • CalendarListView: Lists connected calendars
  • CalendarSyncView: Syncs calendar events
  • CalendarDeleteView: Disconnects calendar integration
  • CalendarStatusView: Gets calendar sync status

Permission Pattern in Views

from core.permissions import HasFeaturePermission
from rest_framework.permissions import IsAuthenticated

class CalendarSyncPermission(IsAuthenticated):
    """Custom permission combining auth + feature check"""
    def has_permission(self, request, view):
        if not super().has_permission(request, view):
            return False

        tenant = getattr(request, 'tenant', None)
        if not tenant:
            return False

        return tenant.has_feature('can_use_calendar_sync')


class CalendarListView(APIView):
    permission_classes = [CalendarSyncPermission]

    def get(self, request):
        # This endpoint is only accessible if:
        # 1. User is authenticated
        # 2. Tenant has can_use_calendar_sync enabled
        ...

Usage Examples

1. Enable Calendar Sync for a Tenant

# Via Django shell or management command
from core.models import Tenant

tenant = Tenant.objects.get(schema_name='demo')
tenant.can_use_calendar_sync = True
tenant.save()

# Check if tenant has feature
if tenant.has_feature('can_use_calendar_sync'):
    print("Calendar sync enabled!")

2. API Endpoints

Initiate Google Calendar OAuth

POST /api/oauth/google/initiate/
Content-Type: application/json

{
  "purpose": "calendar"
}

# Response (if permission granted):
{
  "success": true,
  "authorization_url": "https://accounts.google.com/o/oauth2/auth?..."
}

# Response (if permission denied):
{
  "success": false,
  "error": "Your current plan does not include Calendar Sync. Please upgrade..."
}

List Connected Calendars

GET /api/calendar/list/

# Response:
{
  "success": true,
  "calendars": [
    {
      "id": 1,
      "provider": "Google",
      "email": "user@gmail.com",
      "is_valid": true,
      "is_expired": false,
      "last_used_at": "2025-12-02T10:30:00Z",
      "created_at": "2025-12-01T08:15:00Z"
    }
  ]
}

Sync Calendar Events

POST /api/calendar/sync/
Content-Type: application/json

{
  "credential_id": 1,
  "calendar_id": "primary",
  "start_date": "2025-12-01",
  "end_date": "2025-12-31"
}

# Response:
{
  "success": true,
  "message": "Calendar sync started for user@gmail.com"
}

Check Calendar Sync Status

GET /api/calendar/status/

# Response (if feature enabled):
{
  "success": true,
  "can_use_calendar_sync": true,
  "total_connected": 2,
  "feature_enabled": true
}

# Response (if feature not enabled):
{
  "success": true,
  "can_use_calendar_sync": false,
  "message": "Calendar Sync feature is not available for your plan",
  "total_connected": 0
}

Permission Flow Diagram

User Request to Calendar Endpoint
    ↓
[Is User Authenticated?]
    ├─ NO → 401 Unauthorized
    └─ YES ↓
[Is Request in Tenant Context?]
    ├─ NO → 400 Bad Request
    └─ YES ↓
[Does Tenant have can_use_calendar_sync?]
    ├─ NO → 403 Permission Denied (upgrade message)
    └─ YES ↓
[Process Request]
    ├─ Success → 200 OK
    └─ Error → 500 Server Error

Integration with ViewSets

For ModelViewSet endpoints (like scheduling events from calendar sync):

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from core.permissions import HasFeaturePermission
from schedule.models import Event

class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.all()
    serializer_class = EventSerializer

    # Permission for create/update/delete operations
    permission_classes = [IsAuthenticated, HasFeaturePermission('can_use_calendar_sync')]

    def get_queryset(self):
        # Only show events that user's tenant can access
        return Event.objects.filter(tenant=self.request.tenant)

    @action(detail=False, methods=['post'])
    def sync_from_calendar(self, request):
        """Create events from a calendar sync"""
        # This action is protected by the permission_classes
        calendar_id = request.data.get('calendar_id')
        # ... implement sync logic ...

Note: If you only want to gate specific actions, override get_permissions():

def get_permissions(self):
    if self.action in ['create', 'update', 'destroy', 'sync_from_calendar']:
        # Only allow these actions if calendar sync is enabled
        return [IsAuthenticated(), HasFeaturePermission('can_use_calendar_sync')()]

    # Read-only actions don't require calendar sync permission
    return [IsAuthenticated()]

Migration

The migration 0016_tenant_can_use_calendar_sync.py adds the field to existing tenants with default value of False.

To apply the migration:

cd /home/poduck/Desktop/smoothschedule2/smoothschedule
docker compose -f docker-compose.local.yml exec django python manage.py migrate

Testing

Test Permission Denied

from django.test import TestCase, RequestFactory
from rest_framework.test import APITestCase
from core.models import Tenant
from smoothschedule.users.models import User

class CalendarSyncTests(APITestCase):
    def setUp(self):
        self.tenant = Tenant.objects.create(
            schema_name='test',
            name='Test Tenant',
            can_use_calendar_sync=False  # Feature disabled
        )
        self.user = User.objects.create_user(
            email='user@test.com',
            password='testpass',
            tenant=self.tenant
        )
        self.client.force_authenticate(user=self.user)

    def test_calendar_sync_permission_denied(self):
        """Test that users without permission cannot access calendar sync"""
        response = self.client.post('/api/calendar/list/')

        # Should return 403 Forbidden with upgrade message
        self.assertEqual(response.status_code, 403)
        self.assertIn('upgrade', response.json()['error'].lower())

    def test_calendar_sync_permission_granted(self):
        """Test that users with permission can access calendar sync"""
        self.tenant.can_use_calendar_sync = True
        self.tenant.save()

        response = self.client.get('/api/calendar/list/')

        # Should return 200 OK
        self.assertEqual(response.status_code, 200)

Security Considerations

  1. Permission is checked at view level: The CalendarSyncPermission checks that the tenant has the feature enabled
  2. Tenant isolation: OAuthCredential queries filter by tenant to ensure data isolation
  3. Token security: Tokens are stored encrypted at rest (configured in settings)
  4. CSRF protection: OAuth state parameter prevents CSRF attacks
  5. Audit logging: All calendar sync operations are logged with tenant and user information
  • Model: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/models.py (Tenant.can_use_calendar_sync)
  • Permission: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/permissions.py (HasFeaturePermission)
  • OAuth Views: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/oauth_views.py
  • Calendar Views: /home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/calendar_sync_views.py
  • Migration: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/migrations/0016_tenant_can_use_calendar_sync.py