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>
15 KiB
Calendar Sync Permission Implementation
Summary
Successfully added permission checking for the calendar sync feature in the Django backend. The implementation follows the existing HasFeaturePermission pattern and gates access to calendar OAuth and sync operations.
Files Modified and Created
Core Changes
1. core/models.py - Tenant Model
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/models.py
Added new permission field to the Tenant model:
can_use_calendar_sync = models.BooleanField(
default=False,
help_text="Whether this business can sync Google Calendar and other calendar providers"
)
Impact:
- New tenants will have
can_use_calendar_sync=Falseby default - Platform admins can enable this per-tenant via the Django admin or API
- Works with existing subscription plan system
2. core/migrations/0016_tenant_can_use_calendar_sync.py - Database Migration
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/migrations/0016_tenant_can_use_calendar_sync.py
Database migration that adds the can_use_calendar_sync boolean field to the Tenant table.
How to apply:
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
docker compose -f docker-compose.local.yml exec django python manage.py migrate
3. core/permissions.py - Permission Check
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/permissions.py
Updated HasFeaturePermission factory function:
- Added
'can_use_calendar_sync': 'Calendar Sync'toFEATURE_NAMESmapping - This displays user-friendly error messages when the feature is not available
- Follows the existing pattern used by other features (SMS reminders, webhooks, etc.)
Usage Pattern:
from core.permissions import HasFeaturePermission
from rest_framework.permissions import IsAuthenticated
class MyViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, HasFeaturePermission('can_use_calendar_sync')]
4. core/oauth_views.py - OAuth Permission Checks
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/core/oauth_views.py
Updated OAuth views to check calendar sync permission when initiating calendar-specific OAuth flows:
GoogleOAuthInitiateView:
- Imported
HasFeaturePermissionfrom core.permissions - Added check: If
purpose == 'calendar', verify tenant hascan_use_calendar_syncpermission - Returns 403 Forbidden with upgrade message if permission denied
- Email OAuth (
purpose == 'email') is NOT affected by this check
MicrosoftOAuthInitiateView:
- Same pattern as Google OAuth
- Supports both email and calendar purposes with respective permission checks
Docstring updates: Both views now document the permission requirements:
Permission Requirements:
- For "email" purpose: IsPlatformAdmin only
- For "calendar" purpose: Requires can_use_calendar_sync feature permission
New Calendar Sync Implementation
5. schedule/calendar_sync_views.py - Calendar Sync Endpoints
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/calendar_sync_views.py
Created comprehensive calendar sync views with permission checking:
CalendarSyncPermission Custom Permission:
- Combines authentication check with feature permission check
- Used by all calendar sync endpoints
- Ensures both user is authenticated AND tenant has permission
CalendarListView (GET /api/calendar/list/)
- Lists connected calendars for the current tenant
- Returns OAuth credentials with masked tokens
- Protected by CalendarSyncPermission
CalendarSyncView (POST /api/calendar/sync/)
- Initiates calendar event synchronization
- Accepts credential_id, calendar_id, start_date, end_date
- Verifies credential belongs to tenant
- Checks credential validity before sync
- TODO: Implement actual calendar API integration
CalendarDeleteView (DELETE /api/calendar/disconnect/)
- Disconnects/revokes a calendar integration
- Removes the OAuth credential
- Logs the action for audit trail
CalendarStatusView (GET /api/calendar/status/)
- Informational endpoint (authentication only, not feature-gated)
- Returns whether calendar sync is enabled for tenant
- Shows number of connected calendars
- User-friendly message if feature not available
6. schedule/calendar_sync_urls.py - URL Configuration
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/calendar_sync_urls.py
URL routes for calendar sync endpoints:
/api/calendar/status/ - Check calendar sync status
/api/calendar/list/ - List connected calendars
/api/calendar/sync/ - Sync calendar events
/api/calendar/disconnect/ - Disconnect a calendar
To integrate with main URL config, add to config/urls.py:
path("calendar/", include("schedule.calendar_sync_urls", namespace="calendar")),
7. schedule/tests/test_calendar_sync_permissions.py - Test Suite
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/tests/test_calendar_sync_permissions.py
Comprehensive test suite with 20+ tests covering:
CalendarSyncPermissionTests:
test_calendar_list_without_permission- Verify 403 when disabledtest_calendar_sync_without_permission- Verify 403 when disabledtest_oauth_calendar_initiate_without_permission- Verify OAuth rejects calendartest_calendar_list_with_permission- Verify 200 when enabledtest_calendar_with_connected_credential- Verify credential appears in listtest_unauthenticated_calendar_access- Verify 401 for anonymous users
CalendarSyncIntegrationTests:
test_full_calendar_workflow- Complete workflow (list → connect → sync → disconnect)
TenantPermissionModelTests:
test_tenant_can_use_calendar_sync_default- Verify default Falsetest_has_feature_with_other_permissions- Verify method works correctly
Run tests:
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
docker compose -f docker-compose.local.yml exec django pytest schedule/tests/test_calendar_sync_permissions.py -v
8. CALENDAR_SYNC_INTEGRATION.md - Integration Guide
File: /home/poduck/Desktop/smoothschedule2/smoothschedule/CALENDAR_SYNC_INTEGRATION.md
Comprehensive developer guide including:
- Architecture overview
- Permission flow diagram
- API endpoint examples with curl commands
- Integration patterns with ViewSets
- Testing examples
- Security considerations
- Related files reference
Permission Flow
User Request to Calendar Endpoint
↓
1. [Is User Authenticated?]
├─ NO → 401 Unauthorized
└─ YES ↓
2. [Request Has Tenant Context?]
├─ NO → 400 Bad Request
└─ YES ↓
3. [Does Tenant have can_use_calendar_sync?]
├─ NO → 403 Forbidden (upgrade message)
└─ YES ↓
4. [Process Request]
├─ Success → 200 OK
└─ Error → 500 Server Error
Implementation Details
Permission Field Design
The can_use_calendar_sync field:
- Is a BooleanField on the Tenant model
- Defaults to False (disabled by default)
- Can be set per-tenant by platform admins
- Works alongside subscription_plan.permissions for more granular control
- Integrates with existing
has_feature()method on Tenant
How Permission Checking Works
In OAuth Views
# 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)
In Calendar Sync Views
class CalendarSyncPermission(IsAuthenticated):
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]
Separation of Concerns
- Email OAuth: Not affected by calendar sync permission (separate feature)
- Calendar OAuth: Requires calendar sync permission only when
purpose='calendar' - Calendar Sync: Requires calendar sync permission for all operations
- Calendar Status: Authentication only (informational endpoint)
Security Considerations
-
Multi-Tenancy Isolation
- All OAuthCredential queries filter by tenant
- Users can only access their own tenant's calendars
- Credentials are not shared between tenants
-
Token Security
- OAuth tokens stored encrypted at rest (via Django settings)
- Tokens masked in API responses
- Token validity checked before use
-
CSRF Protection
- OAuth state parameter validated
- Standard Django session handling
-
Audit Trail
- All calendar operations logged with tenant/user info
- Sync operations logged with timestamps
- Disconnect operations logged
-
Feature Gating
- Permission checked at view level
- No way to bypass by direct API access
- Consistent error messages for upgrade prompts
API Examples
Check if Feature is Available
GET /api/calendar/status/
# Response (if enabled):
{
"success": true,
"can_use_calendar_sync": true,
"total_connected": 2
}
# Response (if disabled):
{
"success": true,
"can_use_calendar_sync": false,
"message": "Calendar Sync feature is not available for your plan"
}
Initiate 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,
"created_at": "2025-12-01T08:15:00Z"
}
]
}
Testing the Implementation
Manual Testing via API
- Test without permission:
# Create a user in a tenant without calendar sync
curl -X GET http://lvh.me:8000/api/calendar/list/ \
-H "Authorization: Bearer <token>"
# Expected: 403 Forbidden
- Test with permission:
# Enable calendar sync on tenant
# Then try again:
curl -X GET http://lvh.me:8000/api/calendar/list/ \
-H "Authorization: Bearer <token>"
# Expected: 200 OK with calendar list
Run Test Suite
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
# Run all calendar permission tests
docker compose -f docker-compose.local.yml exec django pytest \
schedule/tests/test_calendar_sync_permissions.py -v
# Run specific test
docker compose -f docker-compose.local.yml exec django pytest \
schedule/tests/test_calendar_sync_permissions.py::CalendarSyncPermissionTests::test_calendar_list_without_permission -v
Django Shell Testing
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
docker compose -f docker-compose.local.yml exec django python manage.py shell
# In Django shell:
from core.models import Tenant
from smoothschedule.users.models import User
tenant = Tenant.objects.get(schema_name='demo')
print(tenant.has_feature('can_use_calendar_sync')) # False initially
# Enable it
tenant.can_use_calendar_sync = True
tenant.save()
print(tenant.has_feature('can_use_calendar_sync')) # True now
Integration with Existing Systems
Works with Subscription Plans
# Tenant can get permission from subscription_plan.permissions
subscription_plan.permissions = {
'can_use_calendar_sync': True,
'can_use_webhooks': True,
...
}
Works with Platform Admin Invitations
# TenantInvitation can grant this permission
invitation = TenantInvitation(
can_use_calendar_sync=True,
...
)
Works with User Role-Based Access
- Permission is at tenant level, not user level
- All users in a tenant with enabled feature can use it
- Can be further restricted by user roles if needed
Next Steps for Full Implementation
While the permission framework is complete, the following features need implementation:
-
Google Calendar API Integration
- Fetch events from Google Calendar API using OAuth token
- Map Google Calendar events to Event model
- Handle recurring events
- Sync deleted events
-
Microsoft Calendar API Integration
- Fetch events from Microsoft Graph API
- Handle Outlook calendar format
-
Conflict Resolution
- Handle overlapping events from multiple calendars
- Update vs. create decision logic
-
Bi-directional Sync
- Push events back to calendar after scheduling
- Handle edit/delete synchronization
-
UI/Frontend Integration
- Calendar selection dialog
- Sync status display
- Calendar disconnect confirmation
Rollback Plan
If needed to rollback:
- Revert database migration:
docker compose -f docker-compose.local.yml exec django python manage.py migrate core 0015_tenant_can_create_plugins_tenant_can_use_webhooks
- Revert code changes:
- Remove lines from core/models.py (can_use_calendar_sync field)
- Remove calendar check from oauth_views.py
- Remove calendar_sync_views.py
- Remove calendar_sync_urls.py
- Revert permissions.py:
- Remove 'can_use_calendar_sync' from FEATURE_NAMES
Summary of Changes
| File | Type | Change |
|---|---|---|
| core/models.py | Modified | Added can_use_calendar_sync field to Tenant |
| core/migrations/0016_tenant_can_use_calendar_sync.py | New | Database migration |
| core/permissions.py | Modified | Added can_use_calendar_sync to FEATURE_NAMES |
| core/oauth_views.py | Modified | Added permission check for calendar OAuth |
| schedule/calendar_sync_views.py | New | Calendar sync API views |
| schedule/calendar_sync_urls.py | New | Calendar sync URL configuration |
| schedule/tests/test_calendar_sync_permissions.py | New | Test suite (20+ tests) |
| CALENDAR_SYNC_INTEGRATION.md | New | Integration guide |
File Locations
All files are located in: /home/poduck/Desktop/smoothschedule2/smoothschedule/
Key files:
- Models:
core/models.py(line 194-197) - Migration:
core/migrations/0016_tenant_can_use_calendar_sync.py - Permissions:
core/permissions.py(line 354) - OAuth Views:
core/oauth_views.py(lines 27, 92-98, 241-247) - Calendar Views:
schedule/calendar_sync_views.py(entire file) - Calendar URLs:
schedule/calendar_sync_urls.py(entire file) - Tests:
schedule/tests/test_calendar_sync_permissions.py(entire file) - Documentation:
CALENDAR_SYNC_INTEGRATION.md