feat: implement article management features with CRUD operations
Some checks failed
Build and Test / run-test (20.x) (push) Has been cancelled
Some checks failed
Build and Test / run-test (20.x) (push) Has been cancelled
This commit is contained in:
177
src/lib/feature/article/article.service.ts
Normal file
177
src/lib/feature/article/article.service.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { getRepository } from '@/lib/db/client';
|
||||
import { ArticleEntity } from '@/lib/feature/article/article.entity';
|
||||
import {
|
||||
ArticleModel,
|
||||
CreateArticleModel,
|
||||
PaginatedArticlesResult,
|
||||
UpdateArticleModel,
|
||||
} from '@/lib/feature/article/article.model';
|
||||
import { UUIDv4 } from '@/utils/types/uuid';
|
||||
|
||||
export const articleEntityToModel = (
|
||||
articleEntity: ArticleEntity
|
||||
): ArticleModel => {
|
||||
return {
|
||||
id: articleEntity.id,
|
||||
title: articleEntity.title,
|
||||
slug: articleEntity.slug,
|
||||
description: articleEntity.description,
|
||||
coverImageUrl: articleEntity.coverImageUrl,
|
||||
content: articleEntity.content,
|
||||
authorId: articleEntity.authorId,
|
||||
externalId: articleEntity.externalId,
|
||||
};
|
||||
};
|
||||
|
||||
/** Retrieves an artible by its external ID.
|
||||
* @param externalId - The external ID of the article to retrieve.
|
||||
* @returns {Promise<ArticleModel | null>} The article model if found, otherwise null.
|
||||
* */
|
||||
export const getArticleByExternalId = async (
|
||||
externalId: UUIDv4
|
||||
): Promise<ArticleModel | null> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
const articleEntity = await articleRepository.findOneBy({
|
||||
externalId: externalId,
|
||||
});
|
||||
|
||||
if (!articleEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return articleEntityToModel(articleEntity);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves an article by its slug.
|
||||
* @param slug - The slug of the article to retrieve.
|
||||
* @returns {Promise<ArticleModel | null>} The article model if found, otherwise null.
|
||||
*/
|
||||
export const getArticleBySlug = async (
|
||||
slug: string
|
||||
): Promise<ArticleModel | null> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
const articleEntity = await articleRepository.findOneBy({ slug });
|
||||
|
||||
if (!articleEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return articleEntityToModel(articleEntity);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all articles by a given author ID.
|
||||
* @param authorId - The ID of the author.
|
||||
* @returns {Promise<ArticleModel[]>} A list of article models.
|
||||
*/
|
||||
export const getArticlesByAuthorId = async (
|
||||
authorId: string
|
||||
): Promise<ArticleModel[]> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
const articleEntities = await articleRepository.findBy({ authorId });
|
||||
|
||||
return articleEntities.map(articleEntityToModel);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of articles ordered by creation date descending.
|
||||
* @param page - The page number (1-based).
|
||||
* @param pageSize - The number of articles per page.
|
||||
* @returns {Promise<PaginatedArticlesResult>} The paginated result.
|
||||
*/
|
||||
export const getArticlesPaginated = async (
|
||||
page: number = 1,
|
||||
pageSize: number = 10
|
||||
): Promise<PaginatedArticlesResult> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
const [articleEntities, total] = await articleRepository.findAndCount({
|
||||
order: { createdAt: 'DESC' },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
});
|
||||
|
||||
return {
|
||||
data: articleEntities.map(articleEntityToModel),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves a new article to the database.
|
||||
* @param article - The article data to save.
|
||||
* @returns {Promise<ArticleModel>} The saved article model.
|
||||
* @throws {Error} If an article with the same slug already exists.
|
||||
*/
|
||||
export const saveArticle = async (
|
||||
article: CreateArticleModel
|
||||
): Promise<ArticleModel> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
if (!article.authorId) {
|
||||
throw new Error('Author ID is required to save an article');
|
||||
}
|
||||
|
||||
if (!!(await articleRepository.findOneBy({ slug: article.slug }))) {
|
||||
throw new Error(`Article with slug ${article.slug} already exists`);
|
||||
}
|
||||
|
||||
const newArticle = articleRepository.create(article);
|
||||
return articleEntityToModel(await articleRepository.save(newArticle));
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates an existing article in the database.
|
||||
* @param articleId - The ID of the article to update.
|
||||
* @param article - The new article data.
|
||||
* @returns {Promise<ArticleModel>} The updated article model.
|
||||
* @throws {Error} If the article with the given ID does not exist.
|
||||
*/
|
||||
export const updateArticle = async (
|
||||
articleId: string,
|
||||
article: UpdateArticleModel
|
||||
): Promise<ArticleModel> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
const existingArticle = await articleRepository.findOneBy({
|
||||
id: articleId,
|
||||
});
|
||||
if (!existingArticle) {
|
||||
throw new Error(`Article with ID ${articleId} not found`);
|
||||
}
|
||||
|
||||
if (!!article.title) existingArticle.title = article.title;
|
||||
if (!!article.slug) existingArticle.slug = article.slug;
|
||||
if (!!article.description)
|
||||
existingArticle.description = article.description;
|
||||
if (!!article.coverImageUrl)
|
||||
existingArticle.coverImageUrl = article.coverImageUrl;
|
||||
if (!!article.content) existingArticle.content = article.content;
|
||||
|
||||
return articleEntityToModel(await articleRepository.save(existingArticle));
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes an article from the database.
|
||||
* @param articleId - The ID of the article to delete.
|
||||
* @throws {Error} If the article with the given ID does not exist.
|
||||
*/
|
||||
export const deleteArticle = async (articleId: string): Promise<void> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
const existingArticle = await articleRepository.findOneBy({
|
||||
id: articleId,
|
||||
});
|
||||
if (!existingArticle) {
|
||||
throw new Error(`Article with ID ${articleId} not found`);
|
||||
}
|
||||
|
||||
await articleRepository.remove(existingArticle);
|
||||
};
|
||||
Reference in New Issue
Block a user