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.
124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Parse Chatwoot routes.rb and generate full route list comparable to GoChat dump_routes output."""
|
|
import re
|
|
import sys
|
|
|
|
def main():
|
|
with open('/home/yanghao05/Workspace/chatwoot-reference/config/routes.rb', 'r') as f:
|
|
content = f.read()
|
|
|
|
lines = content.split('\n')
|
|
namespace_stack = [] # (name, indent_level)
|
|
routes = []
|
|
|
|
def current_prefix():
|
|
parts = [ns for ns, _ in namespace_stack]
|
|
if parts:
|
|
return '/' + '/'.join(parts)
|
|
return ''
|
|
|
|
# Method mapping for Rails actions
|
|
method_map = {
|
|
'index': 'GET', 'show': 'GET', 'create': 'POST',
|
|
'update': 'PATCH', 'destroy': 'DELETE', 'new': 'GET', 'edit': 'GET'
|
|
}
|
|
|
|
# Parse each line
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
stripped = line.strip()
|
|
indent = len(line) - len(line.lstrip())
|
|
|
|
# Namespace
|
|
ns_match = re.match(r'namespace\s+:(\w+)', stripped)
|
|
if ns_match:
|
|
ns = ns_match.group(1)
|
|
namespace_stack.append((ns, indent))
|
|
i += 1
|
|
continue
|
|
|
|
# scope module:
|
|
scope_match = re.match(r'scope\s+module:\s+:(\w+)', stripped)
|
|
if scope_match:
|
|
mod = scope_match.group(1)
|
|
namespace_stack.append(('__scope_' + mod, indent))
|
|
i += 1
|
|
continue
|
|
|
|
# End block
|
|
if stripped == 'end':
|
|
if namespace_stack and namespace_stack[-1][1] >= indent:
|
|
namespace_stack.pop()
|
|
i += 1
|
|
continue
|
|
|
|
# resources / resource
|
|
res_match = re.match(r'(resources|resource)\s+:([a-zA-Z_]\w*)', stripped)
|
|
if res_match:
|
|
kind = res_match.group(1)
|
|
name = res_match.group(2)
|
|
is_singular = (kind == 'resource')
|
|
|
|
# Extract only constraint
|
|
only_match = re.search(r'only:\s*\[([^\]]+)\]', stripped)
|
|
if only_match:
|
|
actions = [a.strip().strip(':').strip("'").strip('"') for a in only_match.group(1).split(',')]
|
|
else:
|
|
if is_singular:
|
|
actions = ['show', 'create', 'update', 'destroy']
|
|
else:
|
|
actions = ['index', 'show', 'create', 'update', 'destroy', 'new', 'edit']
|
|
|
|
# Extract except constraint
|
|
except_match = re.search(r'except:\s*\[([^\]]+)\]', stripped)
|
|
if except_match:
|
|
excluded = [a.strip().strip(':').strip("'").strip('"') for a in except_match.group(1).split(',')]
|
|
actions = [a for a in actions if a not in excluded]
|
|
|
|
prefix = current_prefix()
|
|
|
|
for action in actions:
|
|
method = method_map.get(action, 'GET')
|
|
if is_singular:
|
|
path = prefix + '/' + name
|
|
elif action in ['index', 'create', 'new']:
|
|
path = prefix + '/' + name
|
|
else:
|
|
path = prefix + '/' + name + '/:id'
|
|
routes.append((method, path, action))
|
|
|
|
i += 1
|
|
continue
|
|
|
|
# member do block - look for routes inside
|
|
# collection do block
|
|
|
|
# Direct route definitions
|
|
direct_match = re.match(r"(get|post|put|patch|delete)\s+'([^']+)'", stripped)
|
|
if direct_match:
|
|
method = direct_match.group(1).upper()
|
|
path = direct_match.group(2)
|
|
routes.append((method, current_prefix() + path, ''))
|
|
i += 1
|
|
continue
|
|
|
|
# member action: post :action, on: :member
|
|
member_action = re.match(r"(get|post|put|patch|delete)\s+:(\w+)", stripped)
|
|
if member_action and not res_match:
|
|
method = member_action.group(1).upper()
|
|
action = member_action.group(2)
|
|
routes.append((method, current_prefix() + '/:action_' + action, action))
|
|
i += 1
|
|
continue
|
|
|
|
i += 1
|
|
|
|
# Deduplicate
|
|
unique_routes = sorted(set(routes))
|
|
print(f"Total unique routes: {len(unique_routes)}")
|
|
for method, path, action in unique_routes:
|
|
print(f"{method} {path}")
|
|
|
|
if __name__ == '__main__':
|
|
main() |