Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
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.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Precise GoChat vs Chatwoot route comparison.
|
||||
Normalizes param names for fair comparison.
|
||||
"""
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
def normalize(path):
|
||||
"""Normalize param names: :account_id, :agent_id → :param for fair matching."""
|
||||
# Replace all Rails-style params (:xxx_id, :xxx) with generic :param
|
||||
path = re.sub(r':[a-zA-Z_]+', ':param', 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 extract_resource(path):
|
||||
"""Extract top-level resource from path for grouping."""
|
||||
# Remove /api/v1 prefix and param placeholders
|
||||
m = re.match(r'/api/v[12](/platform/api/v1)?(/public)?/([^:]+)', path)
|
||||
if m:
|
||||
prefix = m.group(1) or ''
|
||||
public = m.group(2) or ''
|
||||
rest = m.group(3)
|
||||
return prefix + public + '/' + rest.split('/')[0]
|
||||
return path
|
||||
|
||||
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
|
||||
print(f"\n{'=' * 80}")
|
||||
print("一、GoChat缺失路由(按资源分组)")
|
||||
print("=" * 80)
|
||||
|
||||
by_resource = defaultdict(list)
|
||||
for key in sorted(missing_in_gochat):
|
||||
method, norm_path = key
|
||||
orig_method, orig_path = chatwoot[key]
|
||||
resource = extract_resource(orig_path)
|
||||
by_resource[resource].append((method, orig_path))
|
||||
|
||||
for resource in sorted(by_resource.keys()):
|
||||
routes = by_resource[resource]
|
||||
print(f"\n [{resource}] — 缺失{len(routes)}条路由:")
|
||||
for method, orig_path in sorted(routes):
|
||||
print(f" {method} {orig_path}")
|
||||
|
||||
# Group extra routes
|
||||
print(f"\n{'=' * 80}")
|
||||
print("二、GoChat多余路由(Chatwoot中不存在)")
|
||||
print("=" * 80)
|
||||
|
||||
by_resource_extra = defaultdict(list)
|
||||
for key in sorted(extra_in_gochat):
|
||||
method, norm_path = key
|
||||
orig_method, orig_path = gochat[key]
|
||||
resource = extract_resource(orig_path)
|
||||
by_resource_extra[resource].append((method, orig_path))
|
||||
|
||||
for resource in sorted(by_resource_extra.keys()):
|
||||
routes = by_resource_extra[resource]
|
||||
if len(routes) <= 5:
|
||||
print(f"\n [{resource}] — 多余{len(routes)}条路由:")
|
||||
for method, orig_path in sorted(routes):
|
||||
print(f" {method} {orig_path}")
|
||||
else:
|
||||
print(f"\n [{resource}] — 多余{len(routes)}条路由 (仅列出前5条):")
|
||||
for method, orig_path in sorted(routes)[:5]:
|
||||
print(f" {method} {orig_path}")
|
||||
|
||||
# Summary table by resource area
|
||||
print(f"\n{'=' * 80}")
|
||||
print("三、差距汇总(按功能域分组)")
|
||||
print("=" * 80)
|
||||
|
||||
# Define functional areas
|
||||
areas = {
|
||||
'Account管理': ['/api/v1/accounts/'],
|
||||
'Agent/团队成员': ['/api/v1/accounts/:param/agents/', '/api/v1/accounts/:param/team_members/', '/api/v1/accounts/:param/teams/', '/api/v1/accounts/:param/inbox_members/'],
|
||||
'消息/对话': ['/api/v1/accounts/:param/conversations/', '/api/v1/accounts/:param/messages/', '/api/v1/accounts/:param/draft_messages/'],
|
||||
'联系人/公司': ['/api/v1/accounts/:param/contacts/', '/api/v1/accounts/:param/companies/', '/api/v1/accounts/:param/contact_inboxes/', '/api/v1/actions/contact_merge'],
|
||||
'Inbox渠道': ['/api/v1/accounts/:param/inboxes/', '/api/v1/accounts/:param/inbox_limits/', '/api/v1/accounts/:param/campaigns/'],
|
||||
'自动化/宏': ['/api/v1/accounts/:param/automation_rules/', '/api/v1/accounts/:param/macros/'],
|
||||
'CSAT/评分': ['/api/v1/accounts/:param/csat_survey_responses/', '/api/v1/accounts/:param/inbox_csat_templates/'],
|
||||
'标签/分类': ['/api/v1/accounts/:param/labels/', '/api/v1/accounts/:param/canned_responses/'],
|
||||
'Captain/AI助手': ['/api/v1/accounts/:param/captain/'],
|
||||
'自定义属性/过滤器': ['/api/v1/accounts/:param/custom_attribute_definitions/', '/api/v1/accounts/:param/custom_filters/'],
|
||||
'通知设置': ['/api/v1/accounts/:param/notifications/', '/api/v1/accounts/:param/notification_settings/', '/api/v1/accounts/:param/notification_subscriptions/'],
|
||||
'报告': ['/api/v1/accounts/:param/reports/', '/api/v2/accounts/'],
|
||||
'SLA策略': ['/api/v1/accounts/:param/sla_policies/', '/api/v1/accounts/:param/applied_slas/'],
|
||||
'Portal/帮助中心': ['/api/v1/accounts/:param/portals/', '/hc/'],
|
||||
'Platform API': ['/platform/api/v1/'],
|
||||
'Public API': ['/public/api/v1/'],
|
||||
'Widget': ['/widget/'],
|
||||
'Webhook': ['/webhooks/'],
|
||||
'用户/Auth': ['/api/v1/profile/', '/api/v1/auth/', '/api/v1/agent_bots/'],
|
||||
'Agent Bot': ['/api/v1/accounts/:param/agent_bots/', '/api/v1/agent_bots/'],
|
||||
'Agent Capacity': ['/api/v1/accounts/:param/agent_capacity_policies/'],
|
||||
'Assignment Policy': ['/api/v1/accounts/:param/assignment_policies/', '/api/v1/accounts/:param/assignment_policy/', '/api/v1/accounts/:param/assignment_policies_v2/'],
|
||||
'Dashboard Apps': ['/api/v1/accounts/:param/dashboard_apps/'],
|
||||
'Hooks/集成': ['/api/v1/accounts/:param/hooks/'],
|
||||
'MFA/SAML': ['/api/v1/accounts/:param/mfa/', '/api/v1/accounts/:param/saml_settings/'],
|
||||
'Bulk Actions': ['/api/v1/accounts/:param/bulk_actions/', '/api/v1/actions/'],
|
||||
'Search': ['/api/v1/accounts/:param/search/'],
|
||||
'Enterprise Billing': ['/enterprise/api/v1/'],
|
||||
}
|
||||
|
||||
for area_name, prefixes in sorted(areas.items()):
|
||||
area_matched = 0
|
||||
area_missing = 0
|
||||
area_extra = 0
|
||||
missing_details = []
|
||||
|
||||
for prefix in prefixes:
|
||||
norm_prefix = normalize(prefix)
|
||||
for key in chatwoot_keys:
|
||||
method, norm_path = key
|
||||
if norm_path.startswith(norm_prefix):
|
||||
if key in gochat_keys:
|
||||
area_matched += 1
|
||||
else:
|
||||
area_missing += 1
|
||||
orig_method, orig_path = chatwoot[key]
|
||||
missing_details.append(f"{orig_method} {orig_path}")
|
||||
|
||||
for prefix in prefixes:
|
||||
norm_prefix = normalize(prefix)
|
||||
for key in gochat_keys:
|
||||
method, norm_path = key
|
||||
if norm_path.startswith(norm_prefix):
|
||||
if key not in chatwoot_keys:
|
||||
area_extra += 1
|
||||
|
||||
total = area_matched + area_missing
|
||||
pct = area_matched / total * 100 if total > 0 else 100
|
||||
|
||||
status = '✅' if pct >= 90 else '⚠️' if pct >= 50 else '❌'
|
||||
print(f"\n {status} {area_name}: {area_matched}/{total} 路由匹配 ({pct:.0f}%) — 缺{area_missing}条")
|
||||
if area_missing > 0 and area_missing <= 10:
|
||||
for d in sorted(missing_details):
|
||||
print(f" 缺: {d}")
|
||||
elif area_missing > 10:
|
||||
for d in sorted(missing_details)[:5]:
|
||||
print(f" 缺: {d}")
|
||||
print(f" ... 还有{area_missing-5}条")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user