feat: implement Trunks with real FreeSWITCH gateway sync
- trunks table (tenant-scoped, RLS): host/proxy/realm, register, username/password_enc (AES-256-GCM), dtmf_mode, ping, transport, and a status/status_updated_at pair meant to be driven by FreeSWITCH events - apps/api/src/trunks: CRUD (POST/GET/GET:id/DELETE), same RBAC/tenant pattern as Extensions, password never exposed in any GET - packages/telephony: buildGatewayXml() generates a Sofia gateway XML file - b2bcall-fs-config now writes sip_profiles/external/<trunk_id>.xml (shared Docker volume with FreeSWITCH -- the vanilla external profile already includes external/*.xml) and runs 'sofia profile external rescan' over ESL; syncs on boot and on demand via Redis pub/sub (b2bcall:trunks:sync), since apps/api runs on the host and fs-config has no port published to reach directly - added FreeSwitchTelephonyProvider.waitUntilConnected() to fix a startup race: the first sync ran before the ESL connection had settled, logging a harmless but noisy error - verified end-to-end with a fake host: create trunk -> gateway file written -> FreeSWITCH shows the real gateway (FAIL_WAIT, expected) -> delete -> file removed (cleanup also correctly swept the stale 'example.com' gateway that had been copied into the volume from the vanilla image) - apps/freeswitch-events/src/trunk-status.ts: written to update Trunk.status from sofia::gateway_state events, using the same normalizeEslEvent path already proven for CHANNEL_* events - KNOWN GAP, documented rather than glossed over: monitored fs-events for ~90s while the gateway visibly transitioned states in FreeSWITCH (FAIL_WAIT/DOWN) and no sofia::gateway_state event was observed arriving. CUSTOM/sofia::* events have not actually been proven working end-to-end in this session -- only CHANNEL_* events have been. Needs verification against a real SIP target before the status auto-update can be trusted in production. See docs/TRUNKS.md and TODO.md. - docs/TRUNKS.md
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "trunk_status" AS ENUM ('UP', 'DOWN', 'REGISTERED', 'TRYING', 'FAILED', 'UNREGISTERED', 'UNKNOWN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "dtmf_mode" AS ENUM ('RFC2833', 'INFO', 'INBAND');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "sip_transport" AS ENUM ('UDP', 'TCP', 'TLS');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "trunks" (
|
||||
"id" UUID NOT NULL,
|
||||
"tenant_id" UUID NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"sofia_profile" TEXT NOT NULL DEFAULT 'external',
|
||||
"host" TEXT NOT NULL,
|
||||
"proxy" TEXT,
|
||||
"realm" TEXT,
|
||||
"register" BOOLEAN NOT NULL DEFAULT true,
|
||||
"username" TEXT,
|
||||
"password_enc" TEXT,
|
||||
"from_user" TEXT,
|
||||
"from_domain" TEXT,
|
||||
"register_proxy" TEXT,
|
||||
"outbound_proxy" TEXT,
|
||||
"expire_seconds" INTEGER NOT NULL DEFAULT 3600,
|
||||
"retry_seconds" INTEGER NOT NULL DEFAULT 30,
|
||||
"caller_id_name" TEXT,
|
||||
"caller_id_number" TEXT,
|
||||
"codecs" TEXT NOT NULL DEFAULT 'PCMU,PCMA,OPUS',
|
||||
"dtmf_mode" "dtmf_mode" NOT NULL DEFAULT 'RFC2833',
|
||||
"ping" BOOLEAN NOT NULL DEFAULT true,
|
||||
"ping_frequency" INTEGER NOT NULL DEFAULT 30,
|
||||
"transport" "sip_transport" NOT NULL DEFAULT 'UDP',
|
||||
"inbound_context" TEXT NOT NULL DEFAULT 'default',
|
||||
"max_cps" INTEGER,
|
||||
"max_channels" INTEGER,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"status" "trunk_status" NOT NULL DEFAULT 'UNKNOWN',
|
||||
"status_updated_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"deleted_at" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "trunks_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "trunks_tenant_id_idx" ON "trunks"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "trunks_tenant_id_name_key" ON "trunks"("tenant_id", "name");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "trunks" ADD CONSTRAINT "trunks_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- Tabela de negocio tenant-scoped: RLS obrigatorio (ver docs/TENANT_ISOLATION.md).
|
||||
ALTER TABLE "trunks" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "trunks" FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "tenant_isolation" ON "trunks"
|
||||
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid);
|
||||
Reference in New Issue
Block a user