sync working as intended
This commit is contained in:
1 parent
9ca05f5ea1
commit
7378d37b36
10 files changed
+503
-57
No files matched your search
@@ -1,7 +1,14 @@
|
||||
import { Injectable, NotFoundException, Logger } from '@nestjs/common';
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
Logger,
|
||||
Inject,
|
||||
forwardRef,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Article, ArticleStatus } from './entities';
|
||||
import { StrapiService } from './strapi.service';
|
||||
import {
|
||||
CreateArticleDto,
|
||||
UpdateArticleDto,
|
||||
@@ -15,6 +22,8 @@ export class ArticlesService {
|
||||
constructor(
|
||||
@InjectRepository(Article)
|
||||
private readonly articleRepository: Repository<Article>,
|
||||
@Inject(forwardRef(() => StrapiService))
|
||||
private readonly strapiService: StrapiService,
|
||||
) {}
|
||||
|
||||
async create(dto: CreateArticleDto): Promise<Article> {
|
||||
@@ -102,44 +111,173 @@ export class ArticlesService {
|
||||
|
||||
async update(id: string, dto: UpdateArticleDto): Promise<Article> {
|
||||
const article = await this.findOne(id);
|
||||
const oldStatus = article.status;
|
||||
Object.assign(article, dto);
|
||||
return await this.articleRepository.save(article);
|
||||
const savedArticle = await this.articleRepository.save(article);
|
||||
|
||||
// Update Strapi if status changed and article has strapiId
|
||||
if (
|
||||
dto.status !== undefined &&
|
||||
dto.status !== oldStatus &&
|
||||
article.strapiId
|
||||
) {
|
||||
try {
|
||||
await this.strapiService.updateArticleStatusInStrapi(
|
||||
article.strapiId,
|
||||
dto.status,
|
||||
);
|
||||
} catch (error) {
|
||||
// Log error but don't fail - backend status is updated
|
||||
this.logger.error(
|
||||
`Failed to update Strapi status for article ${id}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return savedArticle;
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const article = await this.findOne(id);
|
||||
|
||||
// Delete from Strapi if article has strapiId
|
||||
if (article.strapiId) {
|
||||
try {
|
||||
await this.strapiService.deleteArticleFromStrapi(article.strapiId);
|
||||
} catch (error) {
|
||||
// Log error but don't fail - we still want to delete from backend
|
||||
this.logger.error(`Failed to delete article ${id} from Strapi:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
await this.articleRepository.remove(article);
|
||||
}
|
||||
|
||||
async archive(id: string): Promise<Article> {
|
||||
const article = await this.findOne(id);
|
||||
article.status = ArticleStatus.ARCHIVED;
|
||||
return await this.articleRepository.save(article);
|
||||
const savedArticle = await this.articleRepository.save(article);
|
||||
|
||||
// Update Strapi if article has strapiId
|
||||
if (article.strapiId) {
|
||||
try {
|
||||
await this.strapiService.updateArticleStatusInStrapi(
|
||||
article.strapiId,
|
||||
ArticleStatus.ARCHIVED,
|
||||
);
|
||||
} catch (error) {
|
||||
// Log error but don't fail - backend status is updated
|
||||
this.logger.error(
|
||||
`Failed to update Strapi status for article ${id}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return savedArticle;
|
||||
}
|
||||
|
||||
async publish(id: string, status: ArticleStatus = ArticleStatus.PUBLISHED): Promise<Article> {
|
||||
async publish(
|
||||
id: string,
|
||||
status: ArticleStatus = ArticleStatus.PUBLISHED,
|
||||
): Promise<Article> {
|
||||
const article = await this.findOne(id);
|
||||
article.status = status;
|
||||
return await this.articleRepository.save(article);
|
||||
const savedArticle = await this.articleRepository.save(article);
|
||||
|
||||
// Update Strapi if article has strapiId
|
||||
if (article.strapiId) {
|
||||
try {
|
||||
await this.strapiService.updateArticleStatusInStrapi(
|
||||
article.strapiId,
|
||||
status,
|
||||
);
|
||||
} catch (error) {
|
||||
// Log error but don't fail - backend status is updated
|
||||
this.logger.error(
|
||||
`Failed to update Strapi status for article ${id}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return savedArticle;
|
||||
}
|
||||
|
||||
async syncFromStrapi(
|
||||
strapiId: string,
|
||||
data: Partial<CreateArticleDto>,
|
||||
): Promise<Article> {
|
||||
let article = await this.articleRepository.findOne({ where: { strapiId } });
|
||||
|
||||
if (!article) {
|
||||
article = this.articleRepository.create({
|
||||
strapiId,
|
||||
...data,
|
||||
status: data.status || ArticleStatus.DRAFT,
|
||||
// Use upsert to handle race conditions and ensure uniqueness
|
||||
try {
|
||||
// First try to find existing article by strapiId
|
||||
const article = await this.articleRepository.findOne({
|
||||
where: { strapiId },
|
||||
});
|
||||
} else {
|
||||
Object.assign(article, data);
|
||||
}
|
||||
|
||||
return await this.articleRepository.save(article);
|
||||
if (article) {
|
||||
// Update existing article
|
||||
const currentStatus = article.status;
|
||||
Object.assign(article, data);
|
||||
|
||||
// Preserve archived status if article is already archived in our system
|
||||
// unless Strapi explicitly sends a different status
|
||||
if (
|
||||
currentStatus === ArticleStatus.ARCHIVED &&
|
||||
data.status !== ArticleStatus.ARCHIVED
|
||||
) {
|
||||
article.status = ArticleStatus.ARCHIVED;
|
||||
}
|
||||
|
||||
return await this.articleRepository.save(article);
|
||||
} else {
|
||||
// Create new article
|
||||
const newArticle = this.articleRepository.create({
|
||||
strapiId,
|
||||
...data,
|
||||
status: data.status || ArticleStatus.DRAFT,
|
||||
});
|
||||
|
||||
return await this.articleRepository.save(newArticle);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// If we get a unique constraint violation, try to find and update again
|
||||
// This handles race conditions where two syncs happen simultaneously
|
||||
const dbError = error as { code?: string; constraint?: string };
|
||||
if (
|
||||
dbError.code === '23505' &&
|
||||
dbError.constraint?.includes('strapiId')
|
||||
) {
|
||||
this.logger.warn(
|
||||
`Race condition detected for strapiId ${strapiId}, retrying...`,
|
||||
);
|
||||
|
||||
// Wait a bit and retry
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const existingArticle = await this.articleRepository.findOne({
|
||||
where: { strapiId },
|
||||
});
|
||||
|
||||
if (existingArticle) {
|
||||
const currentStatus = existingArticle.status;
|
||||
Object.assign(existingArticle, data);
|
||||
|
||||
if (
|
||||
currentStatus === ArticleStatus.ARCHIVED &&
|
||||
data.status !== ArticleStatus.ARCHIVED
|
||||
) {
|
||||
existingArticle.status = ArticleStatus.ARCHIVED;
|
||||
}
|
||||
|
||||
return await this.articleRepository.save(existingArticle);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-throw other errors
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async removeByStrapiId(strapiId: string): Promise<void> {
|
||||
|
||||
Reference in new issue
Block a user