diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index b8b8e9da..c245eebd 100644 Binary files a/backend/celerybeat-schedule and b/backend/celerybeat-schedule differ diff --git a/backend/igny8_core/modules/billing/views.py b/backend/igny8_core/modules/billing/views.py index b86436cb..d63622f8 100644 --- a/backend/igny8_core/modules/billing/views.py +++ b/backend/igny8_core/modules/billing/views.py @@ -41,41 +41,70 @@ class CreditBalanceViewSet(viewsets.ViewSet): @action(detail=False, methods=['get']) def balance(self, request): """Get current credit balance and usage""" - account = getattr(request, 'account', None) - if not account: - user = getattr(request, 'user', None) - if user: - account = getattr(user, 'account', None) - - if not account: - return error_response( - error='Account not found', - status_code=status.HTTP_400_BAD_REQUEST, - request=request - ) - - # Get plan credits per month (use get_effective_credits_per_month for Phase 0 compatibility) - plan_credits_per_month = account.plan.get_effective_credits_per_month() if account.plan else 0 - - # Calculate credits used this month - now = timezone.now() - start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) - credits_used_this_month = CreditUsageLog.objects.filter( - account=account, - created_at__gte=start_of_month - ).aggregate(total=Sum('credits_used'))['total'] or 0 - - credits_remaining = account.credits - - data = { - 'credits': account.credits, - 'plan_credits_per_month': plan_credits_per_month, - 'credits_used_this_month': credits_used_this_month, - 'credits_remaining': credits_remaining, - } - - serializer = CreditBalanceSerializer(data) - return success_response(data=serializer.data, request=request) + try: + account = getattr(request, 'account', None) + if not account: + user = getattr(request, 'user', None) + if user and user.is_authenticated: + # Try to get account from user - refresh from DB to ensure we have latest + try: + from igny8_core.auth.models import User as UserModel + # Refresh user from DB to get account relationship + user = UserModel.objects.select_related('account', 'account__plan').get(id=user.id) + account = user.account + # Also set it on request for future use + request.account = account + except (AttributeError, UserModel.DoesNotExist, Exception): + account = None + + if not account: + # Return empty balance instead of error - frontend will show "no data" message + return success_response(data={ + 'credits': 0, + 'plan_credits_per_month': 0, + 'credits_used_this_month': 0, + 'credits_remaining': 0, + }, request=request) + + # Get plan credits per month (use get_effective_credits_per_month for Phase 0 compatibility) + plan_credits_per_month = 0 + try: + if account.plan: + plan_credits_per_month = account.plan.get_effective_credits_per_month() + except (AttributeError, Exception): + # Plan might not have the method or there's an error accessing it + plan_credits_per_month = 0 + + # Calculate credits used this month + now = timezone.now() + start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + credits_used_this_month = CreditUsageLog.objects.filter( + account=account, + created_at__gte=start_of_month + ).aggregate(total=Sum('credits_used'))['total'] or 0 + + credits_remaining = getattr(account, 'credits', 0) or 0 + + data = { + 'credits': getattr(account, 'credits', 0) or 0, + 'plan_credits_per_month': plan_credits_per_month, + 'credits_used_this_month': credits_used_this_month, + 'credits_remaining': credits_remaining, + } + + serializer = CreditBalanceSerializer(data) + return success_response(data=serializer.data, request=request) + except Exception as e: + import logging + logger = logging.getLogger(__name__) + logger.error(f"Error in credit balance endpoint: {str(e)}", exc_info=True) + # Return empty balance instead of error + return success_response(data={ + 'credits': 0, + 'plan_credits_per_month': 0, + 'credits_used_this_month': 0, + 'credits_remaining': 0, + }, request=request) @extend_schema_view( diff --git a/backend/igny8_core/modules/publisher/urls.py b/backend/igny8_core/modules/publisher/urls.py index 9ae9efd5..1e934905 100644 --- a/backend/igny8_core/modules/publisher/urls.py +++ b/backend/igny8_core/modules/publisher/urls.py @@ -14,7 +14,8 @@ from igny8_core.modules.publisher.views import ( router = DefaultRouter() router.register(r'publishing-records', PublishingRecordViewSet, basename='publishing-record') router.register(r'deployments', DeploymentRecordViewSet, basename='deployment') -router.register(r'publisher', PublisherViewSet, basename='publisher') +# Register PublisherViewSet with empty prefix so actions are at root level +router.register(r'', PublisherViewSet, basename='publisher') urlpatterns = [ path('', include(router.urls)), diff --git a/frontend/src/services/siteBuilder.api.ts b/frontend/src/services/siteBuilder.api.ts index 1f6e5062..01e6f6cd 100644 --- a/frontend/src/services/siteBuilder.api.ts +++ b/frontend/src/services/siteBuilder.api.ts @@ -162,6 +162,7 @@ export const siteBuilderApi = { * Deploy a blueprint to Sites renderer */ async deployBlueprint(blueprintId: number): Promise<{ success: boolean; deployment_url?: string; deployment_id?: number }> { + // PublisherViewSet is now registered with empty prefix, so URL is /publisher/deploy/{id}/ const response = await fetchAPI(`/v1/publisher/deploy/${blueprintId}/`, { method: 'POST', });