Files
igny8/v2/V2-Execution-Docs/02C-gsc-integration.md
IGNY8 VPS (Salman) 0570052fec 1
2026-03-23 17:20:51 +00:00

29 KiB

IGNY8 Phase 2: GSC Integration (02C)

Google Search Console — Indexing, Inspection & Analytics

Document Version: 1.0 Date: 2026-03-23 Phase: IGNY8 Phase 2 — Feature Expansion Status: Build Ready Source of Truth: Codebase at /data/app/igny8/ Audience: Claude Code, Backend Developers, Architects


1. CURRENT STATE

Existing Integration Infrastructure

  • SiteIntegration model (db_table=igny8_site_integrations) stores WordPress connections with platform='wordpress'
  • SyncEvent model (db_table=igny8_sync_events) logs publish/sync operations
  • Integration app registered at /api/v1/integration/
  • No Google API connections of any kind exist
  • No OAuth 2.0 infrastructure for third-party APIs
  • IntegrationProvider model supports provider_type: ai/payment/email/storage — no search_engine type yet

Content Publishing Flow

  • When Content.site_status changes to published, a PublishingRecord is created
  • Content gets external_url and external_id after WordPress publish
  • No automatic indexing request after publish
  • No tracking of whether published URLs are indexed by Google

What Doesn't Exist

  • Google Search Console OAuth connection
  • URL Inspection API integration
  • Indexing queue with priority and quota management
  • Search analytics data collection/dashboard
  • Re-inspection scheduling
  • Plugin-side index status display
  • Any GSC-related models, endpoints, or tasks

2. WHAT TO BUILD

Overview

Full Google Search Console integration with four capabilities:

  1. OAuth Connection — connect GSC property via Google OAuth 2.0
  2. URL Inspection — inspect URLs via Google's URL Inspection API (2K/day quota), auto-inspect after publish
  3. Indexing Queue — priority-based queue with quota management and re-inspection scheduling
  4. Search Analytics — fetch and cache search performance data (clicks, impressions, CTR, position)

OAuth 2.0 Connection Flow

User clicks "Connect GSC" →
  IGNY8 backend generates Google OAuth URL →
    User authorizes in Google consent screen →
      Google redirects to /api/v1/integration/gsc/callback/ →
        Backend stores encrypted access_token + refresh_token →
          Backend fetches user's GSC properties →
            User selects property → GSCConnection created

Google Cloud project requirements:

  • Search Console API enabled
  • URL Inspection API enabled (separate from Search Console API)
  • OAuth 2.0 client ID (Web application type)
  • Scopes: https://www.googleapis.com/auth/webmasters.readonly, https://www.googleapis.com/auth/indexing
  • Redirect URI: https://{domain}/api/v1/integration/gsc/callback/

Token management:

  • Access tokens expire after 1 hour → background refresh via Celery task
  • Refresh tokens stored encrypted in GSCConnection.refresh_token
  • If refresh fails → set GSCConnection.status='expired', notify user

URL Inspection API

Google API endpoint:

POST https://searchconsole.googleapis.com/v1/urlInspection/index:inspect
Body: {"inspectionUrl": "https://example.com/page", "siteUrl": "sc-domain:example.com"}

Response fields tracked:

Field Type Stored In
verdict PASS/PARTIAL/FAIL/NEUTRAL URLInspectionRecord.verdict
coverageState e.g., "Submitted and indexed" URLInspectionRecord.coverage_state
indexingState e.g., "INDEXING_ALLOWED" URLInspectionRecord.indexing_state
robotsTxtState e.g., "ALLOWED" URLInspectionRecord.last_inspection_result (JSON)
lastCrawlTime ISO datetime URLInspectionRecord.last_crawled
Full response JSON URLInspectionRecord.last_inspection_result

Quota: 2,000 inspections per day per GSC property (resets midnight Pacific Time) Rate limit: 1 request per 3 seconds (safe limit: 600 per 30 minutes)

Indexing Queue System

Priority-based queue that respects daily quota:

