feat: refactor article and user service functions to use TypedResult for enhanced error handling
This commit is contained in:
@@ -11,92 +11,88 @@ import { getSessionData } from '@/lib/session/session-storage';
|
||||
import { TypedResult, wrap } from '@/utils/types/results';
|
||||
import { UUIDv4 } from '@/utils/types/uuid';
|
||||
|
||||
const _getArticleByExternalId = async (
|
||||
externalId: UUIDv4
|
||||
): Promise<ArticleModel | null> => {
|
||||
const result = await service.getArticleByExternalId(externalId);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
};
|
||||
|
||||
const _getArticleBySlug = async (
|
||||
slug: string
|
||||
): Promise<ArticleModel | null> => {
|
||||
const result = await service.getArticleBySlug(slug);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
};
|
||||
|
||||
const _getArticlesPaginated = async (
|
||||
page: number = 1,
|
||||
pageSize: number = 10
|
||||
): Promise<PaginatedArticlesResult> => {
|
||||
const result = await service.getArticlesPaginated(page, pageSize);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
};
|
||||
|
||||
const _saveArticle = async (
|
||||
article: CreateArticleModel
|
||||
): Promise<ArticleModel> => {
|
||||
const session = await getSessionData();
|
||||
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||
throw new Error('Unauthorized: Only admin users can save articles.');
|
||||
}
|
||||
article.authorId = session.user.id;
|
||||
|
||||
const result = await service.saveArticle(article);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
};
|
||||
|
||||
const _updateArticle = async (
|
||||
articleId: string,
|
||||
article: UpdateArticleModel
|
||||
): Promise<ArticleModel> => {
|
||||
const session = await getSessionData();
|
||||
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||
throw new Error('Unauthorized: Only admin users can save articles.');
|
||||
}
|
||||
|
||||
const result = await service.updateArticle(articleId, article);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
};
|
||||
|
||||
const _deleteArticle = async (articleId: string): Promise<void> => {
|
||||
const session = await getSessionData();
|
||||
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||
throw new Error('Unauthorized: Only admin users can delete articles.');
|
||||
}
|
||||
|
||||
const result = await service.deleteArticle(articleId);
|
||||
if (!result.ok) throw result.error;
|
||||
};
|
||||
|
||||
export const getArticleByExternalId: (
|
||||
externalId: UUIDv4
|
||||
) => Promise<TypedResult<ArticleModel | null>> = wrap(_getArticleByExternalId);
|
||||
) => Promise<TypedResult<ArticleModel | null>> = wrap(
|
||||
async (externalId: UUIDv4): Promise<ArticleModel | null> => {
|
||||
const result = await service.getArticleByExternalId(externalId);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
}
|
||||
);
|
||||
|
||||
export const getArticleBySlug: (
|
||||
slug: string
|
||||
) => Promise<TypedResult<ArticleModel | null>> = wrap(_getArticleBySlug);
|
||||
) => Promise<TypedResult<ArticleModel | null>> = wrap(
|
||||
async (slug: string): Promise<ArticleModel | null> => {
|
||||
const result = await service.getArticleBySlug(slug);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
}
|
||||
);
|
||||
|
||||
export const getArticlesPaginated: (
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
) => Promise<TypedResult<PaginatedArticlesResult>> = wrap(
|
||||
_getArticlesPaginated
|
||||
async (
|
||||
page: number = 1,
|
||||
pageSize: number = 10
|
||||
): Promise<PaginatedArticlesResult> => {
|
||||
const result = await service.getArticlesPaginated(page, pageSize);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
}
|
||||
);
|
||||
|
||||
export const saveArticle: (
|
||||
article: CreateArticleModel
|
||||
) => Promise<TypedResult<ArticleModel>> = wrap(_saveArticle);
|
||||
) => Promise<TypedResult<ArticleModel>> = wrap(
|
||||
async (article: CreateArticleModel): Promise<ArticleModel> => {
|
||||
const session = await getSessionData();
|
||||
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||
throw new Error(
|
||||
'Unauthorized: Only admin users can save articles.'
|
||||
);
|
||||
}
|
||||
article.authorId = session.user.id;
|
||||
|
||||
const result = await service.saveArticle(article);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
}
|
||||
);
|
||||
|
||||
export const updateArticle: (
|
||||
articleId: string,
|
||||
article: UpdateArticleModel
|
||||
) => Promise<TypedResult<ArticleModel>> = wrap(_updateArticle);
|
||||
) => Promise<TypedResult<ArticleModel>> = wrap(
|
||||
async (
|
||||
articleId: string,
|
||||
article: UpdateArticleModel
|
||||
): Promise<ArticleModel> => {
|
||||
const session = await getSessionData();
|
||||
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||
throw new Error(
|
||||
'Unauthorized: Only admin users can save articles.'
|
||||
);
|
||||
}
|
||||
|
||||
const result = await service.updateArticle(articleId, article);
|
||||
if (!result.ok) throw result.error;
|
||||
return result.value;
|
||||
}
|
||||
);
|
||||
|
||||
export const deleteArticle: (articleId: string) => Promise<TypedResult<void>> =
|
||||
wrap(_deleteArticle);
|
||||
wrap(async (articleId: string): Promise<void> => {
|
||||
const session = await getSessionData();
|
||||
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||
throw new Error(
|
||||
'Unauthorized: Only admin users can delete articles.'
|
||||
);
|
||||
}
|
||||
|
||||
const result = await service.deleteArticle(articleId);
|
||||
if (!result.ok) throw result.error;
|
||||
});
|
||||
|
||||
@@ -26,155 +26,145 @@ export const articleEntityToModel = (
|
||||
};
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
const _getArticlesByAuthorId = async (
|
||||
authorId: string
|
||||
): Promise<ArticleModel[]> => {
|
||||
const articleRepository = await getRepository(ArticleEntity);
|
||||
|
||||
const articleEntities = await articleRepository.findBy({ authorId });
|
||||
|
||||
return articleEntities.map(articleEntityToModel);
|
||||
};
|
||||
|
||||
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),
|
||||
};
|
||||
};
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
/** Retrieves an article by its external ID. */
|
||||
export const getArticleByExternalId: (
|
||||
externalId: UUIDv4
|
||||
) => Promise<TypedResult<ArticleModel | null>> = wrap(_getArticleByExternalId);
|
||||
) => 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(_getArticleBySlug);
|
||||
) => 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(_getArticlesByAuthorId);
|
||||
) => 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(
|
||||
_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. */
|
||||
export const saveArticle: (
|
||||
article: CreateArticleModel
|
||||
) => Promise<TypedResult<ArticleModel>> = wrap(_saveArticle);
|
||||
) => 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 updateArticle: (
|
||||
articleId: string,
|
||||
article: UpdateArticleModel
|
||||
) => Promise<TypedResult<ArticleModel>> = wrap(_updateArticle);
|
||||
) => Promise<TypedResult<ArticleModel>> = wrap(
|
||||
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. */
|
||||
export const deleteArticle: (articleId: string) => Promise<TypedResult<void>> =
|
||||
wrap(_deleteArticle);
|
||||
wrap(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