diff --git a/backend/igny8_core/business/billing/billing_views.py b/backend/igny8_core/business/billing/billing_views.py index 0a8cdc4a..058b2032 100644 --- a/backend/igny8_core/business/billing/billing_views.py +++ b/backend/igny8_core/business/billing/billing_views.py @@ -1032,7 +1032,7 @@ from rest_framework.decorators import api_view, permission_classes def get_usage_summary(request): """ Get comprehensive usage summary for current account. - Includes hard limits (sites, users, keywords, clusters) and monthly limits (ideas, words, images). + Includes hard limits (sites, users, keywords) and monthly limits (ahrefs queries only). GET /api/v1/billing/usage-summary/ """ diff --git a/backend/igny8_core/business/billing/models.py b/backend/igny8_core/business/billing/models.py index 499468c8..2fedaeab 100644 --- a/backend/igny8_core/business/billing/models.py +++ b/backend/igny8_core/business/billing/models.py @@ -257,12 +257,11 @@ class BillingConfiguration(models.Model): class PlanLimitUsage(AccountBaseModel): """ - Track monthly usage of plan limits (ideas, words, images, prompts) + Track monthly usage of plan limits (ideas, images, prompts) Resets at start of each billing period """ LIMIT_TYPE_CHOICES = [ ('content_ideas', 'Content Ideas'), - ('content_words', 'Content Words'), ('images_basic', 'Basic Images'), ('images_premium', 'Premium Images'), ('image_prompts', 'Image Prompts'), diff --git a/backend/igny8_core/business/content/models.py b/backend/igny8_core/business/content/models.py index e7821b00..34c9ccc8 100644 --- a/backend/igny8_core/business/content/models.py +++ b/backend/igny8_core/business/content/models.py @@ -358,31 +358,8 @@ class Content(SoftDeletableModel, SiteSectorBaseModel): super().save(*args, **kwargs) - # Increment usage for new content or if word count increased - if self.content_html and self.word_count: - # Only count newly generated words - new_words = self.word_count - old_word_count if not is_new else self.word_count - - if new_words > 0: - from igny8_core.business.billing.services.limit_service import LimitService - try: - # Get account from site - account = self.site.account if self.site else None - if account: - LimitService.increment_usage( - account=account, - limit_type='content_words', - amount=new_words, - metadata={ - 'content_id': self.id, - 'content_title': self.title, - 'site_id': self.site.id if self.site else None, - } - ) - except Exception as e: - import logging - logger = logging.getLogger(__name__) - logger.error(f"Error incrementing word usage for content {self.id}: {str(e)}") + # NOTE: Content words no longer tracked as a monthly plan limit. + # Credits are the only enforcement for content generation. def soft_delete(self, user=None, reason=None, retention_days=None): """ diff --git a/backend/igny8_core/business/content/services/content_generation_service.py b/backend/igny8_core/business/content/services/content_generation_service.py index 98968410..6d4fc774 100644 --- a/backend/igny8_core/business/content/services/content_generation_service.py +++ b/backend/igny8_core/business/content/services/content_generation_service.py @@ -30,20 +30,12 @@ class ContentGenerationService: Raises: InsufficientCreditsError: If account doesn't have enough credits """ - from igny8_core.business.billing.services.limit_service import LimitService, MonthlyLimitExceededError - # Get tasks tasks = Tasks.objects.filter(id__in=task_ids, account=account) # Calculate estimated credits needed based on word count total_word_count = sum(task.word_count or 1000 for task in tasks) - # Check monthly word count limit - try: - LimitService.check_monthly_limit(account, 'content_words', amount=total_word_count) - except MonthlyLimitExceededError as e: - raise InsufficientCreditsError(str(e)) - # Check credits try: self.credit_service.check_credits(account, 'content_generation', total_word_count) diff --git a/backend/igny8_core/modules/writer/views.py b/backend/igny8_core/modules/writer/views.py index f9fb6895..9b6120a2 100644 --- a/backend/igny8_core/modules/writer/views.py +++ b/backend/igny8_core/modules/writer/views.py @@ -292,7 +292,6 @@ class TasksViewSet(SiteSectorModelViewSet): content_type_filter = request.query_params.get('content_type', '') content_structure_filter = request.query_params.get('content_structure', '') cluster_filter = request.query_params.get('cluster', '') - source_filter = request.query_params.get('source', '') search = request.query_params.get('search', '') # Apply search to base queryset @@ -310,8 +309,6 @@ class TasksViewSet(SiteSectorModelViewSet): status_qs = status_qs.filter(content_structure=content_structure_filter) if cluster_filter: status_qs = status_qs.filter(cluster_id=cluster_filter) - if source_filter: - status_qs = status_qs.filter(source=source_filter) statuses = list(set(status_qs.values_list('status', flat=True))) statuses = sorted([s for s in statuses if s]) status_labels = { @@ -333,8 +330,6 @@ class TasksViewSet(SiteSectorModelViewSet): type_qs = type_qs.filter(content_structure=content_structure_filter) if cluster_filter: type_qs = type_qs.filter(cluster_id=cluster_filter) - if source_filter: - type_qs = type_qs.filter(source=source_filter) content_types = list(set(type_qs.values_list('content_type', flat=True))) content_types = sorted([t for t in content_types if t]) type_labels = { @@ -356,8 +351,6 @@ class TasksViewSet(SiteSectorModelViewSet): structure_qs = structure_qs.filter(content_type=content_type_filter) if cluster_filter: structure_qs = structure_qs.filter(cluster_id=cluster_filter) - if source_filter: - structure_qs = structure_qs.filter(source=source_filter) structures = list(set(structure_qs.values_list('content_structure', flat=True))) structures = sorted([s for s in structures if s]) structure_labels = { @@ -381,8 +374,6 @@ class TasksViewSet(SiteSectorModelViewSet): cluster_qs = cluster_qs.filter(content_type=content_type_filter) if content_structure_filter: cluster_qs = cluster_qs.filter(content_structure=content_structure_filter) - if source_filter: - cluster_qs = cluster_qs.filter(source=source_filter) from igny8_core.modules.planner.models import Clusters cluster_ids = list(set( cluster_qs.exclude(cluster_id__isnull=True) @@ -394,35 +385,12 @@ class TasksViewSet(SiteSectorModelViewSet): for c in clusters ] - # Get sources (filtered by other fields) - source_qs = base_qs - if status_filter: - source_qs = source_qs.filter(status=status_filter) - if content_type_filter: - source_qs = source_qs.filter(content_type=content_type_filter) - if content_structure_filter: - source_qs = source_qs.filter(content_structure=content_structure_filter) - if cluster_filter: - source_qs = source_qs.filter(cluster_id=cluster_filter) - sources = list(set(source_qs.values_list('source', flat=True))) - sources = sorted([s for s in sources if s]) - source_labels = { - 'manual': 'Manual', - 'planner': 'Planner', - 'ai': 'AI', - } - source_options = [ - {'value': s, 'label': source_labels.get(s, s.title())} - for s in sources - ] - return success_response( data={ 'statuses': status_options, 'content_types': content_type_options, 'content_structures': content_structure_options, 'clusters': cluster_options, - 'sources': source_options, }, request=request ) @@ -970,16 +938,6 @@ class ContentViewSet(SiteSectorModelViewSet): word_count = calculate_word_count(html_content) serializer.validated_data['word_count'] = word_count - # Check monthly word count limit (enforces ALL entry points: manual, import, AI, automation) - if account and word_count > 0: - from igny8_core.business.billing.services.limit_service import LimitService, MonthlyLimitExceededError - from rest_framework.exceptions import ValidationError - - try: - LimitService.check_monthly_limit(account, 'content_words', amount=word_count) - except MonthlyLimitExceededError as e: - raise ValidationError(str(e)) - if account: serializer.save(account=account) else: diff --git a/backend/igny8_core/tasks/plan_limits.py b/backend/igny8_core/tasks/plan_limits.py index b7a668d7..c128c066 100644 --- a/backend/igny8_core/tasks/plan_limits.py +++ b/backend/igny8_core/tasks/plan_limits.py @@ -22,11 +22,7 @@ def reset_monthly_plan_limits(): It finds all accounts where the billing period has ended and resets their monthly usage. Monthly limits that get reset: - - content_ideas - - content_words - - images_basic - - images_premium - - image_prompts + - ahrefs_queries Hard limits (sites, users, keywords, clusters) are NOT reset. """ diff --git a/frontend/src/components/common/ProgressModal.tsx b/frontend/src/components/common/ProgressModal.tsx index 76012e8d..09645d14 100644 --- a/frontend/src/components/common/ProgressModal.tsx +++ b/frontend/src/components/common/ProgressModal.tsx @@ -667,7 +667,7 @@ export default function ProgressModal({ className="max-w-lg" showCloseButton={false} > -
{(() => { const funcName = (functionId || title || '').toLowerCase(); @@ -791,7 +791,29 @@ export default function ProgressModal({