Priority Trigger Description
100 Content published (auto) Newly published content auto-queued
90 Re-inspection (auto) Scheduled follow-up check
70 Manual inspect request User requests specific URL inspection
50 Information query Check status only, no submit
30 Scheduled bulk re-inspect Periodic re-check of all URLs

Queue processing:

  • Celery task runs every 5 minutes
  • Checks GSCDailyQuota for remaining capacity
  • Processes items in priority order (highest first)
  • Respects 1 request/3 second rate limit
  • Status flow: queued → processing → completed/failed/quota_exceeded

Re-Inspection Schedule

After initial inspection, automatically schedule follow-up checks:

Check Timing Purpose
Check 1 24 hours after submission Quick verification
Check 2 3 days after Give Google time to crawl
Check 3 6 days after Most URLs indexed by now
Check 4 13 days after Final automatic check

If still not indexed after Check 4 → mark status='manual_review', stop auto-checking.

Search Analytics

Google API endpoint:

POST https://searchconsole.googleapis.com/v3/sites/{siteUrl}/searchAnalytics/query
Body: {
    "startDate": "2025-01-01",
    "endDate": "2025-03-23",
    "dimensions": ["page", "query", "date"],
    "rowLimit": 25000
}

Metrics collected: clicks, impressions, ctr, position Dimensions: page, query (keyword), country, device, date Date range: up to 16 months historical Caching: Results cached in GSCMetricsCache with 24-hour TTL, refreshed daily via Celery

Auto-Indexing After Publish

When Content.site_status changes to 'published' and the content has an external_url:

  1. Check if GSCConnection exists for the site with status='active'
  2. Create or update URLInspectionRecord for the URL
  3. Add to IndexingQueue with priority=100
  4. If SAG data available: hub pages get inspected before supporting articles (blueprint-aware priority)

Plugin-Side Status Sync

IGNY8 pushes index statuses to the WordPress plugin:

  • Endpoint: POST /wp-json/igny8/v1/gsc/status-sync
  • Payload: {urls: [{url, status, verdict, last_inspected}]}
  • Plugin displays status badges on WP post list table:
    • pending_inspection
    • indexed
    • not_indexed
    • indexing_requested
    • 🚫 error_noindex

3. DATA MODELS & APIs

New Models (integration app)

class GSCConnection(AccountBaseModel):
    """Google Search Console OAuth connection per site."""
    site = models.ForeignKey(
        'igny8_core_auth.Site', on_delete=models.CASCADE,
        related_name='gsc_connections'
    )
    google_email = models.CharField(max_length=255)
    access_token = models.TextField(help_text="Encrypted OAuth access token")
    refresh_token = models.TextField(help_text="Encrypted OAuth refresh token")
    token_expiry = models.DateTimeField()
    gsc_property_url = models.CharField(
        max_length=500,
        help_text="GSC property URL, e.g., sc-domain:example.com"
    )
    status = models.CharField(
        max_length=20, default='active',
        choices=[
            ('active', 'Active'),
            ('expired', 'Token Expired'),
            ('revoked', 'Access Revoked'),
        ],
        db_index=True
    )

    class Meta:
        app_label = 'integration'
        db_table = 'igny8_gsc_connections'
        unique_together = [['site', 'gsc_property_url']]


