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 { TypedResult, wrap } from '@/utils/types/results'; 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, createdAt: articleEntity.createdAt, updatedAt: articleEntity.updatedAt, }; }; /** Retrieves an article by its external ID. */ export const getArticleByExternalId: ( externalId: UUIDv4 ) => Promise> = wrap( async (externalId: UUIDv4): Promise => { 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. */ export const getArticleBySlug: ( slug: string ) => Promise> = wrap( async (slug: string): Promise => { 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. */ export const getArticlesByAuthorId: ( authorId: string ) => Promise> = wrap( async (authorId: string): Promise => { 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. */ export const getArticlesPaginated: ( page?: number, pageSize?: number ) => Promise> = wrap( async ( page: number = 1, pageSize: number = 10 ): Promise => { 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. */ export const saveArticle: ( article: CreateArticleModel ) => Promise> = wrap( async (article: CreateArticleModel): Promise => { 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. */ export const updateArticle: ( articleId: string, article: UpdateArticleModel ) => Promise> = wrap( async ( articleId: string, article: UpdateArticleModel ): Promise => { 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. */ export const deleteArticle: (articleId: string) => Promise> = wrap(async (articleId: string): Promise => { 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); });