Bỏ qua để đến nội dung

Kiến trúc tổng thể

┌─────────────────────────────────────────────────────────────┐
│ Admin console (Nuxt 4, NuxtUI v4) │
│ admin/packages/helpdesk ← Nuxt layer │
│ app/ pages · components · composables (14 entity stores) │
│ ▲ │
│ │ $fetch (cookie-auth) │
│ server/api/helpdesk/** ← BFF: assertPermission + proxy │
└─────────┼─────────────────────────────────────────────────────┘
│ @odp/sdk (customEndpoint, userApi.request)
┌─────────────────────────────────────────────────────────────┐
│ ODP API server (@odp/api, Fastify + Knex) │
│ backend/extensions/helpdesk ← bundle extension │
│ endpoints/ routes · controllers · services │
│ hooks/ filter/action lifecycle + cron schedules │
│ ▲ │
│ │ ItemsService / FilesService / getSchema / database │
│ ▼ │
│ ODP core: permissions · activity · events · Knex · storage │
└─────────────────────────────────────────────────────────────┘
▼ Postgres/MySQL/SQLite (hd_* collections + c_contacts + odp_*)

1 · UI (Vue/Nuxt)

Pages + components + composables trong Nuxt layer. Không gọi thẳng backend — mọi request đi qua BFF cùng origin.

2 · BFF

server/api/helpdesk/** proxy sang /helpdesk/* của backend, đính token từ cookie, chặn quyền bằng assertPermission.

3 · Extension

Endpoints (HTTP) + hooks (lifecycle) + schedules (cron). Đây là nơi chứa toàn bộ logic nghiệp vụ server-side.

4 · ODP core

ItemsService (permission + activity + events), Knex, FilesService, cron đa-instance-safe, storage driver.

  • Thư mụcbackend/extensions/helpdesk/
    • Thư mụcsrc/
      • Thư mụcendpoints/
        • index.ts defineEndpoint → registerRoutes
        • routes.ts mọi route + guard withAccess
        • context.ts AppContext + createItemsService
        • Thư mụccontrollers/ conversations, messages, inboxes, connections, reports…
        • Thư mụcservices/
          • Thư mụcinbound/ imap-source, pipeline, normalized, sanitize, crypto
          • Thư mụcoutbound/ smtp-sender
          • Thư mụcautomation/ engine, conditions, actions
          • sla.ts computeDeadlines, recomputeSlaStatus, autoReopenSnoozed
          • contact-choke-point.ts cửa duy nhất tạo/tìm c_contacts
          • m2m-hydrator.ts hydrate junction (batch whereIn)
          • normalizer.ts mask secret + stringify id
      • hooks/index.ts filter/action + 2 cron (SLA/snooze, IMAP poll)
  • Thư mụcadmin/packages/helpdesk/
    • Thư mụcapp/ pages · components · composables · channels · utils
    • server/api/helpdesk/** BFF routes
    • Thư mụci18n/ en.json, vi.json (đang hoàn thiện)

Đọc danh sách hội thoại (agent mở màn hình):

UI store useHelpdeskConversations.fetchPage(query)
→ $fetch GET /api/helpdesk/conversations?page&limit&status&… (BFF)
→ assertPermission({module:'helpdesk', action:'read'})
→ userApi.request(customEndpoint(GET /helpdesk/conversations)) (SDK)
→ withAccess(ctx,'helpdesk','read', ConversationsController.list)
→ ItemsService.readByQuery + MetaService (total_count/filter_count)
→ hydrateConversations (label_ids + requester từ c_contacts)
→ normalizeConversation (mask + stringify id)

Email vào thành ticket (nền, không cần UI):

cron mỗi phút → pollImapConnections
→ mỗi hd_connections (inbound_provider=imap) → ImapFlow fetch mail mới
→ simpleParser → processInboundEmail (pipeline 8 bước)
→ resolve inbox → loop-guard → sender filter → sanitize
→ dedup theo Message-ID → ensureContactForRequester → threading
→ tạo/append hd_messages → hook fire automation + SLA

Master/Satellite cho danh tính

Tên/email/phone chỉ sống trên c_contacts (master, chia sẻ với module Contacts/HR). Helpdesk chỉ giữ contact_id. Mọi lần tạo/tìm contact đi qua contact-choke-point duy nhất. → Chi tiết

Server sở hữu automation & SLA

Automation, SLA recompute, snooze auto-reopen chạy bằng hooks + cron server-side — không phụ thuộc tab trình duyệt nào đang mở. Client chỉ re-read. → SLA & Snooze

ItemsService, không raw SQL

CRUD collection đi qua ItemsService để có permission + activity + event. Raw Knex chỉ cho aggregation, junction, denormalized counter, watermark — luôn có comment giải thích.

Trust model module-level (v1)

createItemsService ép admin:true; authorization dồn về guard withAccess(module, action) ở tầng route. Chưa có row-scoping theo inbox/team — chủ ý cho team nhỏ. → Trust model

  • Extension load một lần lúc boot; “hot reload” = restart cả process. State ở module-level bị xoá mỗi lần restart. Sửa backend → rebuild dist → restart server (không có auto-reload). → Cấu trúc & vòng đời
  • BFF là ranh giới bắt buộc. UI không bao giờ gọi thẳng :8055. Thêm route backend thì phải thêm route BFF tương ứng.
  • Đổi ODP: directus_*odp_*. Bảng hệ thống là odp_users, odp_files, odp_roles… không còn tiền tố directus_.