class URLInspectionRecord(SiteSectorBaseModel):
    """Tracks URL inspection history and indexing status."""
    url = models.URLField(max_length=2000, db_index=True)
    content = models.ForeignKey(
        'writer.Content', on_delete=models.SET_NULL,
        null=True, blank=True, related_name='inspection_records',
        help_text="Linked IGNY8 content (null for external/non-IGNY8 URLs)"
    )
    last_inspection_result = models.JSONField(
        default=dict, help_text="Full Google API response"
    )
    verdict = models.CharField(
        max_length=20, blank=True, default='',
        help_text="PASS/PARTIAL/FAIL/NEUTRAL"
    )
    coverage_state = models.CharField(max_length=100, blank=True, default='')
    indexing_state = models.CharField(max_length=100, blank=True, default='')
    last_crawled = models.DateTimeField(null=True, blank=True)
    last_inspected = models.DateTimeField(null=True, blank=True)
    inspection_count = models.IntegerField(default=0)
    next_inspection = models.DateTimeField(
        null=True, blank=True,
        help_text="Scheduled next re-inspection datetime"
    )
    status = models.CharField(
        max_length=30, default='pending_inspection',
        choices=[
            ('pending_inspection', 'Pending Inspection'),
            ('indexed', 'Indexed'),
            ('not_indexed', 'Not Indexed'),
            ('indexing_requested', 'Indexing Requested'),
            ('error_noindex', 'Error / No Index'),
            ('manual_review', 'Manual Review Needed'),
        ],
        db_index=True
    )

    class Meta:
        app_label = 'integration'
        db_table = 'igny8_url_inspection_records'
        unique_together = [['site', 'url']]
        ordering = ['-last_inspected']


class IndexingQueue(SiteSectorBaseModel):
    """Priority queue for URL inspection API requests."""
    url = models.URLField(max_length=2000)
    url_inspection_record = models.ForeignKey(
        URLInspectionRecord, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='queue_entries'
    )
    priority = models.IntegerField(
        default=50, db_index=True,
        help_text="100=auto-publish, 90=re-inspect, 70=manual, 50=info, 30=bulk"
    )
    status = models.CharField(
        max_length=20, default='queued',
        choices=[
            ('queued', 'Queued'),
            ('processing', 'Processing'),
            ('completed', 'Completed'),
            ('failed', 'Failed'),
            ('quota_exceeded', 'Quota Exceeded'),
        ],
        db_index=True
    )
    date_added = models.DateTimeField(auto_now_add=True)
    date_processed = models.DateTimeField(null=True, blank=True)
    error_message = models.TextField(blank=True, default='')

    class Meta:
        app_label = 'integration'
        db_table = 'igny8_indexing_queue'
        ordering = ['-priority', 'date_added']


class GSCMetricsCache(SiteSectorBaseModel):
    """Cached search analytics data from GSC API."""
    metric_type = models.CharField(
        max_length=50, db_index=True,
        choices=[
            ('search_analytics', 'Search Analytics'),
            ('page_performance', 'Page Performance'),
            ('keyword_performance', 'Keyword Performance'),
        ]
    )
    dimension_filters = models.JSONField(
        default=dict,
        help_text="Filters used for this query: {dimensions, filters}"
    )
    data = models.JSONField(
        default=list,
        help_text="Cached query results"
    )
    date_range_start = models.DateField()
    date_range_end = models.DateField()
    expires_at = models.DateTimeField(
        help_text="Cache expiry — refresh after this time"
    )

    class Meta:
        app_label = 'integration'
        db_table = 'igny8_gsc_metrics_cache'
        ordering = ['-date_range_end']


class GSCDailyQuota(SiteSectorBaseModel):
    """Tracks daily URL Inspection API usage per site/property."""
    date = models.DateField(db_index=True)
    inspections_used = models.IntegerField(default=0)
    quota_limit = models.IntegerField(default=2000)

    class Meta:
        app_label = 'integration'
        db_table = 'igny8_gsc_daily_quota'
        unique_together = [['site', 'date']]

Migration

igny8_core/migrations/XXXX_gsc_integration.py

New tables:

  1. igny8_gsc_connections
  2. igny8_url_inspection_records
  3. igny8_indexing_queue
  4. igny8_gsc_metrics_cache
  5. igny8_gsc_daily_quota

API Endpoints

# OAuth Connection
POST   /api/v1/integration/gsc/connect/                             # Initiate OAuth (returns redirect URL)
GET    /api/v1/integration/gsc/callback/                             # OAuth callback (stores tokens)
DELETE /api/v1/integration/gsc/disconnect/                           # Revoke + delete connection
GET    /api/v1/integration/gsc/properties/                           # List connected GSC properties
GET    /api/v1/integration/gsc/status/                               # Connection status

# Quota
GET    /api/v1/integration/gsc/quota/                                # Today's quota usage (used/limit)

