Enhance API structure and documentation: Added new tags for Account, Integration, Automation, Linker, Optimizer, and Publisher; updated billing endpoints for admin and customer; improved API reference documentation; fixed endpoint paths in frontend services.
This commit is contained in:
@@ -10,6 +10,7 @@ from django.contrib.auth import get_user_model
|
|||||||
from django.db.models import Q, Count, Sum
|
from django.db.models import Q, Count, Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
|
||||||
from igny8_core.auth.models import Account
|
from igny8_core.auth.models import Account
|
||||||
from igny8_core.business.billing.models import CreditTransaction
|
from igny8_core.business.billing.models import CreditTransaction
|
||||||
@@ -17,6 +18,10 @@ from igny8_core.business.billing.models import CreditTransaction
|
|||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
retrieve=extend_schema(tags=['Account']),
|
||||||
|
partial_update=extend_schema(tags=['Account']),
|
||||||
|
)
|
||||||
class AccountSettingsViewSet(viewsets.ViewSet):
|
class AccountSettingsViewSet(viewsets.ViewSet):
|
||||||
"""Account settings management"""
|
"""Account settings management"""
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
@@ -77,6 +82,11 @@ class AccountSettingsViewSet(viewsets.ViewSet):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
list=extend_schema(tags=['Account']),
|
||||||
|
create=extend_schema(tags=['Account']),
|
||||||
|
destroy=extend_schema(tags=['Account']),
|
||||||
|
)
|
||||||
class TeamManagementViewSet(viewsets.ViewSet):
|
class TeamManagementViewSet(viewsets.ViewSet):
|
||||||
"""Team members management"""
|
"""Team members management"""
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
@@ -166,6 +176,9 @@ class TeamManagementViewSet(viewsets.ViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
overview=extend_schema(tags=['Account']),
|
||||||
|
)
|
||||||
class UsageAnalyticsViewSet(viewsets.ViewSet):
|
class UsageAnalyticsViewSet(viewsets.ViewSet):
|
||||||
"""Usage analytics and statistics"""
|
"""Usage analytics and statistics"""
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|||||||
@@ -8,7 +8,20 @@ from drf_spectacular.utils import extend_schema, OpenApiResponse
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
# Explicit tags we want to keep (from SPECTACULAR_SETTINGS)
|
# Explicit tags we want to keep (from SPECTACULAR_SETTINGS)
|
||||||
EXPLICIT_TAGS = {'Authentication', 'Planner', 'Writer', 'System', 'Billing'}
|
EXPLICIT_TAGS = {
|
||||||
|
'Authentication',
|
||||||
|
'Planner',
|
||||||
|
'Writer',
|
||||||
|
'System',
|
||||||
|
'Billing',
|
||||||
|
'Account',
|
||||||
|
'Automation',
|
||||||
|
'Linker',
|
||||||
|
'Optimizer',
|
||||||
|
'Publisher',
|
||||||
|
'Integration',
|
||||||
|
'Admin Billing',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def postprocess_schema_filter_tags(result, generator, request, public):
|
def postprocess_schema_filter_tags(result, generator, request, public):
|
||||||
@@ -41,6 +54,20 @@ def postprocess_schema_filter_tags(result, generator, request, public):
|
|||||||
filtered_tags = ['System']
|
filtered_tags = ['System']
|
||||||
elif '/billing/' in path or '/api/v1/billing/' in path:
|
elif '/billing/' in path or '/api/v1/billing/' in path:
|
||||||
filtered_tags = ['Billing']
|
filtered_tags = ['Billing']
|
||||||
|
elif '/account/' in path or '/api/v1/account/' in path:
|
||||||
|
filtered_tags = ['Account']
|
||||||
|
elif '/automation/' in path or '/api/v1/automation/' in path:
|
||||||
|
filtered_tags = ['Automation']
|
||||||
|
elif '/linker/' in path or '/api/v1/linker/' in path:
|
||||||
|
filtered_tags = ['Linker']
|
||||||
|
elif '/optimizer/' in path or '/api/v1/optimizer/' in path:
|
||||||
|
filtered_tags = ['Optimizer']
|
||||||
|
elif '/publisher/' in path or '/api/v1/publisher/' in path:
|
||||||
|
filtered_tags = ['Publisher']
|
||||||
|
elif '/integration/' in path or '/api/v1/integration/' in path:
|
||||||
|
filtered_tags = ['Integration']
|
||||||
|
elif '/admin/' in path or '/api/v1/admin/' in path:
|
||||||
|
filtered_tags = ['Admin Billing']
|
||||||
|
|
||||||
operation['tags'] = filtered_tags
|
operation['tags'] = filtered_tags
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
|
||||||
from igny8_core.business.automation.models import AutomationConfig, AutomationRun
|
from igny8_core.business.automation.models import AutomationConfig, AutomationRun
|
||||||
from igny8_core.business.automation.services import AutomationService
|
from igny8_core.business.automation.services import AutomationService
|
||||||
@@ -30,6 +31,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
site = get_object_or_404(Site, id=site_id, account=request.user.account)
|
site = get_object_or_404(Site, id=site_id, account=request.user.account)
|
||||||
return site, None
|
return site, None
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def config(self, request):
|
def config(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -68,6 +70,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
'next_run_at': config.next_run_at,
|
'next_run_at': config.next_run_at,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['put'])
|
@action(detail=False, methods=['put'])
|
||||||
def update_config(self, request):
|
def update_config(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -142,6 +145,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
'next_run_at': config.next_run_at,
|
'next_run_at': config.next_run_at,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'])
|
||||||
def run_now(self, request):
|
def run_now(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -175,6 +179,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def current_run(self, request):
|
def current_run(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -211,6 +216,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'])
|
||||||
def pause(self, request):
|
def pause(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -234,6 +240,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
status=status.HTTP_404_NOT_FOUND
|
status=status.HTTP_404_NOT_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'])
|
||||||
def resume(self, request):
|
def resume(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -262,6 +269,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
status=status.HTTP_404_NOT_FOUND
|
status=status.HTTP_404_NOT_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def history(self, request):
|
def history(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -291,6 +299,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def logs(self, request):
|
def logs(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -323,6 +332,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
status=status.HTTP_404_NOT_FOUND
|
status=status.HTTP_404_NOT_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def estimate(self, request):
|
def estimate(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -342,6 +352,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
'sufficient': site.account.credits >= (estimated_credits * 1.2)
|
'sufficient': site.account.credits >= (estimated_credits * 1.2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def pipeline_overview(self, request):
|
def pipeline_overview(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -504,6 +515,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['get'], url_path='current_processing')
|
@action(detail=False, methods=['get'], url_path='current_processing')
|
||||||
def current_processing(self, request):
|
def current_processing(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -547,6 +559,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['post'], url_path='pause')
|
@action(detail=False, methods=['post'], url_path='pause')
|
||||||
def pause_automation(self, request):
|
def pause_automation(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -596,6 +609,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['post'], url_path='resume')
|
@action(detail=False, methods=['post'], url_path='resume')
|
||||||
def resume_automation(self, request):
|
def resume_automation(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -649,6 +663,7 @@ class AutomationViewSet(viewsets.ViewSet):
|
|||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Automation'])
|
||||||
@action(detail=False, methods=['post'], url_path='cancel')
|
@action(detail=False, methods=['post'], url_path='cancel')
|
||||||
def cancel_automation(self, request):
|
def cancel_automation(self, request):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -11,14 +11,21 @@ from .views import (
|
|||||||
AdminBillingViewSet,
|
AdminBillingViewSet,
|
||||||
AccountPaymentMethodViewSet,
|
AccountPaymentMethodViewSet,
|
||||||
)
|
)
|
||||||
|
from igny8_core.modules.billing.views import (
|
||||||
|
CreditBalanceViewSet,
|
||||||
|
CreditUsageViewSet,
|
||||||
|
)
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'invoices', InvoiceViewSet, basename='invoice')
|
router.register(r'invoices', InvoiceViewSet, basename='invoice')
|
||||||
router.register(r'payments', PaymentViewSet, basename='payment')
|
router.register(r'payments', PaymentViewSet, basename='payment')
|
||||||
router.register(r'credit-packages', CreditPackageViewSet, basename='credit-package')
|
router.register(r'credit-packages', CreditPackageViewSet, basename='credit-package')
|
||||||
router.register(r'transactions', CreditTransactionViewSet, basename='transaction')
|
router.register(r'transactions', CreditTransactionViewSet, basename='transaction')
|
||||||
router.register(r'admin', AdminBillingViewSet, basename='admin-billing')
|
|
||||||
router.register(r'payment-methods', AccountPaymentMethodViewSet, basename='payment-method')
|
router.register(r'payment-methods', AccountPaymentMethodViewSet, basename='payment-method')
|
||||||
|
# Canonical credits endpoints (unified billing)
|
||||||
|
router.register(r'credits/balance', CreditBalanceViewSet, basename='credit-balance')
|
||||||
|
router.register(r'credits/usage', CreditUsageViewSet, basename='credit-usage')
|
||||||
|
router.register(r'credits/transactions', CreditTransactionViewSet, basename='credit-transactions')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Country/config-driven available methods (legacy alias)
|
# Country/config-driven available methods (legacy alias)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from rest_framework.permissions import IsAuthenticated
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Invoice,
|
Invoice,
|
||||||
@@ -366,6 +367,14 @@ class CreditTransactionViewSet(viewsets.ViewSet):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
invoices=extend_schema(tags=['Admin Billing']),
|
||||||
|
payments=extend_schema(tags=['Admin Billing']),
|
||||||
|
pending_payments=extend_schema(tags=['Admin Billing']),
|
||||||
|
approve_payment=extend_schema(tags=['Admin Billing']),
|
||||||
|
reject_payment=extend_schema(tags=['Admin Billing']),
|
||||||
|
stats=extend_schema(tags=['Admin Billing']),
|
||||||
|
)
|
||||||
class AdminBillingViewSet(viewsets.ViewSet):
|
class AdminBillingViewSet(viewsets.ViewSet):
|
||||||
"""Admin billing management"""
|
"""Admin billing management"""
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from .views import AdminBillingViewSet
|
from .views import AdminBillingViewSet
|
||||||
|
from igny8_core.business.billing.views import (
|
||||||
|
AdminBillingViewSet as BillingAdminViewSet,
|
||||||
|
)
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
|
|
||||||
@@ -9,6 +13,12 @@ urlpatterns = [
|
|||||||
path('users/', AdminBillingViewSet.as_view({'get': 'list_users'}), name='admin-users-list'),
|
path('users/', AdminBillingViewSet.as_view({'get': 'list_users'}), name='admin-users-list'),
|
||||||
path('users/<int:user_id>/adjust-credits/', AdminBillingViewSet.as_view({'post': 'adjust_credits'}), name='admin-adjust-credits'),
|
path('users/<int:user_id>/adjust-credits/', AdminBillingViewSet.as_view({'post': 'adjust_credits'}), name='admin-adjust-credits'),
|
||||||
path('credit-costs/', AdminBillingViewSet.as_view({'get': 'list_credit_costs', 'post': 'update_credit_costs'}), name='admin-credit-costs'),
|
path('credit-costs/', AdminBillingViewSet.as_view({'get': 'list_credit_costs', 'post': 'update_credit_costs'}), name='admin-credit-costs'),
|
||||||
|
# Unified admin billing endpoints (alias legacy /billing/admin/* under /admin/billing/*)
|
||||||
|
path('billing/invoices/', BillingAdminViewSet.as_view({'get': 'invoices'}), name='admin-billing-invoices'),
|
||||||
|
path('billing/payments/', BillingAdminViewSet.as_view({'get': 'payments'}), name='admin-billing-payments'),
|
||||||
|
path('billing/pending_payments/', BillingAdminViewSet.as_view({'get': 'pending_payments'}), name='admin-billing-pending-payments'),
|
||||||
|
path('billing/<int:pk>/approve_payment/', BillingAdminViewSet.as_view({'post': 'approve_payment'}), name='admin-billing-approve-payment'),
|
||||||
|
path('billing/<int:pk>/reject_payment/', BillingAdminViewSet.as_view({'post': 'reject_payment'}), name='admin-billing-reject-payment'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ urlpatterns = [
|
|||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
# User-facing billing overview
|
# User-facing billing overview
|
||||||
path('account_balance/', BillingOverviewViewSet.as_view({'get': 'account_balance'}), name='account-balance'),
|
path('account_balance/', BillingOverviewViewSet.as_view({'get': 'account_balance'}), name='account-balance'),
|
||||||
|
# Canonical credit balance endpoint
|
||||||
|
path('credits/balance/', CreditBalanceViewSet.as_view({'get': 'list'}), name='credit-balance-canonical'),
|
||||||
|
# Explicit list endpoints
|
||||||
path('transactions/', CreditTransactionViewSet.as_view({'get': 'list'}), name='transactions'),
|
path('transactions/', CreditTransactionViewSet.as_view({'get': 'list'}), name='transactions'),
|
||||||
path('usage/', CreditUsageViewSet.as_view({'get': 'list'}), name='usage'),
|
path('usage/', CreditUsageViewSet.as_view({'get': 'list'}), name='usage'),
|
||||||
# Admin billing endpoints
|
# Admin billing endpoints
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from .exceptions import InsufficientCreditsError
|
|||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(
|
||||||
list=extend_schema(tags=['Billing']),
|
list=extend_schema(tags=['Billing'], summary='Get credit balance'),
|
||||||
)
|
)
|
||||||
class CreditBalanceViewSet(viewsets.ViewSet):
|
class CreditBalanceViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
@@ -38,8 +38,7 @@ class CreditBalanceViewSet(viewsets.ViewSet):
|
|||||||
throttle_scope = 'billing'
|
throttle_scope = 'billing'
|
||||||
throttle_classes = [DebugScopedRateThrottle]
|
throttle_classes = [DebugScopedRateThrottle]
|
||||||
|
|
||||||
@action(detail=False, methods=['get'])
|
def list(self, request):
|
||||||
def balance(self, request):
|
|
||||||
"""Get current credit balance and usage"""
|
"""Get current credit balance and usage"""
|
||||||
account = getattr(request, 'account', None)
|
account = getattr(request, 'account', None)
|
||||||
if not account:
|
if not account:
|
||||||
@@ -125,6 +124,7 @@ class CreditUsageViewSet(AccountModelViewSet):
|
|||||||
|
|
||||||
return queryset.order_by('-created_at')
|
return queryset.order_by('-created_at')
|
||||||
|
|
||||||
|
@extend_schema(tags=['Billing'], summary='Get usage summary')
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def summary(self, request):
|
def summary(self, request):
|
||||||
"""Get usage summary for date range"""
|
"""Get usage summary for date range"""
|
||||||
@@ -214,6 +214,7 @@ class CreditUsageViewSet(AccountModelViewSet):
|
|||||||
serializer = UsageSummarySerializer(data)
|
serializer = UsageSummarySerializer(data)
|
||||||
return success_response(data=serializer.data, request=request)
|
return success_response(data=serializer.data, request=request)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Billing'], summary='Get usage limits')
|
||||||
@action(detail=False, methods=['get'], url_path='limits', url_name='limits')
|
@action(detail=False, methods=['get'], url_path='limits', url_name='limits')
|
||||||
def limits(self, request):
|
def limits(self, request):
|
||||||
"""
|
"""
|
||||||
@@ -434,6 +435,13 @@ class BillingOverviewViewSet(viewsets.ViewSet):
|
|||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
stats=extend_schema(tags=['Admin Billing'], summary='Admin billing stats'),
|
||||||
|
list_users=extend_schema(tags=['Admin Billing'], summary='List users with credit info'),
|
||||||
|
adjust_credits=extend_schema(tags=['Admin Billing'], summary='Adjust user credits'),
|
||||||
|
list_credit_costs=extend_schema(tags=['Admin Billing'], summary='List credit cost configurations'),
|
||||||
|
update_credit_costs=extend_schema(tags=['Admin Billing'], summary='Update credit cost configurations'),
|
||||||
|
)
|
||||||
class AdminBillingViewSet(viewsets.ViewSet):
|
class AdminBillingViewSet(viewsets.ViewSet):
|
||||||
"""Admin-only billing management API"""
|
"""Admin-only billing management API"""
|
||||||
permission_classes = [IsAuthenticatedAndActive, permissions.IsAdminUser]
|
permission_classes = [IsAuthenticatedAndActive, permissions.IsAdminUser]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from rest_framework import status
|
|||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
|
||||||
from igny8_core.api.base import SiteSectorModelViewSet
|
from igny8_core.api.base import SiteSectorModelViewSet
|
||||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
||||||
@@ -21,6 +22,14 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
list=extend_schema(tags=['Integration']),
|
||||||
|
create=extend_schema(tags=['Integration']),
|
||||||
|
retrieve=extend_schema(tags=['Integration']),
|
||||||
|
update=extend_schema(tags=['Integration']),
|
||||||
|
partial_update=extend_schema(tags=['Integration']),
|
||||||
|
destroy=extend_schema(tags=['Integration']),
|
||||||
|
)
|
||||||
class IntegrationViewSet(SiteSectorModelViewSet):
|
class IntegrationViewSet(SiteSectorModelViewSet):
|
||||||
"""
|
"""
|
||||||
ViewSet for SiteIntegration model.
|
ViewSet for SiteIntegration model.
|
||||||
@@ -88,6 +97,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
|||||||
|
|
||||||
return SiteIntegrationSerializer
|
return SiteIntegrationSerializer
|
||||||
|
|
||||||
|
@extend_schema(tags=['Integration'])
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def test_connection(self, request, pk=None):
|
def test_connection(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
@@ -118,6 +128,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
|||||||
def allow_request(self, request, view):
|
def allow_request(self, request, view):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@extend_schema(tags=['Integration'])
|
||||||
@action(detail=False, methods=['post'], url_path='test-connection',
|
@action(detail=False, methods=['post'], url_path='test-connection',
|
||||||
permission_classes=[AllowAny], throttle_classes=[NoThrottle])
|
permission_classes=[AllowAny], throttle_classes=[NoThrottle])
|
||||||
def test_connection_collection(self, request):
|
def test_connection_collection(self, request):
|
||||||
@@ -221,6 +232,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
|||||||
logger.info(f"[IntegrationViewSet] Deleted integration {integration.id} due to failed connection test")
|
logger.info(f"[IntegrationViewSet] Deleted integration {integration.id} due to failed connection test")
|
||||||
return error_response(result.get('message', 'Connection test failed'), None, status.HTTP_400_BAD_REQUEST, request)
|
return error_response(result.get('message', 'Connection test failed'), None, status.HTTP_400_BAD_REQUEST, request)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Integration'])
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def sync(self, request, pk=None):
|
def sync(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
@@ -252,6 +264,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
|||||||
response_status = status.HTTP_200_OK if result.get('success') else status.HTTP_400_BAD_REQUEST
|
response_status = status.HTTP_200_OK if result.get('success') else status.HTTP_400_BAD_REQUEST
|
||||||
return success_response(result, request=request, status_code=response_status)
|
return success_response(result, request=request, status_code=response_status)
|
||||||
|
|
||||||
|
@extend_schema(tags=['Integration'])
|
||||||
@action(detail=True, methods=['get'])
|
@action(detail=True, methods=['get'])
|
||||||
def sync_status(self, request, pk=None):
|
def sync_status(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
|
||||||
from igny8_core.api.base import SiteSectorModelViewSet
|
from igny8_core.api.base import SiteSectorModelViewSet
|
||||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
||||||
@@ -20,6 +21,14 @@ from igny8_core.business.publishing.models import PublishingRecord, DeploymentRe
|
|||||||
from igny8_core.business.publishing.services.publisher_service import PublisherService
|
from igny8_core.business.publishing.services.publisher_service import PublisherService
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
list=extend_schema(tags=['Publisher']),
|
||||||
|
create=extend_schema(tags=['Publisher']),
|
||||||
|
retrieve=extend_schema(tags=['Publisher']),
|
||||||
|
update=extend_schema(tags=['Publisher']),
|
||||||
|
partial_update=extend_schema(tags=['Publisher']),
|
||||||
|
destroy=extend_schema(tags=['Publisher']),
|
||||||
|
)
|
||||||
class PublishingRecordViewSet(SiteSectorModelViewSet):
|
class PublishingRecordViewSet(SiteSectorModelViewSet):
|
||||||
"""
|
"""
|
||||||
ViewSet for PublishingRecord model.
|
ViewSet for PublishingRecord model.
|
||||||
@@ -41,6 +50,14 @@ class PublishingRecordViewSet(SiteSectorModelViewSet):
|
|||||||
return PublishingRecordSerializer
|
return PublishingRecordSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
list=extend_schema(tags=['Publisher']),
|
||||||
|
create=extend_schema(tags=['Publisher']),
|
||||||
|
retrieve=extend_schema(tags=['Publisher']),
|
||||||
|
update=extend_schema(tags=['Publisher']),
|
||||||
|
partial_update=extend_schema(tags=['Publisher']),
|
||||||
|
destroy=extend_schema(tags=['Publisher']),
|
||||||
|
)
|
||||||
class DeploymentRecordViewSet(SiteSectorModelViewSet):
|
class DeploymentRecordViewSet(SiteSectorModelViewSet):
|
||||||
"""
|
"""
|
||||||
ViewSet for DeploymentRecord model.
|
ViewSet for DeploymentRecord model.
|
||||||
@@ -63,6 +80,7 @@ class DeploymentRecordViewSet(SiteSectorModelViewSet):
|
|||||||
return DeploymentRecordSerializer
|
return DeploymentRecordSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view()
|
||||||
class PublisherViewSet(viewsets.ViewSet):
|
class PublisherViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
Publisher actions for publishing content.
|
Publisher actions for publishing content.
|
||||||
@@ -76,6 +94,7 @@ class PublisherViewSet(viewsets.ViewSet):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.publisher_service = PublisherService()
|
self.publisher_service = PublisherService()
|
||||||
|
|
||||||
|
@extend_schema(tags=['Publisher'])
|
||||||
@action(detail=False, methods=['post'], url_path='publish')
|
@action(detail=False, methods=['post'], url_path='publish')
|
||||||
def publish(self, request):
|
def publish(self, request):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1580,12 +1580,12 @@ class ContentViewSet(SiteSectorModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(
|
||||||
list=extend_schema(tags=['Writer - Taxonomies']),
|
list=extend_schema(tags=['Writer']),
|
||||||
create=extend_schema(tags=['Writer - Taxonomies']),
|
create=extend_schema(tags=['Writer']),
|
||||||
retrieve=extend_schema(tags=['Writer - Taxonomies']),
|
retrieve=extend_schema(tags=['Writer']),
|
||||||
update=extend_schema(tags=['Writer - Taxonomies']),
|
update=extend_schema(tags=['Writer']),
|
||||||
partial_update=extend_schema(tags=['Writer - Taxonomies']),
|
partial_update=extend_schema(tags=['Writer']),
|
||||||
destroy=extend_schema(tags=['Writer - Taxonomies']),
|
destroy=extend_schema(tags=['Writer']),
|
||||||
)
|
)
|
||||||
class ContentTaxonomyViewSet(SiteSectorModelViewSet):
|
class ContentTaxonomyViewSet(SiteSectorModelViewSet):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -352,12 +352,32 @@ SPECTACULAR_SETTINGS = {
|
|||||||
# Tag configuration - prevent auto-generation and use explicit tags
|
# Tag configuration - prevent auto-generation and use explicit tags
|
||||||
'TAGS': [
|
'TAGS': [
|
||||||
{'name': 'Authentication', 'description': 'User authentication and registration'},
|
{'name': 'Authentication', 'description': 'User authentication and registration'},
|
||||||
|
{'name': 'Account', 'description': 'Account settings, team, and usage analytics'},
|
||||||
|
{'name': 'Integration', 'description': 'Site integrations and sync'},
|
||||||
|
{'name': 'System', 'description': 'Settings, prompts, and integrations'},
|
||||||
|
{'name': 'Admin Billing', 'description': 'Admin-only billing management'},
|
||||||
|
{'name': 'Billing', 'description': 'Credits, usage, and transactions'},
|
||||||
{'name': 'Planner', 'description': 'Keywords, clusters, and content ideas'},
|
{'name': 'Planner', 'description': 'Keywords, clusters, and content ideas'},
|
||||||
{'name': 'Writer', 'description': 'Tasks, content, and images'},
|
{'name': 'Writer', 'description': 'Tasks, content, and images'},
|
||||||
{'name': 'System', 'description': 'Settings, prompts, and integrations'},
|
{'name': 'Automation', 'description': 'Automation configuration and runs'},
|
||||||
{'name': 'Billing', 'description': 'Credits, usage, and transactions'},
|
{'name': 'Linker', 'description': 'Internal linking operations'},
|
||||||
|
{'name': 'Optimizer', 'description': 'Content optimization operations'},
|
||||||
|
{'name': 'Publisher', 'description': 'Publishing records and deployments'},
|
||||||
|
],
|
||||||
|
'TAGS_ORDER': [
|
||||||
|
'Authentication',
|
||||||
|
'Account',
|
||||||
|
'Integration',
|
||||||
|
'System',
|
||||||
|
'Admin Billing',
|
||||||
|
'Billing',
|
||||||
|
'Planner',
|
||||||
|
'Writer',
|
||||||
|
'Automation',
|
||||||
|
'Linker',
|
||||||
|
'Optimizer',
|
||||||
|
'Publisher',
|
||||||
],
|
],
|
||||||
'TAGS_ORDER': ['Authentication', 'Planner', 'Writer', 'System', 'Billing'],
|
|
||||||
# Postprocessing hook to filter out auto-generated tags
|
# Postprocessing hook to filter out auto-generated tags
|
||||||
'POSTPROCESSING_HOOKS': ['igny8_core.api.schema_extensions.postprocess_schema_filter_tags'],
|
'POSTPROCESSING_HOOKS': ['igny8_core.api.schema_extensions.postprocess_schema_filter_tags'],
|
||||||
|
|
||||||
|
|||||||
@@ -95,12 +95,35 @@ Development: http://localhost:8000/api/v1/
|
|||||||
```
|
```
|
||||||
/api/v1/
|
/api/v1/
|
||||||
├── auth/ # Authentication and user management
|
├── auth/ # Authentication and user management
|
||||||
|
├── account/ # Account settings, team, and usage analytics
|
||||||
|
├── integration/ # Site integrations and sync
|
||||||
|
├── system/ # Settings, prompts, integrations
|
||||||
|
├── admin/billing/ # Admin-only billing management
|
||||||
|
├── billing/ # Credits, transactions, usage
|
||||||
├── planner/ # Keywords, clusters, content ideas
|
├── planner/ # Keywords, clusters, content ideas
|
||||||
├── writer/ # Tasks, content, images
|
├── writer/ # Tasks, content, images
|
||||||
├── system/ # Settings, prompts, integrations
|
├── automation/ # Automation configuration and runs
|
||||||
└── billing/ # Credits, transactions, usage
|
├── linker/ # Internal linking operations
|
||||||
|
├── optimizer/ # Content optimization operations
|
||||||
|
└── publisher/ # Publishing records and deployments
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Module → Tag Map (Swagger/ReDoc)
|
||||||
|
- Authentication → `Authentication`
|
||||||
|
- Account → `Account`
|
||||||
|
- Integration → `Integration`
|
||||||
|
- System → `System`
|
||||||
|
- Admin Billing → `Admin Billing`
|
||||||
|
- Billing → `Billing`
|
||||||
|
- Planner → `Planner`
|
||||||
|
- Writer → `Writer`
|
||||||
|
- Automation → `Automation`
|
||||||
|
- Linker → `Linker`
|
||||||
|
- Optimizer → `Optimizer`
|
||||||
|
- Publisher → `Publisher`
|
||||||
|
|
||||||
|
Tag display order (docs): Authentication, Account, Integration, System, Admin Billing, Billing, Planner, Writer, Automation, Linker, Optimizer, Publisher
|
||||||
|
|
||||||
### Technology Stack
|
### Technology Stack
|
||||||
|
|
||||||
- **Framework**: Django REST Framework (DRF)
|
- **Framework**: Django REST Framework (DRF)
|
||||||
@@ -985,6 +1008,30 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
|||||||
- `GET /api/v1/system/ping/` - Health check endpoint (AllowAny)
|
- `GET /api/v1/system/ping/` - Health check endpoint (AllowAny)
|
||||||
- `GET /api/v1/system/request-metrics/{request_id}/` - Get request metrics for debugging
|
- `GET /api/v1/system/request-metrics/{request_id}/` - Get request metrics for debugging
|
||||||
|
|
||||||
|
### Admin Billing & Credits (Admin-only, Unified)
|
||||||
|
|
||||||
|
**Base Path**: `/api/v1/admin/billing/` (all admin billing/credits live here)
|
||||||
|
|
||||||
|
- `GET /api/v1/admin/billing/stats/` - System billing stats (admin-only)
|
||||||
|
- `GET /api/v1/admin/billing/invoices/` - Admin invoice listing (all accounts)
|
||||||
|
- `GET /api/v1/admin/billing/payments/` - Admin payment listing (all accounts)
|
||||||
|
- `GET /api/v1/admin/billing/pending_payments/` - Pending manual payments (admin review queue)
|
||||||
|
- `POST /api/v1/admin/billing/{id}/approve_payment/` - Approve manual payment (admin-only)
|
||||||
|
- `POST /api/v1/admin/billing/{id}/reject_payment/` - Reject manual payment (admin-only)
|
||||||
|
- `GET /api/v1/admin/credit-costs/` - List credit cost configurations (admin-only)
|
||||||
|
- `POST /api/v1/admin/credit-costs/` - Update credit cost configurations (admin-only)
|
||||||
|
- `GET /api/v1/admin/users/` - List users/accounts with credit info (admin-only)
|
||||||
|
- `POST /api/v1/admin/users/{user_id}/adjust-credits/` - Adjust user credits (admin-only)
|
||||||
|
|
||||||
|
> Non-standard/legacy endpoints to deprecate and remove (do not use):
|
||||||
|
> - `/api/v1/billing/admin/stats/`
|
||||||
|
> - `/api/v1/billing/admin/invoices/`
|
||||||
|
> - `/api/v1/billing/admin/payments/`
|
||||||
|
> - `/api/v1/billing/admin/pending_payments/`
|
||||||
|
> - `/api/v1/billing/admin/{id}/approve_payment/`
|
||||||
|
> - `/api/v1/billing/admin/{id}/reject_payment/`
|
||||||
|
|
||||||
|
|
||||||
### Billing Module Endpoints
|
### Billing Module Endpoints
|
||||||
|
|
||||||
**Base Path**: `/api/v1/billing/`
|
**Base Path**: `/api/v1/billing/`
|
||||||
@@ -994,7 +1041,7 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
|||||||
**Base Path**: `/api/v1/billing/credits/balance/`
|
**Base Path**: `/api/v1/billing/credits/balance/`
|
||||||
**Permission**: IsAuthenticatedAndActive + HasTenantAccess
|
**Permission**: IsAuthenticatedAndActive + HasTenantAccess
|
||||||
|
|
||||||
- `GET /api/v1/billing/credits/balance/balance/` - Get credit balance
|
- `GET /api/v1/billing/credits/balance/` - Get credit balance
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
```json
|
```json
|
||||||
@@ -1041,6 +1088,52 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
|||||||
- `start_date` - Filter by start date
|
- `start_date` - Filter by start date
|
||||||
- `end_date` - Filter by end date
|
- `end_date` - Filter by end date
|
||||||
|
|
||||||
|
#### Credit Packages
|
||||||
|
|
||||||
|
**Base Path**: `/api/v1/billing/credit-packages/`
|
||||||
|
**Permission**: IsAuthenticated
|
||||||
|
|
||||||
|
- `GET /api/v1/billing/credit-packages/` - List available credit packages
|
||||||
|
- `POST /api/v1/billing/credit-packages/{id}/purchase/` - Purchase a credit package
|
||||||
|
|
||||||
|
#### Invoices
|
||||||
|
|
||||||
|
**Base Path**: `/api/v1/billing/invoices/`
|
||||||
|
**Permission**: IsAuthenticated
|
||||||
|
|
||||||
|
- `GET /api/v1/billing/invoices/` - List invoices
|
||||||
|
- `GET /api/v1/billing/invoices/{id}/` - Get invoice detail
|
||||||
|
- `GET /api/v1/billing/invoices/{id}/download_pdf/` - Download invoice PDF
|
||||||
|
|
||||||
|
#### Payment Methods
|
||||||
|
|
||||||
|
**Base Path**: `/api/v1/billing/payment-methods/`
|
||||||
|
**Permission**: IsAuthenticated
|
||||||
|
|
||||||
|
- `GET /api/v1/billing/payment-methods/` - List payment methods
|
||||||
|
- `POST /api/v1/billing/payment-methods/` - Create payment method
|
||||||
|
- `GET /api/v1/billing/payment-methods/{id}/` - Get payment method
|
||||||
|
- `PUT /api/v1/billing/payment-methods/{id}/` - Update payment method
|
||||||
|
- `PATCH /api/v1/billing/payment-methods/{id}/` - Partial update payment method
|
||||||
|
- `DELETE /api/v1/billing/payment-methods/{id}/` - Delete payment method
|
||||||
|
- `POST /api/v1/billing/payment-methods/{id}/set_default/` - Set default payment method
|
||||||
|
- `GET /api/v1/billing/payment-methods/available/` - List available payment methods (config-driven)
|
||||||
|
|
||||||
|
#### Payments
|
||||||
|
|
||||||
|
**Base Path**: `/api/v1/billing/payments/`
|
||||||
|
**Permission**: IsAuthenticated
|
||||||
|
|
||||||
|
- `GET /api/v1/billing/payments/` - List payments
|
||||||
|
- `POST /api/v1/billing/payments/manual/` - Submit manual payment for approval
|
||||||
|
|
||||||
|
#### Transactions (alias-free)
|
||||||
|
|
||||||
|
**Base Path**: `/api/v1/billing/transactions/`
|
||||||
|
**Permission**: IsAuthenticated
|
||||||
|
|
||||||
|
- `GET /api/v1/billing/transactions/` - List transactions (with current balance included)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Integration Examples
|
## Integration Examples
|
||||||
@@ -1348,6 +1441,28 @@ All API changes are documented in `CHANGELOG.md` with:
|
|||||||
- Affected areas
|
- Affected areas
|
||||||
- Migration notes (if applicable)
|
- Migration notes (if applicable)
|
||||||
|
|
||||||
|
### Endpoint Change & Documentation Checklist (Unified API)
|
||||||
|
|
||||||
|
1) Design
|
||||||
|
- Map to an existing module/tag; if new, add to Module Namespaces and Tag Map.
|
||||||
|
- Choose path under the correct base (`/api/v1/{module}/...`); avoid new sub-namespaces unless justified.
|
||||||
|
|
||||||
|
2) Implement
|
||||||
|
- Use unified response helpers and proper permissions/rate limits.
|
||||||
|
- Add `extend_schema` tags matching the module tag.
|
||||||
|
|
||||||
|
3) Schema & Docs
|
||||||
|
- Ensure swagger tag exists in `SPECTACULAR_SETTINGS` with the agreed order.
|
||||||
|
- Regenerate/reload the API (server restart) so `/api/schema/` reflects changes.
|
||||||
|
- Verify in Swagger UI (`/api/docs/`) and ReDoc (`/api/redoc/`) that the operation is under the right tag.
|
||||||
|
|
||||||
|
4) Reference Updates
|
||||||
|
- Update this reference file with the new endpoint(s) under the correct module section.
|
||||||
|
- Update `CHANGELOG.md` (type, summary, impacted clients).
|
||||||
|
|
||||||
|
5) Deprecation (if applicable)
|
||||||
|
- Mark legacy routes, add timeline, and keep compatibility shims only temporarily.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ const endpointGroups: EndpointGroup[] = [
|
|||||||
{ path: "/v1/system/settings/account/", method: "GET", description: "Account settings" },
|
{ path: "/v1/system/settings/account/", method: "GET", description: "Account settings" },
|
||||||
{ path: "/v1/billing/credits/balance/", method: "GET", description: "Credit balance" },
|
{ path: "/v1/billing/credits/balance/", method: "GET", description: "Credit balance" },
|
||||||
{ path: "/v1/billing/credits/usage/", method: "GET", description: "Usage logs" },
|
{ path: "/v1/billing/credits/usage/", method: "GET", description: "Usage logs" },
|
||||||
|
{ path: "/v1/billing/credits/transactions/", method: "GET", description: "Transactions" },
|
||||||
{ path: "/v1/billing/credits/usage/summary/", method: "GET", description: "Usage summary" },
|
{ path: "/v1/billing/credits/usage/summary/", method: "GET", description: "Usage summary" },
|
||||||
{
|
{
|
||||||
path: "/v1/billing/credits/usage/limits/",
|
path: "/v1/billing/credits/usage/limits/",
|
||||||
@@ -126,6 +127,36 @@ const endpointGroups: EndpointGroup[] = [
|
|||||||
{ path: "/v1/billing/credits/transactions/", method: "GET", description: "Transactions" },
|
{ path: "/v1/billing/credits/transactions/", method: "GET", description: "Transactions" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Billing (Customer)",
|
||||||
|
endpoints: [
|
||||||
|
{ path: "/v1/billing/credit-packages/", method: "GET", description: "List credit packages" },
|
||||||
|
{ path: "/v1/billing/invoices/", method: "GET", description: "List invoices" },
|
||||||
|
{ path: "/v1/billing/payments/", method: "GET", description: "List payments" },
|
||||||
|
{ path: "/v1/billing/payment-methods/", method: "GET", description: "List payment methods" },
|
||||||
|
{ path: "/v1/billing/payment-methods/available/", method: "GET", description: "Available payment methods" },
|
||||||
|
{ path: "/v1/billing/payments/manual/", method: "POST", description: "Submit manual payment" },
|
||||||
|
{ path: "/v1/billing/payments/available_methods/", method: "GET", description: "Payment methods (available_methods)" },
|
||||||
|
{ path: "/v1/billing/payment-methods/1/set_default/", method: "POST", description: "Set default payment method (sample id)" },
|
||||||
|
{ path: "/v1/billing/payment-methods/1/", method: "PATCH", description: "Update payment method (sample id)" },
|
||||||
|
{ path: "/v1/billing/payment-methods/1/", method: "DELETE", description: "Delete payment method (sample id)" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Admin Billing",
|
||||||
|
endpoints: [
|
||||||
|
{ path: "/v1/admin/billing/stats/", method: "GET", description: "Admin billing stats" },
|
||||||
|
{ path: "/v1/admin/billing/invoices/", method: "GET", description: "Admin invoices" },
|
||||||
|
{ path: "/v1/admin/billing/payments/", method: "GET", description: "Admin payments" },
|
||||||
|
{ path: "/v1/admin/billing/pending_payments/", method: "GET", description: "Pending manual payments" },
|
||||||
|
{ path: "/v1/admin/billing/1/approve_payment/", method: "POST", description: "Approve manual payment (sample id)" },
|
||||||
|
{ path: "/v1/admin/billing/1/reject_payment/", method: "POST", description: "Reject manual payment (sample id)" },
|
||||||
|
{ path: "/v1/admin/credit-costs/", method: "GET", description: "Credit cost configs" },
|
||||||
|
{ path: "/v1/admin/credit-costs/", method: "POST", description: "Update credit cost configs" },
|
||||||
|
{ path: "/v1/admin/users/", method: "GET", description: "Admin users with credits" },
|
||||||
|
{ path: "/v1/admin/users/1/adjust-credits/", method: "POST", description: "Adjust credits (sample id)" },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "CRUD Operations - Planner",
|
name: "CRUD Operations - Planner",
|
||||||
endpoints: [
|
endpoints: [
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export interface PendingPayment extends Payment {
|
|||||||
|
|
||||||
export async function getCreditBalance(): Promise<CreditBalance> {
|
export async function getCreditBalance(): Promise<CreditBalance> {
|
||||||
// Use business billing CreditTransactionViewSet.balance
|
// Use business billing CreditTransactionViewSet.balance
|
||||||
return fetchAPI('/v1/billing/transactions/balance/');
|
return fetchAPI('/v1/billing/credits/balance/');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCreditTransactions(): Promise<{
|
export async function getCreditTransactions(): Promise<{
|
||||||
@@ -268,7 +268,7 @@ export async function getCreditUsageLimits(): Promise<{
|
|||||||
export async function getAdminBillingStats(): Promise<AdminBillingStats> {
|
export async function getAdminBillingStats(): Promise<AdminBillingStats> {
|
||||||
// Admin billing dashboard metrics
|
// Admin billing dashboard metrics
|
||||||
// Use business billing stats endpoint to include revenue, accounts, credits, and recent payments
|
// Use business billing stats endpoint to include revenue, accounts, credits, and recent payments
|
||||||
return fetchAPI('/v1/billing/admin/stats/');
|
return fetchAPI('/v1/admin/billing/stats/');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAdminInvoices(params?: { status?: string; account_id?: number; search?: string }): Promise<{
|
export async function getAdminInvoices(params?: { status?: string; account_id?: number; search?: string }): Promise<{
|
||||||
@@ -280,7 +280,7 @@ export async function getAdminInvoices(params?: { status?: string; account_id?:
|
|||||||
if (params?.account_id) queryParams.append('account_id', String(params.account_id));
|
if (params?.account_id) queryParams.append('account_id', String(params.account_id));
|
||||||
if (params?.search) queryParams.append('search', params.search);
|
if (params?.search) queryParams.append('search', params.search);
|
||||||
|
|
||||||
const url = `/v1/billing/admin/invoices/${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
|
const url = `/v1/admin/billing/invoices/${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
|
||||||
return fetchAPI(url);
|
return fetchAPI(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ export async function getAdminPayments(params?: { status?: string; account_id?:
|
|||||||
if (params?.account_id) queryParams.append('account_id', String(params.account_id));
|
if (params?.account_id) queryParams.append('account_id', String(params.account_id));
|
||||||
if (params?.payment_method) queryParams.append('payment_method', params.payment_method);
|
if (params?.payment_method) queryParams.append('payment_method', params.payment_method);
|
||||||
|
|
||||||
const url = `/v1/billing/admin/payments/${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
|
const url = `/v1/admin/billing/payments/${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
|
||||||
return fetchAPI(url);
|
return fetchAPI(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +624,7 @@ export async function getPendingPayments(): Promise<{
|
|||||||
results: PendingPayment[];
|
results: PendingPayment[];
|
||||||
count: number;
|
count: number;
|
||||||
}> {
|
}> {
|
||||||
return fetchAPI('/v1/billing/admin/pending_payments/');
|
return fetchAPI('/v1/admin/billing/pending_payments/');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function approvePayment(paymentId: number, data?: {
|
export async function approvePayment(paymentId: number, data?: {
|
||||||
@@ -633,7 +633,7 @@ export async function approvePayment(paymentId: number, data?: {
|
|||||||
message: string;
|
message: string;
|
||||||
payment: Payment;
|
payment: Payment;
|
||||||
}> {
|
}> {
|
||||||
return fetchAPI(`/v1/billing/admin/${paymentId}/approve_payment/`, {
|
return fetchAPI(`/v1/admin/billing/${paymentId}/approve_payment/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data || {}),
|
body: JSON.stringify(data || {}),
|
||||||
});
|
});
|
||||||
@@ -646,7 +646,7 @@ export async function rejectPayment(paymentId: number, data: {
|
|||||||
message: string;
|
message: string;
|
||||||
payment: Payment;
|
payment: Payment;
|
||||||
}> {
|
}> {
|
||||||
return fetchAPI(`/v1/billing/admin/${paymentId}/reject_payment/`, {
|
return fetchAPI(`/v1/admin/billing/${paymentId}/reject_payment/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user