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
JavaScript
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;