# URL Inspection
POST   /api/v1/integration/gsc/inspect/                              # Queue single URL for inspection
POST   /api/v1/integration/gsc/inspect/bulk/                         # Queue multiple URLs
GET    /api/v1/integration/gsc/inspections/                          # List inspection records (filterable)
GET    /api/v1/integration/gsc/inspections/{id}/                     # Single inspection detail

# Search Analytics
GET    /api/v1/integration/gsc/analytics/                            # Search analytics (cached)
GET    /api/v1/integration/gsc/analytics/keywords/                   # Keyword performance
GET    /api/v1/integration/gsc/analytics/pages/                      # Page performance
GET    /api/v1/integration/gsc/analytics/export/                     # CSV export

# Queue Management (admin)
GET    /api/v1/integration/gsc/queue/                                # View queue status
POST   /api/v1/integration/gsc/queue/clear/                          # Clear failed/quota_exceeded items

Services

# igny8_core/business/integration/gsc_service.py

class GSCService:
    """Google Search Console API client wrapper."""

    def get_oauth_url(self, site_id: int, redirect_uri: str) -> str:
        """Generate Google OAuth consent URL."""
        pass

    def handle_oauth_callback(self, code: str, site_id: int, account_id: int) -> GSCConnection:
        """Exchange auth code for tokens, create GSCConnection."""
        pass

    def refresh_access_token(self, connection: GSCConnection) -> bool:
        """Refresh expired access token using refresh_token."""
        pass

    def inspect_url(self, connection: GSCConnection, url: str) -> Dict:
        """Call URL Inspection API. Returns parsed response."""
        pass

    def fetch_search_analytics(
        self, connection: GSCConnection,
        start_date: str, end_date: str,
        dimensions: list, row_limit: int = 25000
    ) -> List[Dict]:
        """Fetch search analytics data from GSC API."""
        pass

    def list_properties(self, connection: GSCConnection) -> List[str]:
        """List all GSC properties accessible by the connected account."""
        pass


class IndexingQueueProcessor:
    """Processes the indexing queue respecting quota and rate limits."""

    RATE_LIMIT_SECONDS = 3  # 1 request per 3 seconds
    QUOTA_BUFFER = 50       # Reserve 50 inspections for manual use

    def process_queue(self, site_id: int):
        """Process queued items for a site, respecting daily quota."""
        quota = GSCDailyQuota.objects.get_or_create(
            site_id=site_id,
            date=timezone.now().date(),
            defaults={'quota_limit': 2000}
        )[0]

        remaining = quota.quota_limit - quota.inspections_used - self.QUOTA_BUFFER
        if remaining <= 0:
            return {'processed': 0, 'reason': 'quota_exceeded'}

        items = IndexingQueue.objects.filter(
            site_id=site_id,
            status='queued'
        ).order_by('-priority', 'date_added')[:remaining]

        processed = 0
        for item in items:
            item.status = 'processing'
            item.save()
            try:
                result = self.gsc_service.inspect_url(connection, item.url)
                self._update_inspection_record(item, result)
                item.status = 'completed'
                item.date_processed = timezone.now()
                quota.inspections_used += 1
                processed += 1
                time.sleep(self.RATE_LIMIT_SECONDS)
            except QuotaExceededException:
                item.status = 'quota_exceeded'
                break
            except Exception as e:
                item.status = 'failed'
                item.error_message = str(e)
            finally:
                item.save()
                quota.save()

        return {'processed': processed}

    def _update_inspection_record(self, queue_item, result):
        """Create/update URLInspectionRecord from API result."""
        record, created = URLInspectionRecord.objects.update_or_create(
            site=queue_item.site,
            url=queue_item.url,
            defaults={
                'last_inspection_result': result,
                'verdict': result.get('inspectionResult', {}).get('indexStatusResult', {}).get('verdict', ''),
                'coverage_state': result.get('inspectionResult', {}).get('indexStatusResult', {}).get('coverageState', ''),
                'indexing_state': result.get('inspectionResult', {}).get('indexStatusResult', {}).get('indexingState', ''),
                'last_inspected': timezone.now(),
                'inspection_count': models.F('inspection_count') + 1,
            }
        )
        # Schedule re-inspection
        self._schedule_next_inspection(record)

    def _schedule_next_inspection(self, record):
        """Schedule follow-up inspection based on inspection count."""
        delays = {1: 1, 2: 3, 3: 6, 4: 13}  # days after inspection
        if record.inspection_count in delays:
            record.next_inspection = timezone.now() + timedelta(days=delays[record.inspection_count])
            record.save()
        elif record.inspection_count > 4 and record.verdict != 'PASS':
            record.status = 'manual_review'
            record.next_inspection = None
            record.save()

