270 lines
7.4 KiB
TypeScript
270 lines
7.4 KiB
TypeScript
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<T> {
|
|
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<string>('STRAPI_URL') || 'http://localhost:1337';
|
|
this.strapiApiToken =
|
|
this.configService.get<string>('STRAPI_API_TOKEN') || '';
|
|
}
|
|
|
|
private getHeaders() {
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
if (this.strapiApiToken) {
|
|
headers['Authorization'] = `Bearer ${this.strapiApiToken}`;
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
async syncArticles(): Promise<void> {
|
|
try {
|
|
this.logger.log('Starting articles sync from Strapi...');
|
|
|
|
const response = await lastValueFrom(
|
|
this.httpService.get<StrapiResponse<StrapiArticle[]>>(
|
|
`${this.strapiUrl}/api/articles`,
|
|
{
|
|
headers: this.getHeaders(),
|
|
},
|
|
),
|
|
);
|
|
|
|
const strapiArticles = response.data.data;
|
|
let syncedCount = 0;
|
|
|
|
for (const strapiArticle of strapiArticles) {
|
|
const articleData: Partial<CreateArticleDto> = {
|
|
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<void> {
|
|
try {
|
|
this.logger.log(`Syncing single article from Strapi: ${strapiId}`);
|
|
|
|
const response = await lastValueFrom(
|
|
this.httpService.get<StrapiResponse<StrapiArticle>>(
|
|
`${this.strapiUrl}/api/articles/${strapiId}`,
|
|
{
|
|
headers: this.getHeaders(),
|
|
},
|
|
),
|
|
);
|
|
|
|
const strapiArticle = response.data.data;
|
|
const articleData: Partial<CreateArticleDto> = {
|
|
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<void> {
|
|
try {
|
|
this.logger.log('Starting live blogs sync from Strapi...');
|
|
|
|
const response = await lastValueFrom(
|
|
this.httpService.get<StrapiResponse<StrapiLiveBlog[]>>(
|
|
`${this.strapiUrl}/api/live-blogs`,
|
|
{
|
|
headers: this.getHeaders(),
|
|
},
|
|
),
|
|
);
|
|
|
|
const strapiLiveBlogs = response.data.data;
|
|
let syncedCount = 0;
|
|
|
|
for (const strapiLiveBlog of strapiLiveBlogs) {
|
|
const liveBlogData: Partial<CreateLiveBlogDto> = {
|
|
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<void> {
|
|
try {
|
|
this.logger.log(`Syncing single live blog from Strapi: ${strapiId}`);
|
|
|
|
const response = await lastValueFrom(
|
|
this.httpService.get<StrapiResponse<StrapiLiveBlog>>(
|
|
`${this.strapiUrl}/api/live-blogs/${strapiId}`,
|
|
{
|
|
headers: this.getHeaders(),
|
|
},
|
|
),
|
|
);
|
|
|
|
const strapiLiveBlog = response.data.data;
|
|
const liveBlogData: Partial<CreateLiveBlogDto> = {
|
|
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<void> {
|
|
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}`);
|
|
}
|
|
}
|
|
}
|