#!/usr/bin/env python3 """ Fix all Gin radix tree param name conflicts in router.go. Strategy: Replace generic `:id` with specific resource param names everywhere. Rules (based on Chatwoot convention and Gin tree requirements): - accounts/:id → :account_id (but most already use :account_id) - conversations/:id → :conversation_id (already done) - messages/:id → :message_id - agent_bots/:id → :agent_bot_id - assistants/:id → :assistant_id - inboxes/:id → :inbox_id (but some already use :inbox_id) - teams/:id → :team_id - contacts/:id → :contact_id - campaigns/:id → :campaign_id - labels/:id → :label_id - automation_rules/:id → :automation_id - portals/:id → :portal_id - categories/:id → :category_id - articles/:id → :article_id - canned_responses/:id → :canned_id - macros/:id → :macro_id - custom_attribute_definitions/:id → :attribute_id - csat_survey_responses/:id → :csat_id - notes/:id → :note_id - webhook_subscriptions/:id → :webhook_id - team_members/:id → :member_id (or :user_id) - dashboard_apps/:id → :dashboard_app_id - notification_subscriptions/:identifier → keep (no conflict) - push_subscriptions/:id → :push_subscription_id - notifications/:id → :notification_id - draft_messages/:id → :draft_id - contacts/:id/custom_attributes → :contact_id - companies/:id → :company_id - sla_policies/:id → :policy_id - custom_roles/:id → :custom_role_id - folders/:id → :folder_id - inbox_members/:id → :member_id - hooks/:id → :hook_id """ import re router_path = '/home/yanghao05/Workspace/gochat/internal/router/router.go' handler_dir = '/home/yanghao05/Workspace/gochat/internal/handler/api/v1' # Read router.go with open(router_path) as f: router_content = f.read() router_lines = router_content.split('\n') # Context-aware replacement map # For each resource type, map :id → specific param name # We need to know which Group variable the route is under # First pass: extract group variable → resource type mapping # e.g. conversations := conversations.Group(...) → conversations var handles conversation routes # msgs := conversations.Group("/:conversation_id/messages") → msgs var handles message routes # Build a mapping of: (group_var_name → (parent_group_var, path_segment, specific_param_name)) # Then when we see varName.GET("/:id", ...), we know what :id should be renamed to # Simplified approach: look at the route context to determine the resource type # If route is under "conversations" group → :id means :conversation_id # If route is under "msgs" or "messages" group → :id means :message_id # etc. # Let's trace the group hierarchy from the router.go group_map = {} # var_name → (parent_var, sub_path, implied_resource_type) # Parse group definitions for i, line in enumerate(router_lines): stripped = line.strip() # Match: varName := parent.Group("/path/:param/...") gmatch = re.match(r'(\w+)\s*:?=\s*(\w+)\.Group\("([^"]+)"\)', stripped) if gmatch: var_name = gmatch.group(1) parent = gmatch.group(2) path = gmatch.group(3) group_map[var_name] = (parent, path) # Now for each line with a bare :id in a route, determine what it should be renamed to # based on context (which group variable is used, what resource type it represents) replacements_made = [] for i, line in enumerate(router_lines): stripped = line.strip() if stripped.startswith('//') or not stripped: continue # Find route registrations with bare :id # Pattern: varName.GET("/:id", ...) or varName.GET("/:id/subpath", ...) # We need to determine which resource type :id refers to # Check if line has a bare :id (not part of a longer name) if not re.search(r'"/:id(?:"|/)', stripped): continue # Determine the resource type from the group variable name and context for method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'Any']: pattern = rf'^(\w+)\.{method}\("([^"]+)"\s*,' rmatch = re.match(pattern, stripped) if rmatch: var_name = rmatch.group(1) path = rmatch.group(2) # Trace group hierarchy to determine resource type # Walk up from var_name through parent groups resource_param = None # Special cases based on variable name patterns var_lower = var_name.lower() if var_name == 'conversations': resource_param = 'conversation_id' elif var_name in ('msgs', 'messages') or 'message' in var_lower: resource_param = 'message_id' elif var_name == 'agents' or var_name == 'agentBotGroup': resource_param = 'agent_id' elif var_name == 'agentBots' or var_name == 'agentBotsGroup': resource_param = 'agent_bot_id' elif var_name == 'teams': resource_param = 'team_id' elif var_name == 'inboxes' or var_name == 'inboxGroup': resource_param = 'inbox_id' elif var_name == 'contacts' or var_name == 'contactGroup': resource_param = 'contact_id' elif var_name == 'campaigns' or var_name == 'campaignGroup': resource_param = 'campaign_id' elif var_name == 'labels': resource_param = 'label_id' elif var_name == 'automationRules' or var_name == 'automationGroup': resource_param = 'automation_id' elif var_name == 'portals' or var_name == 'portalGroup': resource_param = 'portal_id' elif var_name == 'categories': resource_param = 'category_id' elif var_name == 'articles': resource_param = 'article_id' elif var_name == 'cannedResponses' or var_name == 'cannedGroup': resource_param = 'canned_id' elif var_name == 'macros': resource_param = 'macro_id' elif var_name == 'customAttrDefs' or var_name == 'customAttrGroup': resource_param = 'attribute_id' elif var_name == 'notes' or var_name == 'noteGroup': resource_param = 'note_id' elif var_name == 'teamMembers': resource_param = 'member_id' elif var_name == 'dashboardApps': resource_param = 'dashboard_app_id' elif var_name == 'drafts': resource_param = 'draft_id' elif var_name == 'participants' or var_name == 'participantGroup': resource_param = 'participant_id' elif var_name == 'companies': resource_param = 'company_id' elif var_name == 'slaPolicies' or var_name == 'slaGroup': resource_param = 'policy_id' elif var_name == 'customRoles': resource_param = 'custom_role_id' elif var_name == 'folders': resource_param = 'folder_id' elif var_name == 'hooks' or var_name == 'hookGroup': resource_param = 'hook_id' elif var_name == 'assistants' or var_name == 'assistantGroup': resource_param = 'assistant_id' elif var_name == 'documents': resource_param = 'document_id' elif var_name == 'tools' or var_name == 'toolGroup': resource_param = 'tool_id' elif var_name == 'inboxMembers': resource_param = 'member_id' elif var_name == 'whatsappCalls': resource_param = 'call_id' elif var_name == 'botRules' or var_name == 'botRuleGroup': resource_param = 'rule_id' elif var_name == 'preferences': resource_param = 'preference_id' elif var_name == 'scenarios': resource_param = 'scenario_id' elif var_name == 'insights': resource_param = 'conversation_id' # insights are conversation-scoped # Platform-level routes (not under accounts group) elif var_name == 'g' or var_name == 'v1': # Check path context if '/notifications' in path or '/notification' in path: resource_param = 'notification_id' elif '/push_subscriptions' in path: resource_param = 'push_subscription_id' else: # Check nearby lines for context resource_param = None # accountScoped routes elif var_name == 'accountScoped' or var_name == 'accounts': # Path might be like "/:id" which means account_id if path == '/:id' or path.startswith('/:id'): resource_param = 'account_id' if resource_param and resource_param != 'id': # Replace :id with :resource_param in the path new_path = path.replace('/:id"', f'/:{resource_param}"') new_path = new_path.replace('/:id/', f'/:{resource_param}/') if new_path != path: # Replace in the original line new_line = line.replace(f'"{path}"', f'"{new_path}"') router_lines[i] = new_line replacements_made.append((i+1, var_name, path, new_path, resource_param)) break # Write back new_content = '\n'.join(router_lines) with open(router_path, 'w') as f: f.write(new_content) print(f"Made {len(replacements_made)} replacements in router.go:") for linenum, var, old, new, param in replacements_made: print(f" Line {linenum}: {var} {old} → {new} (:id → :{param})")