Celery Tasks

# igny8_core/tasks/gsc_tasks.py

@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def process_indexing_queue(self, site_id: int = None):
    """Process pending indexing queue items. Runs every 5 minutes."""
    # If site_id provided, process that site only
    # Otherwise, process all sites with active GSCConnection
    pass

@shared_task(bind=True, max_retries=3, default_retry_delay=300)
def refresh_gsc_tokens(self):
    """Refresh expiring GSC OAuth tokens. Runs hourly."""
    expiring = GSCConnection.objects.filter(
        status='active',
        token_expiry__lte=timezone.now() + timedelta(minutes=10)
    )
    for conn in expiring:
        GSCService().refresh_access_token(conn)

@shared_task(bind=True, max_retries=3, default_retry_delay=300)
def fetch_search_analytics(self):
    """Fetch and cache search analytics for all connected sites. Runs daily."""
    pass

@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def schedule_reinspections(self):
    """Add due re-inspections to the queue. Runs daily."""
    due = URLInspectionRecord.objects.filter(
        next_inspection__lte=timezone.now(),
        status__in=['not_indexed', 'indexing_requested']
    )
    for record in due:
        IndexingQueue.objects.get_or_create(
            site=record.site,
            url=record.url,
            status='queued',
            defaults={'priority': 90, 'url_inspection_record': record}
        )

@shared_task(bind=True)
def auto_queue_published_content(self, content_id: int):
    """Queue newly published content for GSC inspection. Triggered by publish signal."""
    content = Content.objects.get(id=content_id)
    if not content.external_url:
        return
    connection = GSCConnection.objects.filter(
        site=content.site, status='active'
    ).first()
    if not connection:
        return
    record, _ = URLInspectionRecord.objects.get_or_create(
        site=content.site, url=content.external_url,
        defaults={'content': content, 'sector': content.sector, 'account': content.account}
    )
    IndexingQueue.objects.create(
        site=content.site, sector=content.sector, account=content.account,
        url=content.external_url, url_inspection_record=record,
        priority=100, status='queued'
    )

Beat schedule additions (add to igny8_core/celery.py):

'process-indexing-queue': {
    'task': 'gsc.process_indexing_queue',
    'schedule': crontab(minute='*/5'),  # Every 5 minutes
},
'refresh-gsc-tokens': {
    'task': 'gsc.refresh_gsc_tokens',
    'schedule': crontab(minute=30),  # Every hour at :30
},
'fetch-search-analytics': {
    'task': 'gsc.fetch_search_analytics',
    'schedule': crontab(hour=4, minute=0),  # Daily at 4 AM
},
'schedule-reinspections': {
    'task': 'gsc.schedule_reinspections',
    'schedule': crontab(hour=5, minute=0),  # Daily at 5 AM
},

Auto-Indexing Signal

Connect to Content model's post-publish flow:

# igny8_core/business/integration/signals.py

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender='writer.Content')
def queue_for_gsc_inspection(sender, instance, **kwargs):
    """When content is published, auto-queue for GSC inspection."""
    if instance.site_status == 'published' and instance.external_url:
        auto_queue_published_content.delay(instance.id)

Credit Costs

Operation Credits Notes
GSC OAuth connection setup 1 One-time per connection
URL inspections (per 100) 0.1 Batch pricing
Indexing request (per URL) 0.05 Minimal cost
Analytics caching (per site/month) 0.5 Monthly recurring

