All checks were successful
Build and Test / run-test (20.x) (push) Successful in 2m5s
173 lines
5.5 KiB
TypeScript
173 lines
5.5 KiB
TypeScript
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 {
|
|
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<TypedResult<ArticleModel | null>> = wrap(
|
|
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. */
|
|
export const getArticleBySlug: (
|
|
slug: string
|
|
) => Promise<TypedResult<ArticleModel | null>> = wrap(
|
|
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. */
|
|
export const getArticlesByAuthorId: (
|
|
authorId: string
|
|
) => Promise<TypedResult<ArticleModel[]>> = wrap(
|
|
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. */
|
|
export const getArticlesPaginated: (
|
|
page?: number,
|
|
pageSize?: number
|
|
) => Promise<TypedResult<PaginatedArticlesResult>> = wrap(
|
|
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. */
|
|
export const saveArticle: (
|
|
article: CreateArticleModel
|
|
) => Promise<TypedResult<ArticleModel>> = wrap(
|
|
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. */
|
|
export const updateArticleByExternalId: (
|
|
externalId: string,
|
|
article: UpdateArticleModel
|
|
) => Promise<TypedResult<ArticleModel>> = wrap(
|
|
async (
|
|
externalId: string,
|
|
article: UpdateArticleModel
|
|
): Promise<ArticleModel> => {
|
|
const articleRepository = await getRepository(ArticleEntity);
|
|
|
|
const existingArticle = await articleRepository.findOneBy({
|
|
externalId: externalId,
|
|
});
|
|
if (!existingArticle) {
|
|
throw new Error(`Article with ID ${externalId} 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 deleteArticleByExternalId: (
|
|
externalId: UUIDv4
|
|
) => Promise<TypedResult<void>> = wrap(
|
|
async (externalId: UUIDv4): Promise<void> => {
|
|
const articleRepository = await getRepository(ArticleEntity);
|
|
|
|
const existingArticle = await articleRepository.findOneBy({
|
|
externalId: externalId,
|
|
});
|
|
if (!existingArticle) {
|
|
throw new Error(`Article with ExternalID ${externalId} not found`);
|
|
}
|
|
|
|
await articleRepository.remove(existingArticle);
|
|
}
|
|
);
|