137 lines
5.7 KiB
Python
137 lines
5.7 KiB
Python
import re, os, sys
|
|
|
|
# Parse Chatwoot routes.rb to extract all API routes
|
|
routes_rb = '/home/yanghao05/Workspace/chatwoot-reference/config/routes.rb'
|
|
with open(routes_rb) as f:
|
|
text = f.read()
|
|
|
|
# We need to parse nested namespace/resource blocks
|
|
# Strategy: manually parse the nested structure
|
|
|
|
# Known Chatwoot API route structure from routes.rb analysis:
|
|
# - /api/v1/ namespace with nested resources
|
|
# - /api/v2/ namespace
|
|
# - /public/ namespace
|
|
# - /widget/ namespace
|
|
# - Webhook routes
|
|
# - Auth routes
|
|
|
|
# Let's extract all controller#action pairs from the controllers directory
|
|
# and map them to expected routes
|
|
|
|
controllers_dir = '/home/yanghao05/Workspace/chatwoot-reference/app/controllers/api'
|
|
routes = []
|
|
|
|
for root, dirs, files in os.walk(controllers_dir):
|
|
for fname in files:
|
|
if fname.endswith('_controller.rb') and 'base_controller' not in fname and 'concerns' not in root:
|
|
fpath = os.path.join(root, fname)
|
|
# Convert file path to controller name
|
|
rel_path = os.path.relpath(fpath, '/home/yanghao05/Workspace/chatwoot-reference/app/controllers')
|
|
rel_path = rel_path.replace('.rb', '')
|
|
# Convert path separators to :: for Ruby module notation
|
|
controller_name = rel_path.replace('/', '::')
|
|
|
|
# Read the file to extract public action methods
|
|
with open(fpath) as cf:
|
|
controller_text = cf.read()
|
|
|
|
# Find all public method definitions (actions)
|
|
# In Rails controllers, actions are public methods defined with def
|
|
actions = []
|
|
in_private = False
|
|
in_protected = False
|
|
for line in controller_text.split('\n'):
|
|
stripped = line.strip()
|
|
if stripped == 'private' or stripped.startswith('private :'):
|
|
in_private = True
|
|
continue
|
|
if stripped == 'protected' or stripped.startswith('protected :'):
|
|
in_protected = True
|
|
continue
|
|
if re.match(r'def\s+\w+', stripped) and not in_private and not in_protected:
|
|
action_name = re.match(r'def\s+(\w+)', stripped).group(1)
|
|
# Skip internal/helper methods
|
|
if not action_name.startswith('_') and action_name not in ['initialize', 'setup', 'teardown']:
|
|
actions.append(action_name)
|
|
# Reset private/protected scope when encountering new visibility keyword
|
|
if stripped == 'public':
|
|
in_private = False
|
|
in_protected = False
|
|
|
|
for action in actions:
|
|
routes.append((controller_name, action))
|
|
|
|
# Now map controller names to API routes
|
|
# Standard Rails routing conventions:
|
|
# api/v1/accounts/conversations_controller -> /api/v1/accounts/:account_id/conversations
|
|
# api/v1/accounts/conversations/messages_controller -> /api/v1/accounts/:account_id/conversations/:conversation_id/messages
|
|
|
|
# Build route path from controller name
|
|
def controller_to_route_path(controller_name):
|
|
"""Convert Ruby controller name to expected route path prefix."""
|
|
parts = controller_name.split('::')
|
|
path_parts = []
|
|
resource_stack = []
|
|
|
|
for part in parts:
|
|
# Skip 'api' and 'v1'/'v2' - they become path prefixes
|
|
if part == 'api':
|
|
continue
|
|
if part in ('v1', 'v2'):
|
|
path_parts.append(f'/api/{part}')
|
|
continue
|
|
|
|
# Singularize: Rails uses plural for resources, singular for nested resource names
|
|
# Check if this is a nested singular resource (like 'contacts' inside conversations)
|
|
name = part
|
|
|
|
# Convert CamelCase to snake_case
|
|
name = re.sub(r'([A-Z])', r'_\1', name).lower().lstrip('_')
|
|
|
|
# Handle special cases
|
|
if name == 'captain':
|
|
path_parts.append('/captain')
|
|
continue
|
|
|
|
# This part represents a resource
|
|
# If it's a plural resource name, it gets :id parameter in routes
|
|
# Common plural resources
|
|
plural_resources = [
|
|
'accounts', 'conversations', 'contacts', 'inboxes', 'messages',
|
|
'agents', 'teams', 'team_members', 'labels', 'notifications',
|
|
'campaigns', 'automation_rules', 'canned_responses', 'webhooks',
|
|
'portals', 'categories', 'articles', 'csat_survey_responses',
|
|
'macros', 'custom_filters', 'custom_attribute_definitions',
|
|
'agent_bots', 'dashboard_apps', 'assignment_policies',
|
|
'sla_policies', 'custom_roles', 'whatsapp_calls',
|
|
'bulk_actions', 'inbox_members', 'notes',
|
|
'agent_capacity_policies', 'inbox_limits',
|
|
'copilot_threads', 'copilot_messages'
|
|
]
|
|
|
|
if name in plural_resources:
|
|
path_parts.append(f'/{name}')
|
|
path_parts.append(f'/:{"id" if name == "accounts" else name.replace("s", "") + "_id" if not name.endswith("es") else name[:-2] + "_id"}')
|
|
else:
|
|
path_parts.append(f'/{name}')
|
|
|
|
return ''.join(path_parts)
|
|
|
|
# Print summary
|
|
print("=== Chatwoot Controllers & Actions ===")
|
|
for controller, action in sorted(routes):
|
|
print(f" {controller}#{action}")
|
|
print(f"\nTotal controller actions: {len(routes)}")
|
|
|
|
# Also group by controller
|
|
controller_actions = {}
|
|
for controller, action in routes:
|
|
if controller not in controller_actions:
|
|
controller_actions[controller] = []
|
|
controller_actions[controller].append(action)
|
|
|
|
print(f"\nTotal controllers: {len(controller_actions)}")
|
|
for controller in sorted(controller_actions.keys()):
|
|
actions = controller_actions[controller]
|
|
print(f" {controller}: {len(actions)} actions -> {', '.join(actions)}") |