Add to CreditCostConfig:

CreditCostConfig.objects.get_or_create(
    operation_type='gsc_inspection',
    defaults={'display_name': 'GSC URL Inspection', 'base_credits': 1}
)
CreditCostConfig.objects.get_or_create(
    operation_type='gsc_analytics',
    defaults={'display_name': 'GSC Analytics Sync', 'base_credits': 1}
)

4. IMPLEMENTATION STEPS

Step 1: Create GSC Models

File to create/modify:

  • backend/igny8_core/business/integration/gsc_models.py (or add to existing models.py)
  • 5 new models: GSCConnection, URLInspectionRecord, IndexingQueue, GSCMetricsCache, GSCDailyQuota

Step 2: Create and Run Migration

cd /data/app/igny8/backend
python manage.py makemigrations --name gsc_integration
python manage.py migrate

Step 3: Build GSCService

File to create:

  • backend/igny8_core/business/integration/gsc_service.py
  • Requires google-auth, google-auth-oauthlib, google-api-python-client packages

Add to requirements.txt:

google-auth>=2.0.0
google-auth-oauthlib>=1.0.0
google-api-python-client>=2.0.0

Step 4: Build IndexingQueueProcessor

File to create:

  • backend/igny8_core/business/integration/indexing_queue_processor.py

Step 5: Build Celery Tasks

File to create:

  • backend/igny8_core/tasks/gsc_tasks.py

Add beat schedule entries to:

  • backend/igny8_core/celery.py

Step 6: Build Auto-Indexing Signal

File to create:

  • backend/igny8_core/business/integration/signals.py

Register in:

  • backend/igny8_core/business/integration/apps.pyready() method

Step 7: Build Serializers

File to create:

  • backend/igny8_core/modules/integration/serializers/gsc_serializers.py

Step 8: Build ViewSets and URLs

Files to create:

  • backend/igny8_core/modules/integration/views/gsc_views.py
  • Modify backend/igny8_core/modules/integration/urls.py — register GSC endpoints

Step 9: Add OAuth Settings

Add to backend/igny8_core/settings.py:

# Google OAuth 2.0 (GSC Integration)
GOOGLE_CLIENT_ID = env('GOOGLE_CLIENT_ID', default='')
GOOGLE_CLIENT_SECRET = env('GOOGLE_CLIENT_SECRET', default='')
GOOGLE_REDIRECT_URI = env('GOOGLE_REDIRECT_URI', default='')

Step 10: Frontend

Files to create in frontend/src/:

  • pages/Integration/GSCDashboard.tsx — main GSC dashboard
  • pages/Integration/GSCAnalytics.tsx — search analytics with charts
  • pages/Integration/GSCInspections.tsx — URL inspection list with status badges
  • pages/Integration/GSCConnect.tsx — OAuth connection flow
  • components/Integration/QuotaIndicator.tsx — daily quota usage bar
  • components/Integration/InspectionStatusBadge.tsx — status badges
  • stores/gscStore.ts — Zustand store
  • api/gsc.ts — API client

Step 11: Tests

cd /data/app/igny8/backend
python manage.py test igny8_core.business.integration.tests.test_gsc_service
python manage.py test igny8_core.business.integration.tests.test_indexing_queue
python manage.py test igny8_core.modules.integration.tests.test_gsc_views

5. ACCEPTANCE CRITERIA

  • 5 new database tables created and migrated successfully
  • Google OAuth 2.0 flow works: connect → consent → callback → tokens stored
  • GSC properties listed after successful OAuth connection
  • Token refresh works automatically before expiry via Celery task
  • URL Inspection API calls succeed and results stored in URLInspectionRecord
  • Daily quota tracked in GSCDailyQuota, respects 2,000/day limit
  • Rate limit of 1 request/3 seconds enforced in queue processor
  • Re-inspection schedule runs: 1 day, 3 days, 6 days, 13 days after initial check
  • URLs not indexed after Check 4 marked as 'manual_review'
  • Content publish triggers auto-queue at priority 100
  • Search analytics data fetched and cached with 24-hour TTL
  • Analytics endpoints return cached data with date range filtering
  • All endpoints require authentication and enforce account isolation
  • Frontend GSC dashboard shows: connection status, quota usage, inspection list, analytics charts
  • inspection status badges display correctly on URL list
  • google-auth, google-auth-oauthlib, google-api-python-client added to requirements.txt
  • Disconnecting GSC revokes token and deletes GSCConnection

