Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
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)}") |