UNPKG

iluria-sdk

Version:

SDK oficial do Iluria para integração com lojas e criação de frontends customizados

327 lines (282 loc) 9.14 kB
const GraphQLQueryBuilder = require('./graphql-builder'); const CacheManager = require('./cache'); const { NoOpCache } = require('./cache'); /** * Blog module - Gerencia posts de blog via GraphQL */ class Blog { constructor(httpClient, cacheManager = null, config = {}) { this.http = httpClient; this.config = config; // Usa o cache manager fornecido ou cria um padrão if (cacheManager === false) { this.cache = new NoOpCache(); } else if (cacheManager) { this.cache = cacheManager; } else { // Mantém compatibilidade: cria cache simples se não fornecido this.cache = new CacheManager({ maxMemorySize: 50, ttl: 60000, storage: false // Apenas memória por padrão }); } // TTL padrão this.cacheTTL = 60000; // 1 minute cache if (this.config.debug) { console.log('[Blog] Initialized with cache:', !!this.cache); } } /** * Lista posts de blog com filtros e campos customizados * @param {object} options - Opções de filtro (limit, offset, search, categoryId) * @param {Array<string>} fields - Campos a buscar para cada post * @returns {object} - Lista de posts com paginação */ async list(options = {}, fields = null) { // Type safety validation if (options && typeof options !== 'object') { throw new Error('Blog.list: options must be an object'); } if (fields && !Array.isArray(fields)) { throw new Error('Blog.list: fields must be an array of strings'); } // Campos padrão se não especificados if (!fields) { fields = [ 'id', 'title', 'slug', 'excerpt', 'photos.originalUrl', 'photos.urls.small', 'photos.urls.medium', 'photos.urls.large', 'photos.urls.xlarge', 'publishedAt', 'createdAt', 'updatedAt', 'category.id', 'category.name' ]; } if (this.config.debug) { console.log('[Blog] Listing posts with options:', options); console.log('[Blog] Requested fields:', fields); } // Constrói query GraphQL const queryBuilder = new GraphQLQueryBuilder(); // Adiciona campos dos posts fields.forEach(field => { queryBuilder.addField(`blogPosts.${field}`, { filters: options }); }); const query = queryBuilder.buildQuery(); const variables = queryBuilder.getVariables(); if (this.config.debug) { console.log('[Blog] GraphQL query built'); } try { const data = await this.http.graphql(query, variables); if (this.config.debug) { console.log('[Blog] Posts fetched successfully:', data.blogPosts?.items?.length || 0); } return data.blogPosts; } catch (error) { console.error('[Blog] Error fetching blog posts:', error); throw error; } } /** * Busca um post específico por slug * @param {string} slug - Slug do post do blog * @param {Array<string>} fields - Campos a buscar * @returns {object} - Dados do post */ async get(slug, fields = null) { // Type safety validation if (!slug || typeof slug !== 'string') { throw new Error('Blog.get: slug must be a non-empty string'); } if (fields && !Array.isArray(fields)) { throw new Error('Blog.get: fields must be an array of strings'); } // Campos padrão se não especificados if (!fields) { fields = [ 'id', 'title', 'slug', 'content', 'htmlContent', 'excerpt', 'photos.originalUrl', 'photos.urls.small', 'photos.urls.medium', 'photos.urls.large', 'photos.urls.xlarge', 'photos.urls.xxlarge', 'photos.urls.huge', 'publishedAt', 'createdAt', 'updatedAt', 'metaTitle', 'metaDescription', 'category.id', 'category.name', 'category.slug', 'tags', 'readingTime', 'author.id', 'author.name' ]; } // Gera chave de cache const cacheKey = `blog:${slug}:${fields.join(',')}`; if (this.config.debug) { console.log(`[Blog] Getting post with slug: ${slug}`); } // Verifica cache primeiro if (this.cache) { const cached = await this.cache.get(cacheKey); if (cached) { if (this.config.debug) { console.log(`[Blog] Cache hit for slug: ${slug}`); } return cached; } } // Constrói query GraphQL const queryBuilder = new GraphQLQueryBuilder(); // Adiciona campos do post individual fields.forEach(field => { queryBuilder.addField(`blog.${field}`, { blogSlug: slug }); }); const query = queryBuilder.buildQuery(); const variables = queryBuilder.getVariables(); try { const data = await this.http.graphql(query, variables); // Extrai o post do alias gerado const alias = `blog_${slug.replace(/[^a-zA-Z0-9]/g, '_')}`; const post = data[alias]; if (!post) { if (this.config.debug) { console.log(`[Blog] Post not found for slug: ${slug}`); } throw new Error('Blog post not found'); } // Salva no cache if (this.cache) { await this.cache.set(cacheKey, post, this.cacheTTL); if (this.config.debug) { console.log(`[Blog] Post cached for slug: ${slug}`); } } if (this.config.debug) { console.log(`[Blog] Post fetched successfully for slug: ${slug}`); } return post; } catch (error) { console.error(`[Blog] Error fetching blog post ${slug}:`, error); throw error; } } /** * Busca múltiplos posts por slugs * @param {Array<string>} slugs - Array de slugs dos posts * @param {Array<string>} fields - Campos a buscar * @returns {Array<object>} - Array de posts */ async multiple(slugs, fields = null) { if (!Array.isArray(slugs) || slugs.length === 0) { return []; } // Campos padrão se não especificados if (!fields) { fields = [ 'id', 'title', 'slug', 'excerpt', 'photos.urls.medium', 'publishedAt', 'category.name' ]; } // Constrói query GraphQL para múltiplos posts const queryBuilder = new GraphQLQueryBuilder(); slugs.forEach(slug => { fields.forEach(field => { queryBuilder.addField(`blog.${field}`, { blogSlug: slug }); }); }); const query = queryBuilder.buildQuery(); const variables = queryBuilder.getVariables(); try { const data = await this.http.graphql(query, variables); // Extrai posts dos aliases gerados const posts = []; slugs.forEach(slug => { const alias = `blog_${slug.replace(/[^a-zA-Z0-9]/g, '_')}`; if (data[alias]) { posts.push(data[alias]); } }); return posts; } catch (error) { console.error('Error fetching multiple blog posts:', error); throw error; } } /** * Busca posts relacionados baseado em categoria ou tags * @param {string} slug - Slug do post de referência * @param {object} options - Opções (limit, fields) * @returns {Array<object>} - Posts relacionados */ async related(slug, options = {}) { const { limit = 5, fields = null } = options; try { // Primeiro busca o post atual para pegar categoria const currentPost = await this.get(slug, ['category.id', 'tags']); // Busca posts da mesma categoria, excluindo o atual const relatedOptions = { limit: limit + 1, // +1 para filtrar o atual depois categoryId: currentPost.category?.id }; const relatedPosts = await this.list(relatedOptions, fields); // Remove o post atual da lista return relatedPosts.items.filter(post => post.slug !== slug).slice(0, limit); } catch (error) { console.error(`Error fetching related posts for ${slug}:`, error); throw error; } } /** * Limpa o cache de blogs */ async clearCache() { if (this.cache && this.cache.clear) { await this.cache.clear(); } } /** * Invalida cache por padrão (ex: por slug ou categoria) * @param {string} pattern - Padrão para invalidar */ async invalidateCache(pattern) { if (this.cache && this.cache.invalidate) { return this.cache.invalidate(pattern); } return 0; } /** * Estatísticas do cache */ async getCacheStats() { if (!this.cache || !this.cache.getStats) { return { enabled: false }; } return this.cache.getStats(); } } module.exports = Blog;