6. CLAUDE CODE INSTRUCTIONS

Execution Order

  1. Read backend/igny8_core/business/integration/models.py — understand existing SiteIntegration, SyncEvent
  2. Read backend/igny8_core/modules/integration/urls.py — understand existing URL patterns
  3. Read backend/igny8_core/celery.py — understand beat schedule registration
  4. Add new packages to requirements.txt
  5. Create GSC models (5 models)
  6. Create migration, run it
  7. Build GSCService (OAuth + API client)
  8. Build IndexingQueueProcessor
  9. Build Celery tasks (4 tasks) + register in beat schedule
  10. Build auto-indexing signal
  11. Build serializers, ViewSets, URLs
  12. Build frontend components

Key Constraints

  • ALL primary keys are BigAutoField (integer). No UUIDs.
  • Model class names: GSCConnection, URLInspectionRecord, IndexingQueue, GSCMetricsCache, GSCDailyQuota (descriptive names, not plural)
  • Frontend: .tsx files, Zustand stores, Vitest testing
  • Celery app name: igny8_core
  • All db_tables use igny8_ prefix
  • Tokens MUST be encrypted at rest (use same encryption as SiteIntegration.credentials_json)
  • OAuth client_id/secret must be in environment variables, never in code
  • Follow existing integration app patterns for URL structure

File Tree (New/Modified)

backend/igny8_core/
├── business/integration/
│   ├── models.py                                        # MODIFY or NEW gsc_models.py: 5 new models
│   ├── gsc_service.py                                   # NEW: GSCService (OAuth + API)
│   ├── indexing_queue_processor.py                       # NEW: IndexingQueueProcessor
│   ├── signals.py                                       # NEW: auto-indexing signal
│   └── apps.py                                          # MODIFY: register signals in ready()
├── tasks/
│   └── gsc_tasks.py                                     # NEW: 4 Celery tasks
├── celery.py                                            # MODIFY: add 4 beat schedule entries
├── settings.py                                          # MODIFY: add GOOGLE_* settings
├── modules/integration/
│   ├── serializers/
│   │   └── gsc_serializers.py                           # NEW
│   ├── views/
│   │   └── gsc_views.py                                 # NEW
│   └── urls.py                                          # MODIFY: register GSC endpoints
├── migrations/
│   └── XXXX_gsc_integration.py                          # NEW: auto-generated
├── requirements.txt                                     # MODIFY: add google-auth packages

frontend/src/
├── pages/Integration/
│   ├── GSCDashboard.tsx                                 # NEW
│   ├── GSCAnalytics.tsx                                 # NEW
│   ├── GSCInspections.tsx                               # NEW
│   └── GSCConnect.tsx                                   # NEW
├── components/Integration/
│   ├── QuotaIndicator.tsx                               # NEW
│   └── InspectionStatusBadge.tsx                        # NEW
├── stores/
│   └── gscStore.ts                                      # NEW: Zustand store
├── api/
│   └── gsc.ts                                           # NEW: API client

Cross-References

  • 01E (blueprint-aware pipeline): triggers auto-indexing after publish, hub pages prioritized
  • 02E (backlinks): GSC impressions data feeds backlink KPI dashboard
  • 02F (optimizer): GSC position data identifies optimization candidates
  • 03A (WP plugin standalone): standalone plugin has GSC dashboard tab
  • 03B (WP plugin connected): connected mode syncs index statuses from IGNY8 to WP
  • 04B (reporting): GSC metrics (clicks, impressions, CTR) feed into service reports