import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { HttpService } from '@nestjs/axios'; import { lastValueFrom } from 'rxjs'; import { ArticlesService } from './articles.service'; import { LiveBlogService } from './live-blog.service'; import { CreateArticleDto, CreateLiveBlogDto } from './articles.dto'; import { ArticleStatus, LiveBlogStatus } from './entities'; interface StrapiArticle { id: number; documentId: string; title: string; description: string; content: string; slug: string; publishedAt: string | null; createdAt: string; updatedAt: string; } interface StrapiLiveBlog { id: number; documentId: string; title: string; description: string; slug: string; status: 'draft' | 'live' | 'ended' | 'archived'; publishedAt: string | null; createdAt: string; updatedAt: string; } interface StrapiResponse { data: T; meta: { pagination: { page: number; pageSize: number; pageCount: number; total: number; }; }; } @Injectable() export class StrapiService { private readonly logger = new Logger(StrapiService.name); private readonly strapiUrl: string; private readonly strapiApiToken: string; constructor( private readonly configService: ConfigService, private readonly httpService: HttpService, private readonly articlesService: ArticlesService, private readonly liveBlogService: LiveBlogService, ) { this.strapiUrl = this.configService.get('STRAPI_URL') || 'http://localhost:1337'; this.strapiApiToken = this.configService.get('STRAPI_API_TOKEN') || ''; } private getHeaders() { const headers: Record = { 'Content-Type': 'application/json', }; if (this.strapiApiToken) { headers['Authorization'] = `Bearer ${this.strapiApiToken}`; } return headers; } async syncArticles(): Promise { try { this.logger.log('Starting articles sync from Strapi...'); const response = await lastValueFrom( this.httpService.get>( `${this.strapiUrl}/api/articles`, { headers: this.getHeaders(), }, ), ); const strapiArticles = response.data.data; let syncedCount = 0; for (const strapiArticle of strapiArticles) { const articleData: Partial = { title: strapiArticle.title, excerpt: strapiArticle.description, content: strapiArticle.content, slug: strapiArticle.slug, status: strapiArticle.publishedAt ? ArticleStatus.PUBLISHED : ArticleStatus.DRAFT, tags: [], }; await this.articlesService.syncFromStrapi( strapiArticle.documentId, articleData, ); syncedCount++; } this.logger.log( `Successfully synced ${syncedCount} articles from Strapi`, ); } catch (error) { this.logger.error('Failed to sync articles from Strapi', error); throw error; } } async syncSingleArticle(strapiId: string): Promise { try { this.logger.log(`Syncing single article from Strapi: ${strapiId}`); const response = await lastValueFrom( this.httpService.get>( `${this.strapiUrl}/api/articles/${strapiId}`, { headers: this.getHeaders(), }, ), ); const strapiArticle = response.data.data; const articleData: Partial = { title: strapiArticle.title, excerpt: strapiArticle.description, content: strapiArticle.content, slug: strapiArticle.slug, status: strapiArticle.publishedAt ? ArticleStatus.PUBLISHED : ArticleStatus.DRAFT, tags: [], }; await this.articlesService.syncFromStrapi( strapiArticle.documentId, articleData, ); this.logger.log(`Successfully synced article: ${strapiArticle.title}`); } catch (error) { this.logger.error( `Failed to sync article ${strapiId} from Strapi`, error, ); throw error; } } async syncLiveBlogs(): Promise { try { this.logger.log('Starting live blogs sync from Strapi...'); const response = await lastValueFrom( this.httpService.get>( `${this.strapiUrl}/api/live-blogs`, { headers: this.getHeaders(), }, ), ); const strapiLiveBlogs = response.data.data; let syncedCount = 0; for (const strapiLiveBlog of strapiLiveBlogs) { const liveBlogData: Partial = { title: strapiLiveBlog.title, description: strapiLiveBlog.description, slug: strapiLiveBlog.slug, status: this.mapStrapiStatusToLiveBlogStatus(strapiLiveBlog.status), }; await this.liveBlogService.syncFromStrapi( strapiLiveBlog.documentId, liveBlogData, ); syncedCount++; } this.logger.log( `Successfully synced ${syncedCount} live blogs from Strapi`, ); } catch (error) { this.logger.error('Failed to sync live blogs from Strapi', error); throw error; } } async syncSingleLiveBlog(strapiId: string): Promise { try { this.logger.log(`Syncing single live blog from Strapi: ${strapiId}`); const response = await lastValueFrom( this.httpService.get>( `${this.strapiUrl}/api/live-blogs/${strapiId}`, { headers: this.getHeaders(), }, ), ); const strapiLiveBlog = response.data.data; const liveBlogData: Partial = { title: strapiLiveBlog.title, description: strapiLiveBlog.description, slug: strapiLiveBlog.slug, status: this.mapStrapiStatusToLiveBlogStatus(strapiLiveBlog.status), }; await this.liveBlogService.syncFromStrapi( strapiLiveBlog.documentId, liveBlogData, ); this.logger.log(`Successfully synced live blog: ${strapiLiveBlog.title}`); } catch (error) { this.logger.error( `Failed to sync live blog ${strapiId} from Strapi`, error, ); throw error; } } private mapStrapiStatusToLiveBlogStatus(status: string): LiveBlogStatus { switch (status) { case 'live': return LiveBlogStatus.LIVE; case 'draft': return LiveBlogStatus.DRAFT; case 'ended': return LiveBlogStatus.ENDED; case 'archived': return LiveBlogStatus.ARCHIVED; default: return LiveBlogStatus.DRAFT; } } async handleWebhook( event: 'entry.create' | 'entry.update' | 'entry.delete', data: { documentId: string; model?: string }, ): Promise { this.logger.log( `Received webhook event: ${event} for model: ${data.model}`, ); if (event === 'entry.delete') { this.logger.log(`Handling delete for document: ${data.documentId}`); return; } // Route to appropriate sync method based on model if (data.model === 'article') { await this.syncSingleArticle(data.documentId); } else if (data.model === 'live-blog') { await this.syncSingleLiveBlog(data.documentId); } else { this.logger.warn(`Unknown model type in webhook: ${data.model}`); } } }