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

Backend · Cấu trúc & vòng đời

Khai báo trong backend/extensions/helpdesk/package.json:

{
"name": "@codihaus/odp-extension-helpdesk",
"main": "dist/index.js",
"odp-extension": {
"id": "helpdesk",
"type": "bundle",
"entries": [
{ "type": "hook", "name": "helpdesk-hooks", "source": "src/hooks/index.ts" },
{ "type": "endpoint", "name": "helpdesk", "source": "src/endpoints/index.ts" }
],
"permissions": {
"module": "helpdesk",
"displayName": "Helpdesk",
"actions": [
{ "key": "read", "displayName": "View Helpdesk", "riskLevel": "low" },
{ "key": "create", "displayName": "Create Helpdesk Records", "riskLevel": "low" },
{ "key": "update", "displayName": "Edit Helpdesk Records", "riskLevel": "medium" },
{ "key": "delete", "displayName": "Delete Helpdesk Records", "riskLevel": "medium" },
{ "key": "manage-settings", "displayName": "Manage Settings", "riskLevel": "high" },
{ "key": "manage-automation", "displayName": "Manage Automation", "riskLevel": "high" },
{ "key": "manage-inboxes", "displayName": "Manage Inboxes", "riskLevel": "high" }
]
}
}
}

permissions.actions ở đây chính là danh sách action mà validateAppAccess sẽ đối chiếu — xem Endpoints, routes & guards. Khai báo permission ngay trong package.json để ODP tự sinh policy option trong Admin console, không cần migration riêng.

  • Thư mụcbackend/extensions/helpdesk/
    • package.json odp-extension bundle manifest
    • Thư mụcsrc/
      • Thư mụcendpoints/
        • index.ts defineEndpoint → registerRoutes(router, ctx)
        • context.ts AppContext + createItemsService (admin:true ép ở đây)
        • routes.ts toàn bộ route + guard withAccess
        • middleware/require-access.ts
        • 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
          • contact-choke-point.ts
          • m2m-hydrator.ts
          • normalizer.ts
      • hooks/index.ts filter/action + 2 cron (SLA/snooze, IMAP poll)
    • dist/index.js build output — single ESM bundle, load bởi ODP

ODP boot theo thứ tự cố định (chi tiết đầy đủ ở skill odp-extension-devreferences/extension-loading.md):

Phase 1 — Hooks

extensionManager.scanAndLoadHooks() chạy trước khi Fastify server tồn tại. Toàn bộ filter(...), action(...), schedule(...) trong hooks/index.ts đăng ký lên emitter ở bước này.

Phase 2 — Endpoints

createServer() dựng Fastify xong mới gọi extensionManager.loadEndpoints() — lúc này endpoints/index.ts mới chạy, route mount vào router.

Route mount tại /helpdesk/* (bundle entry name = helpdesk, khớp name trong odp-extension.entries).

src/endpoints/index.ts chỉ làm một việc: build AppContext rồi giao cho registerRoutes:

backend/extensions/helpdesk/src/endpoints/index.ts
import { defineEndpoint } from '@codihaus/odp-extensions-sdk';
import type { AppContext } from './context.js';
import { registerRoutes } from './routes.js';
export default defineEndpoint((router, { services, database, logger, env, getSchema }) => {
const ctx: AppContext = {
services,
database,
getSchema,
log: logger as any,
env: env as Record<string, unknown>,
};
registerRoutes(router, ctx);
});

AppContext — hình dạng “toàn bộ API surface” của extension

Phần tiêu đề “AppContext — hình dạng “toàn bộ API surface” của extension”

context.ts định nghĩa type dùng xuyên suốt mọi controller/service:

backend/extensions/helpdesk/src/endpoints/context.ts
export interface AppContext {
services: any;
database: any;
getSchema: () => Promise<any>;
log: {
info: (obj: unknown, msg?: string) => void;
warn: (obj: unknown, msg?: string) => void;
error: (obj: unknown, msg?: string) => void;
};
env: Record<string, unknown>;
// Optional so existing call sites (that build ctx without cache) still typecheck;
// inbound services that need it guard for undefined.
cache?: ExtensionCache;
// Emitter for cross-module action events (e.g. notify.trigger → Power Notify).
emitter?: { emitAction: (event: string, payload: any, context: any) => void };
}

hooks/index.ts build một AppContext tương tự nhưng có thêm cache (isolated store, dùng cho OAuth token cache + IMAP poison-message backoff) và emitter. Hai đầu vào (hook + endpoint) build ctx độc lập — không chia sẻ instance — nhưng cùng shape để các service dùng chung (sla.ts, automation/engine.ts) chạy được từ cả hai phía.

Mọi truy cập DB collection đi qua createItemsService(ctx, collection, accountability) — xem chi tiết cơ chế ép admin:trueMaster/Satellite & Trust model.

// package.json scripts
"scripts": {
"build": "node node_modules/@codihaus/odp-extensions-sdk/dist/cli.js build",
"dev": "node node_modules/@codihaus/odp-extensions-sdk/dist/cli.js build --watch"
}

node_modules/@codihaus/odp-extensions-sdk không tồn tại cục bộ trong backend/extensions/helpdesk/ — Node resolution đi ngược lên cây thư mục và tìm thấy ở backend/node_modules/@codihaus/odp-extensions-sdk/dist/cli.js (shamefully-hoist). Chạy npm run build trong thư mục extension thực chất gọi CLI đã hoist lên backend/node_modules.

Build output là một file duy nhất dist/index.js (ESM, self-contained) — đây là file ODP require/import lúc boot (theo main trong package.json).