Skip to main content
Glama
employee-api.ts12.7 kB
import axios, { AxiosRequestConfig } from "axios"; import { CreateEmployeeDTO, EmployeeSearch, SendInviteDTO, UpdateEmployeeDTO } from "../schemas/employee-schemas.js"; import { ErrorHandler } from "../utils/error-handler.js"; import { EmployeeApi, EmployeeSearchParams } from "./generated/index.js"; /** * API клиент для работы с employee endpoints * Интегрируется с существующей системой аутентификации */ export class EmployeeApiClient { private baseUrl: string; private apiKey: string; private generatedApi: EmployeeApi; constructor(baseUrl?: string, apiKey?: string) { this.baseUrl = baseUrl || process.env.EMPLOYEE_API_HOST_URL || "https://api.development.myradius.ru"; this.apiKey = apiKey || process.env.EMPLOYEE_API_KEY || ""; // Добавляем /employees prefix если не присутствует if (!this.baseUrl.includes("/employees")) { this.baseUrl = this.baseUrl.replace(/\/$/, "") + "/employees/"; } // Инициализируем сгенерированный API клиент this.generatedApi = new EmployeeApi(this.baseUrl, this.apiKey); } /** * Выполнение HTTP запроса */ private async makeRequest<T>(method: string, path: string, body: unknown = null, params: unknown = null): Promise<T> { const url = `${this.baseUrl}${path}`; const headers: Record<string, string> = { Authorization: this.apiKey, Accept: "application/json", }; if (method.toUpperCase() !== "GET" && body !== null) { headers["Content-Type"] = "application/json"; } console.log("[DEBUG] Employee API Request", { method, url, headers: { ...headers, Authorization: "***" }, body, params, }); try { const config: AxiosRequestConfig = { url, method, headers, params, }; if (method.toUpperCase() !== "GET" && body !== null) { config.data = body; } const response = await axios(config); return response.data; } catch (error) { if (axios.isAxiosError(error)) { if (error.response) { console.error("[DEBUG] Employee API Error Response", { status: error.response.status, data: error.response.data, }); } throw new Error(`Employee API request failed: ${error.message}`); } throw error; } } /** * Создание сотрудника * POST /employees */ async createEmployee(employee: CreateEmployeeDTO): Promise<unknown> { const context = ErrorHandler.createErrorContext( "createEmployee", `${this.baseUrl}/employees`, "POST", undefined, employee.companyId ); return ErrorHandler.executeWithResilience(async () => { return this.generatedApi.createEmployee(employee); }, context); } /** * Поиск сотрудников * GET /employees/search */ async searchEmployees(searchParams: EmployeeSearch): Promise<unknown> { const params: EmployeeSearchParams = { lastName: searchParams.lastName, firstName: searchParams.firstName, email: searchParams.email, }; const context = ErrorHandler.createErrorContext("searchEmployees", `${this.baseUrl}/employees/search`, "GET"); return ErrorHandler.executeWithResilience(async () => { return this.generatedApi.searchEmployees(params); }, context); } /** * Получение сотрудника по ID * GET /employees/{id} */ async getEmployee(id: string): Promise<unknown> { return this.generatedApi.getEmployee(id); } /** * Удаление сотрудника * DELETE /employees/{id} */ async deleteEmployee(id: string): Promise<unknown> { return this.generatedApi.deleteEmployee(id); } /** * Отправка приглашения * POST /invites/send */ async sendInvite(invite: SendInviteDTO): Promise<unknown> { // Для invites может быть другой endpoint const inviteUrl = this.baseUrl.replace("/employees/", "/invites/"); const url = `${inviteUrl}send`; const headers: Record<string, string> = { Authorization: this.apiKey, Accept: "application/json", "Content-Type": "application/json", }; console.log("[DEBUG] Invite API Request", { method: "POST", url, headers: { ...headers, Authorization: "***" }, body: invite, }); try { const response = await axios.post(url, invite, { headers }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { if (error.response) { console.error("[DEBUG] Invite API Error Response", { status: error.response.status, data: error.response.data, }); } throw new Error(`Invite API request failed: ${error.message}`); } throw error; } } /** * Проверка уникальности сотрудника * Использует search для проверки существования */ async checkEmployeeUniqueness( lastName: string, firstName: string, email?: string ): Promise<{ found: boolean; employees: unknown[]; message?: string; exactMatch?: boolean; similarMatches?: unknown[]; conflictType?: "exact" | "similar" | "email" | "none"; }> { try { // Поиск по точному совпадению ФИО const exactSearchParams: EmployeeSearch = { lastName, firstName, ...(email && { email }), }; const exactResult = await this.searchEmployees(exactSearchParams); const exactEmployees = Array.isArray(exactResult) ? exactResult : (exactResult as { employees?: unknown[] })?.employees || []; // Если найдено точное совпадение if (exactEmployees.length > 0) { return { found: true, employees: exactEmployees, exactMatch: true, conflictType: "exact", message: `Найдено точное совпадение: ${exactEmployees.length} сотрудник(ов) с такими ФИО${email ? " и email" : ""}`, }; } // Поиск по email (если указан) if (email) { const emailSearchParams: EmployeeSearch = { email }; const emailResult = await this.searchEmployees(emailSearchParams); const emailEmployees = Array.isArray(emailResult) ? emailResult : (emailResult as { employees?: unknown[] })?.employees || []; if (emailEmployees.length > 0) { return { found: true, employees: emailEmployees, exactMatch: false, similarMatches: emailEmployees, conflictType: "email", message: `Найден сотрудник с таким же email: ${email}`, }; } } // Поиск похожих сотрудников (только по фамилии) const similarSearchParams: EmployeeSearch = { lastName }; const similarResult = await this.searchEmployees(similarSearchParams); const similarEmployees = Array.isArray(similarResult) ? similarResult : (similarResult as { employees?: unknown[] })?.employees || []; if (similarEmployees.length > 0) { return { found: false, employees: [], exactMatch: false, similarMatches: similarEmployees, conflictType: "similar", message: `Найдено ${similarEmployees.length} сотрудник(ов) с такой же фамилией. Проверьте, не является ли это дубликатом.`, }; } return { found: false, employees: [], exactMatch: false, conflictType: "none", message: "Сотрудник не найден, можно создавать", }; } catch (error) { console.error("[ERROR] Failed to check employee uniqueness", error); return { found: false, employees: [], message: "Ошибка при проверке уникальности", conflictType: "none", }; } } /** * Обработка конфликта при создании сотрудника * Предлагает варианты разрешения конфликта */ async handleEmployeeConflict( createDto: CreateEmployeeDTO, conflictInfo: { found: boolean; employees: unknown[]; conflictType?: "exact" | "similar" | "email" | "none"; message?: string; } ): Promise<{ action: "create" | "update" | "skip" | "manual"; message: string; suggestedActions?: string[]; }> { if (!conflictInfo.found || conflictInfo.conflictType === "none") { return { action: "create", message: "Конфликтов не найдено, создаём нового сотрудника", }; } switch (conflictInfo.conflictType) { case "exact": return { action: "manual", message: `⚠️ Найден точный дубликат: ${conflictInfo.message}`, suggestedActions: [ "1. Обновить существующую запись", "2. Создать новую запись (возможно, это другой человек)", "3. Пропустить добавление", ], }; case "email": return { action: "manual", message: `⚠️ Конфликт email: ${conflictInfo.message}`, suggestedActions: [ "1. Обновить существующую запись с новым email", "2. Создать новую запись с другим email", "3. Пропустить добавление", ], }; case "similar": return { action: "manual", message: `⚠️ Возможный дубликат: ${conflictInfo.message}`, suggestedActions: [ "1. Проверить, не является ли это дубликатом", "2. Создать новую запись (разные люди)", "3. Пропустить добавление", ], }; default: return { action: "create", message: "Создаём нового сотрудника", }; } } /** * Принудительное создание сотрудника (игнорируя конфликты) */ async createEmployeeForce(createDto: CreateEmployeeDTO): Promise<unknown> { const context = ErrorHandler.createErrorContext( "createEmployeeForce", `${this.baseUrl}/employees`, "POST", undefined, createDto.companyId ); return ErrorHandler.executeWithResilience( async () => { const url = `${this.baseUrl}/employees`; const headers = { Authorization: this.apiKey, Accept: "application/json", "Content-Type": "application/json", }; console.log("[DEBUG] Force Create Employee Request", { method: "POST", url, headers: { ...headers, Authorization: "***" }, body: createDto, }); const response = await axios.post(url, createDto, { headers }); return response.data; }, context, { maxRetries: 2, // Меньше попыток для принудительного создания baseDelayMs: 1000, } ); } /** * Обновление существующего сотрудника */ async updateEmployee(employeeId: string, updateDto: UpdateEmployeeDTO): Promise<unknown> { const context = ErrorHandler.createErrorContext("updateEmployee", `${this.baseUrl}/employees/${employeeId}`, "PUT"); return ErrorHandler.executeWithResilience(async () => { const url = `${this.baseUrl}/employees/${employeeId}`; const headers = { Authorization: this.apiKey, Accept: "application/json", "Content-Type": "application/json", }; console.log("[DEBUG] Update Employee Request", { method: "PUT", url, headers: { ...headers, Authorization: "***" }, body: updateDto, }); const response = await axios.put(url, updateDto, { headers }); return response.data; }, context); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/inite-ai/radius-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server