Pedido do usuário: abrir a edição completa do ramal no mesmo lugar de ver/redefinir a senha, em vez de só um campo (grupo de captura) editável isoladamente. UpdateExtensionDto ganha name/context/codecs (só callGroup e caller ID eram editáveis antes); codecs valida contra uma lista fixa e agora realmente vira absolute_codec_string no directory XML — antes a coluna existia no schema mas não tinha efeito nenhum no FreeSWITCH. Grupo de captura vira um input com datalist alimentado pelos grupos já usados em outros ramais do tenant, em vez de digitar de novo do zero. Testado ponta a ponta com Playwright (tenant/ramais descartáveis): grupo reaproveitado de outro ramal, edição de nome/caller ID/contexto, remoção de um codec — tudo persistido e refletido na tela depois de salvar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { requireSession } from "@/lib/session";
|
|
import { apiFetch, ApiError } from "@/lib/api";
|
|
import type { Extension } from "@/lib/extension-types";
|
|
|
|
function extractErrorMessage(err: unknown): string {
|
|
if (err instanceof ApiError) {
|
|
try {
|
|
const parsed = JSON.parse(err.message);
|
|
if (Array.isArray(parsed.message)) return parsed.message.join(" ");
|
|
if (typeof parsed.message === "string") return parsed.message;
|
|
} catch {
|
|
// corpo não era JSON — usa a mensagem crua
|
|
}
|
|
return err.message || "Falha inesperada na API.";
|
|
}
|
|
return "Falha inesperada. Tente novamente.";
|
|
}
|
|
|
|
export interface CreateExtensionInput {
|
|
number: string;
|
|
name: string;
|
|
callerIdName?: string;
|
|
callerIdNumber?: string;
|
|
context?: string;
|
|
maxRegistrations?: number;
|
|
callGroup?: string;
|
|
}
|
|
|
|
export async function createExtension(
|
|
input: CreateExtensionInput,
|
|
): Promise<{ ok: true; extension: Extension; sipPassword: string } | { ok: false; error: string }> {
|
|
const session = await requireSession();
|
|
try {
|
|
const result = await apiFetch<Extension & { sipPassword: string }>("/extensions", session.accessToken, {
|
|
method: "POST",
|
|
body: JSON.stringify(input),
|
|
});
|
|
const { sipPassword, ...extension } = result;
|
|
revalidatePath("/app/telefonia/ramais");
|
|
return { ok: true, extension, sipPassword };
|
|
} catch (err) {
|
|
return { ok: false, error: extractErrorMessage(err) };
|
|
}
|
|
}
|
|
|
|
export async function resetExtensionPassword(id: string): Promise<{ ok: true; sipPassword: string } | { ok: false; error: string }> {
|
|
const session = await requireSession();
|
|
try {
|
|
const { sipPassword } = await apiFetch<{ sipPassword: string }>(`/extensions/${id}/reset-password`, session.accessToken, {
|
|
method: "POST",
|
|
});
|
|
return { ok: true, sipPassword };
|
|
} catch (err) {
|
|
return { ok: false, error: extractErrorMessage(err) };
|
|
}
|
|
}
|
|
|
|
/** Diferente de `resetExtensionPassword`: não troca nada, só mostra a
|
|
* senha atual de novo — pra reconfigurar um telefone/softphone sem
|
|
* derrubar o registro de outro aparelho já usando essa senha. */
|
|
export async function revealExtensionPassword(id: string): Promise<{ ok: true; sipPassword: string } | { ok: false; error: string }> {
|
|
const session = await requireSession();
|
|
try {
|
|
const { sipPassword } = await apiFetch<{ sipPassword: string }>(`/extensions/${id}/reveal-password`, session.accessToken, {
|
|
method: "POST",
|
|
});
|
|
return { ok: true, sipPassword };
|
|
} catch (err) {
|
|
return { ok: false, error: extractErrorMessage(err) };
|
|
}
|
|
}
|
|
|
|
export interface UpdateExtensionInput {
|
|
name?: string;
|
|
callerIdName?: string | null;
|
|
callerIdNumber?: string | null;
|
|
context?: string;
|
|
codecs?: string;
|
|
maxRegistrations?: number;
|
|
callGroup?: string | null;
|
|
}
|
|
|
|
export async function updateExtension(id: string, input: UpdateExtensionInput): Promise<{ ok: true; extension: Extension } | { ok: false; error: string }> {
|
|
const session = await requireSession();
|
|
try {
|
|
const extension = await apiFetch<Extension>(`/extensions/${id}`, session.accessToken, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(input),
|
|
});
|
|
revalidatePath(`/app/telefonia/ramais/${id}`);
|
|
revalidatePath("/app/telefonia/ramais");
|
|
return { ok: true, extension };
|
|
} catch (err) {
|
|
return { ok: false, error: extractErrorMessage(err) };
|
|
}
|
|
}
|
|
|
|
export async function deleteExtension(id: string): Promise<{ ok: true } | { ok: false; error: string }> {
|
|
const session = await requireSession();
|
|
try {
|
|
await apiFetch<void>(`/extensions/${id}`, session.accessToken, { method: "DELETE" });
|
|
revalidatePath("/app/telefonia/ramais");
|
|
return { ok: true };
|
|
} catch (err) {
|
|
return { ok: false, error: extractErrorMessage(err) };
|
|
}
|
|
}
|