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.
100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Precise comparison of GoChat routes vs Chatwoot routes.
|
|
Normalizes parameter names (:account_id → :id, etc.) for fair comparison.
|
|
"""
|
|
import re
|
|
import sys
|
|
|
|
def normalize(path):
|
|
"""Normalize route params to generic :id for comparison."""
|
|
# Replace all :xxx_id variants with :id
|
|
path = re.sub(r':[a-zA-Z_]+_id', ':id', path)
|
|
# Replace :website_token, :thread_id, :slug, :identifier, etc. with :id
|
|
path = re.sub(r':(website_token|thread_id|slug|identifier|channel_type|fb_id|locale|category_slug|article_slug|portal_id|user_id)', ':id', path)
|
|
# Remove trailing slashes
|
|
path = path.rstrip('/')
|
|
return path
|
|
|
|
def load_routes(filepath):
|
|
routes = {}
|
|
with open(filepath) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
parts = line.split(' ', 1)
|
|
if len(parts) == 2:
|
|
method, path = parts
|
|
norm = normalize(path)
|
|
routes[(method, norm)] = (method, path)
|
|
return routes
|
|
|
|
def main():
|
|
gochat = load_routes('/tmp/gochat_routes_all.txt')
|
|
chatwoot = load_routes('/tmp/chatwoot_routes_all.txt')
|
|
|
|
gochat_keys = set(gochat.keys())
|
|
chatwoot_keys = set(chatwoot.keys())
|
|
|
|
matched = gochat_keys & chatwoot_keys
|
|
missing_in_gochat = chatwoot_keys - gochat_keys
|
|
extra_in_gochat = gochat_keys - chatwoot_keys
|
|
|
|
print("=" * 80)
|
|
print("GoChat vs Chatwoot 路由对比报告")
|
|
print("=" * 80)
|
|
print(f"\nGoChat 路由总数: {len(gochat_keys)}")
|
|
print(f"Chatwoot API路由总数: {len(chatwoot_keys)}")
|
|
print(f"匹配路由数: {len(matched)}")
|
|
print(f"GoChat缺失路由数: {len(missing_in_gochat)}")
|
|
print(f"GoChat多余路由数: {len(extra_in_gochat)}")
|
|
print(f"覆盖率: {len(matched)/len(chatwoot_keys)*100:.1f}%")
|
|
|
|
# Group missing routes by resource prefix
|
|
print(f"\n{'=' * 80}")
|
|
print("GoChat缺失路由 (按资源分组)")
|
|
print("=" * 80)
|
|
|
|
by_resource = {}
|
|
for key in sorted(missing_in_gochat):
|
|
method, norm_path = key
|
|
orig_method, orig_path = chatwoot[key]
|
|
# Extract resource name
|
|
m = re.match(r'/api/v1(/platform/api/v1)?(/public)?(/[a-zA-Z_]+(?:_scope)?/?)?(/[a-zA-Z_]+)', norm_path)
|
|
if m:
|
|
resource = m.group(0)
|
|
else:
|
|
resource = norm_path.split('/:id')[0] if '/:id' in norm_path else norm_path
|
|
by_resource.setdefault(resource, []).append((method, orig_path, norm_path))
|
|
|
|
for resource in sorted(by_resource.keys()):
|
|
routes = by_resource[resource]
|
|
print(f"\n [{resource}] — 缺失{len(routes)}条路由:")
|
|
for method, orig_path, norm_path in sorted(routes):
|
|
print(f" {method} {orig_path}")
|
|
|
|
# Group extra routes
|
|
print(f"\n{'=' * 80}")
|
|
print("GoChat多余路由 (Chatwoot中不存在)")
|
|
print("=" * 80)
|
|
|
|
by_resource_extra = {}
|
|
for key in sorted(extra_in_gochat):
|
|
method, norm_path = key
|
|
orig_method, orig_path = gochat[key]
|
|
# Extract top-level prefix
|
|
parts = norm_path.split('/')
|
|
if len(parts) >= 4:
|
|
resource = '/' + '/'.join(parts[:4])
|
|
else:
|
|
resource = norm_path
|
|
by_resource_extra.setdefault(resource, []).append((method, orig_path))
|
|
|
|
for resource in sorted(by_resource_extra.keys()):
|
|
routes = by_resource_extra[resource]
|
|
print(f"\n [{resource}] — 多余{len(routes)}条路由:")
|
|
for method, orig_path in sorted(routes):
|
|
print(f" {method} {orig_path}")
|
|
|
|
if __name__ == '__main__':
|
|
main() |