@tiflux/mcp
Version:
TiFlux MCP Server - Model Context Protocol integration for Claude Code and other AI clients
393 lines (323 loc) • 9.89 kB
JavaScript
/**
* ClientRepository - Acesso a dados para clientes
*
* Responsabilidades:
* - Abstrai a API do TiFlux para operações de cliente
* - Implementa busca por nome com paginação
* - Normaliza respostas da API
* - Error handling específico para clientes
*/
class ClientRepository {
constructor(container) {
this.container = container;
this.logger = container.resolve('logger');
this.httpClient = container.resolve('tifluxHttpClient');
this.config = container.resolve('config');
this.clientMapper = null; // Lazy loading
}
/**
* Busca clientes por nome
*/
async searchByName(clientName) {
const timer = this.logger.startTimer(`repo_search_clients`);
try {
this.logger.debug('Repository: searching clients by name', {
clientName: clientName?.substring(0, 30)
});
// Constrói query parameters para busca
const queryParams = new URLSearchParams({
client_name: clientName,
// Pode incluir outros parâmetros de busca se a API suportar
// per_page: '50',
// active: 'true'
});
const response = await this.httpClient.get(`/clients/search?${queryParams}`, {
timeout: 20000, // Timeout específico para busca
maxRetries: 2
});
timer();
// Valida resposta da busca
if (response.statusCode >= 400) {
const errorMessage = this._extractAPIErrorMessage(response.data);
throw new APIError(
`Falha ao buscar clientes: ${errorMessage}`,
response.statusCode,
response.data
);
}
// Mapeia resultados
const clients = this._getClientMapper().mapSearchResultsFromAPI(response.data);
this.logger.info('Repository: clients search completed', {
searchTerm: clientName?.substring(0, 30),
resultCount: clients.length
});
return clients;
} catch (error) {
timer();
this.logger.error('Repository: failed to search clients', {
clientName: clientName?.substring(0, 30),
error: error.message,
statusCode: error.statusCode
});
if (error instanceof APIError) {
throw error;
}
throw new APIError(
`Falha ao buscar clientes por nome "${clientName}": ${error.message}`,
error.statusCode || 500,
{ originalError: error.message }
);
}
}
/**
* Busca cliente por ID
*/
async getById(clientId) {
const timer = this.logger.startTimer(`repo_get_client_${clientId}`);
try {
this.logger.debug('Repository: fetching client by ID', { clientId });
const response = await this.httpClient.get(`/clients/${clientId}`, {
timeout: 15000,
maxRetries: 2
});
timer();
// Verifica se cliente foi encontrado
if (response.statusCode === 404) {
return null;
}
// Valida resposta da API
if (response.statusCode >= 400) {
const errorMessage = this._extractAPIErrorMessage(response.data);
throw new APIError(
`Falha ao buscar cliente #${clientId}: ${errorMessage}`,
response.statusCode,
response.data
);
}
// Mapeia cliente
const client = this._getClientMapper().mapFromAPI(response.data);
this.logger.debug('Repository: client fetched successfully', {
clientId,
clientName: client.name?.substring(0, 50)
});
return client;
} catch (error) {
timer();
this.logger.error('Repository: failed to get client', {
clientId,
error: error.message,
statusCode: error.statusCode
});
if (error instanceof APIError) {
throw error;
}
throw new APIError(
`Falha ao buscar cliente #${clientId}: ${error.message}`,
error.statusCode || 500,
{ originalError: error.message }
);
}
}
/**
* Lista clientes com paginação (se necessário)
*/
async list(options = {}) {
const timer = this.logger.startTimer('repo_list_clients');
try {
this.logger.debug('Repository: listing clients', { options });
const queryParams = new URLSearchParams();
if (options.page) {
queryParams.set('page', options.page.toString());
}
if (options.per_page) {
queryParams.set('per_page', Math.min(options.per_page, 200).toString());
}
if (options.active !== undefined) {
queryParams.set('active', options.active.toString());
}
const endpoint = queryParams.toString() ?
`/clients?${queryParams}` :
'/clients';
const response = await this.httpClient.get(endpoint, {
timeout: 25000,
maxRetries: 2
});
timer();
// Valida resposta da listagem
if (response.statusCode >= 400) {
const errorMessage = this._extractAPIErrorMessage(response.data);
throw new APIError(
`Falha ao listar clientes: ${errorMessage}`,
response.statusCode,
response.data
);
}
// Mapeia lista de clientes
const clientList = this._getClientMapper().mapListFromAPI(response.data);
this.logger.info('Repository: clients listed successfully', {
count: clientList.clients?.length || 0
});
return clientList;
} catch (error) {
timer();
this.logger.error('Repository: failed to list clients', {
options,
error: error.message,
statusCode: error.statusCode
});
if (error instanceof APIError) {
throw error;
}
throw new APIError(
`Falha ao listar clientes: ${error.message}`,
error.statusCode || 500,
{ originalError: error.message }
);
}
}
/**
* Verifica se um cliente existe
*/
async exists(clientId) {
try {
const client = await this.getById(clientId);
return client !== null;
} catch (error) {
if (error instanceof APIError && error.statusCode === 404) {
return false;
}
throw error;
}
}
/**
* Busca clientes por múltiplos critérios (se a API suportar)
*/
async searchByCriteria(criteria = {}) {
const timer = this.logger.startTimer('repo_search_clients_criteria');
try {
this.logger.debug('Repository: searching clients by criteria', {
criteriaKeys: Object.keys(criteria)
});
const queryParams = new URLSearchParams();
// Adiciona critérios de busca suportados
if (criteria.name) {
queryParams.set('name', criteria.name);
}
if (criteria.email) {
queryParams.set('email', criteria.email);
}
if (criteria.document) {
queryParams.set('document', criteria.document);
}
if (criteria.phone) {
queryParams.set('phone', criteria.phone);
}
if (criteria.active !== undefined) {
queryParams.set('active', criteria.active.toString());
}
const response = await this.httpClient.get(`/clients/search?${queryParams}`, {
timeout: 20000,
maxRetries: 2
});
timer();
// Valida resposta
if (response.statusCode >= 400) {
const errorMessage = this._extractAPIErrorMessage(response.data);
throw new APIError(
`Falha na busca por critérios: ${errorMessage}`,
response.statusCode,
response.data
);
}
// Mapeia resultados
const clients = this._getClientMapper().mapSearchResultsFromAPI(response.data);
this.logger.info('Repository: criteria search completed', {
criteriaKeys: Object.keys(criteria),
resultCount: clients.length
});
return clients;
} catch (error) {
timer();
this.logger.error('Repository: failed to search clients by criteria', {
criteria,
error: error.message,
statusCode: error.statusCode
});
if (error instanceof APIError) {
throw error;
}
throw new APIError(
`Falha na busca por critérios: ${error.message}`,
error.statusCode || 500,
{ originalError: error.message }
);
}
}
/**
* Extrai mensagem de erro da resposta da API
*/
_extractAPIErrorMessage(errorData) {
if (!errorData) {
return 'Erro desconhecido';
}
if (typeof errorData === 'string') {
return errorData;
}
// Tenta diferentes formatos de erro da API TiFlux
if (errorData.message) {
return errorData.message;
}
if (errorData.error) {
if (typeof errorData.error === 'string') {
return errorData.error;
}
if (errorData.error.message) {
return errorData.error.message;
}
}
if (errorData.errors && Array.isArray(errorData.errors)) {
return errorData.errors.join(', ');
}
if (errorData.details) {
return errorData.details;
}
return JSON.stringify(errorData).substring(0, 200);
}
/**
* Lazy loading do ClientMapper
*/
_getClientMapper() {
if (!this.clientMapper) {
// Importa aqui para evitar dependência circular
const ClientMapper = require('./ClientMapper');
this.clientMapper = new ClientMapper(this.container);
}
return this.clientMapper;
}
/**
* Estatísticas do repository
*/
getStats() {
return {
endpoints: {
search: '/clients/search',
get_by_id: '/clients/:id',
list: '/clients'
},
features: {
search_by_name: true,
search_by_criteria: true,
pagination: true,
active_filter: true
},
timeouts: {
search: '20s',
get_by_id: '15s',
list: '25s'
}
};
}
}
// Import das classes de erro
const { APIError } = require('../../utils/errors');
module.exports = ClientRepository;