refactor/adds-result-type-to-other-features #2

Merged
HideyoshiNakazone merged 3 commits from refactor/adds-result-type-to-other-features into main 2026-04-11 05:07:54 +00:00
4 changed files with 274 additions and 282 deletions
Showing only changes of commit 3ea1112369 - Show all commits

View File

@@ -11,92 +11,88 @@ import { getSessionData } from '@/lib/session/session-storage';
import { TypedResult, wrap } from '@/utils/types/results'; import { TypedResult, wrap } from '@/utils/types/results';
import { UUIDv4 } from '@/utils/types/uuid'; import { UUIDv4 } from '@/utils/types/uuid';
const _getArticleByExternalId = async ( export const getArticleByExternalId: (
externalId: UUIDv4 externalId: UUIDv4
): Promise<ArticleModel | null> => { ) => Promise<TypedResult<ArticleModel | null>> = wrap(
async (externalId: UUIDv4): Promise<ArticleModel | null> => {
const result = await service.getArticleByExternalId(externalId); const result = await service.getArticleByExternalId(externalId);
if (!result.ok) throw result.error; if (!result.ok) throw result.error;
return result.value; return result.value;
}; }
);
const _getArticleBySlug = async ( export const getArticleBySlug: (
slug: string slug: string
): Promise<ArticleModel | null> => { ) => Promise<TypedResult<ArticleModel | null>> = wrap(
async (slug: string): Promise<ArticleModel | null> => {
const result = await service.getArticleBySlug(slug); const result = await service.getArticleBySlug(slug);
if (!result.ok) throw result.error; if (!result.ok) throw result.error;
return result.value; return result.value;
}; }
);
const _getArticlesPaginated = async ( export const getArticlesPaginated: (
page?: number,
pageSize?: number
) => Promise<TypedResult<PaginatedArticlesResult>> = wrap(
async (
page: number = 1, page: number = 1,
pageSize: number = 10 pageSize: number = 10
): Promise<PaginatedArticlesResult> => { ): Promise<PaginatedArticlesResult> => {
const result = await service.getArticlesPaginated(page, pageSize); const result = await service.getArticlesPaginated(page, pageSize);
if (!result.ok) throw result.error; if (!result.ok) throw result.error;
return result.value; return result.value;
}; }
);
const _saveArticle = async ( export const saveArticle: (
article: CreateArticleModel article: CreateArticleModel
): Promise<ArticleModel> => { ) => Promise<TypedResult<ArticleModel>> = wrap(
async (article: CreateArticleModel): Promise<ArticleModel> => {
const session = await getSessionData(); const session = await getSessionData();
if (!session || !session?.user || session?.user.role !== 'admin') { if (!session || !session?.user || session?.user.role !== 'admin') {
throw new Error('Unauthorized: Only admin users can save articles.'); throw new Error(
'Unauthorized: Only admin users can save articles.'
);
} }
article.authorId = session.user.id; article.authorId = session.user.id;
const result = await service.saveArticle(article); const result = await service.saveArticle(article);
if (!result.ok) throw result.error; if (!result.ok) throw result.error;
return result.value; return result.value;
}; }
);
const _updateArticle = async ( export const updateArticle: (
articleId: string,
article: UpdateArticleModel
) => Promise<TypedResult<ArticleModel>> = wrap(
async (
articleId: string, articleId: string,
article: UpdateArticleModel article: UpdateArticleModel
): Promise<ArticleModel> => { ): Promise<ArticleModel> => {
const session = await getSessionData(); const session = await getSessionData();
if (!session || !session?.user || session?.user.role !== 'admin') { if (!session || !session?.user || session?.user.role !== 'admin') {
throw new Error('Unauthorized: Only admin users can save articles.'); throw new Error(
'Unauthorized: Only admin users can save articles.'
);
} }
const result = await service.updateArticle(articleId, article); const result = await service.updateArticle(articleId, article);
if (!result.ok) throw result.error; if (!result.ok) throw result.error;
return result.value; return result.value;
}; }
);
const _deleteArticle = async (articleId: string): Promise<void> => { export const deleteArticle: (articleId: string) => Promise<TypedResult<void>> =
wrap(async (articleId: string): Promise<void> => {
const session = await getSessionData(); const session = await getSessionData();
if (!session || !session?.user || session?.user.role !== 'admin') { if (!session || !session?.user || session?.user.role !== 'admin') {
throw new Error('Unauthorized: Only admin users can delete articles.'); throw new Error(
'Unauthorized: Only admin users can delete articles.'
);
} }
const result = await service.deleteArticle(articleId); const result = await service.deleteArticle(articleId);
if (!result.ok) throw result.error; if (!result.ok) throw result.error;
}; });
export const getArticleByExternalId: (
externalId: UUIDv4
) => Promise<TypedResult<ArticleModel | null>> = wrap(_getArticleByExternalId);
export const getArticleBySlug: (
slug: string
) => Promise<TypedResult<ArticleModel | null>> = wrap(_getArticleBySlug);
export const getArticlesPaginated: (
page?: number,
pageSize?: number
) => Promise<TypedResult<PaginatedArticlesResult>> = wrap(
_getArticlesPaginated
);
export const saveArticle: (
article: CreateArticleModel
) => Promise<TypedResult<ArticleModel>> = wrap(_saveArticle);
export const updateArticle: (
articleId: string,
article: UpdateArticleModel
) => Promise<TypedResult<ArticleModel>> = wrap(_updateArticle);
export const deleteArticle: (articleId: string) => Promise<TypedResult<void>> =
wrap(_deleteArticle);

View File

@@ -26,9 +26,11 @@ export const articleEntityToModel = (
}; };
}; };
const _getArticleByExternalId = async ( /** Retrieves an article by its external ID. */
export const getArticleByExternalId: (
externalId: UUIDv4 externalId: UUIDv4
): Promise<ArticleModel | null> => { ) => Promise<TypedResult<ArticleModel | null>> = wrap(
async (externalId: UUIDv4): Promise<ArticleModel | null> => {
const articleRepository = await getRepository(ArticleEntity); const articleRepository = await getRepository(ArticleEntity);
const articleEntity = await articleRepository.findOneBy({ const articleEntity = await articleRepository.findOneBy({
@@ -40,11 +42,14 @@ const _getArticleByExternalId = async (
} }
return articleEntityToModel(articleEntity); return articleEntityToModel(articleEntity);
}; }
);
const _getArticleBySlug = async ( /** Retrieves an article by its slug. */
export const getArticleBySlug: (
slug: string slug: string
): Promise<ArticleModel | null> => { ) => Promise<TypedResult<ArticleModel | null>> = wrap(
async (slug: string): Promise<ArticleModel | null> => {
const articleRepository = await getRepository(ArticleEntity); const articleRepository = await getRepository(ArticleEntity);
const articleEntity = await articleRepository.findOneBy({ slug }); const articleEntity = await articleRepository.findOneBy({ slug });
@@ -54,19 +59,28 @@ const _getArticleBySlug = async (
} }
return articleEntityToModel(articleEntity); return articleEntityToModel(articleEntity);
}; }
);
const _getArticlesByAuthorId = async ( /** Retrieves all articles by a given author ID. */
export const getArticlesByAuthorId: (
authorId: string authorId: string
): Promise<ArticleModel[]> => { ) => Promise<TypedResult<ArticleModel[]>> = wrap(
async (authorId: string): Promise<ArticleModel[]> => {
const articleRepository = await getRepository(ArticleEntity); const articleRepository = await getRepository(ArticleEntity);
const articleEntities = await articleRepository.findBy({ authorId }); const articleEntities = await articleRepository.findBy({ authorId });
return articleEntities.map(articleEntityToModel); return articleEntities.map(articleEntityToModel);
}; }
);
const _getArticlesPaginated = async ( /** 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, page: number = 1,
pageSize: number = 10 pageSize: number = 10
): Promise<PaginatedArticlesResult> => { ): Promise<PaginatedArticlesResult> => {
@@ -85,11 +99,14 @@ const _getArticlesPaginated = async (
pageSize, pageSize,
totalPages: Math.ceil(total / pageSize), totalPages: Math.ceil(total / pageSize),
}; };
}; }
);
const _saveArticle = async ( /** Saves a new article to the database. */
export const saveArticle: (
article: CreateArticleModel article: CreateArticleModel
): Promise<ArticleModel> => { ) => Promise<TypedResult<ArticleModel>> = wrap(
async (article: CreateArticleModel): Promise<ArticleModel> => {
const articleRepository = await getRepository(ArticleEntity); const articleRepository = await getRepository(ArticleEntity);
if (!article.authorId) { if (!article.authorId) {
@@ -102,9 +119,15 @@ const _saveArticle = async (
const newArticle = articleRepository.create(article); const newArticle = articleRepository.create(article);
return articleEntityToModel(await articleRepository.save(newArticle)); return articleEntityToModel(await articleRepository.save(newArticle));
}; }
);
const _updateArticle = async ( /** Updates an existing article in the database. */
export const updateArticle: (
articleId: string,
article: UpdateArticleModel
) => Promise<TypedResult<ArticleModel>> = wrap(
async (
articleId: string, articleId: string,
article: UpdateArticleModel article: UpdateArticleModel
): Promise<ArticleModel> => { ): Promise<ArticleModel> => {
@@ -125,10 +148,15 @@ const _updateArticle = async (
existingArticle.coverImageUrl = article.coverImageUrl; existingArticle.coverImageUrl = article.coverImageUrl;
if (!!article.content) existingArticle.content = article.content; if (!!article.content) existingArticle.content = article.content;
return articleEntityToModel(await articleRepository.save(existingArticle)); return articleEntityToModel(
}; await articleRepository.save(existingArticle)
);
}
);
const _deleteArticle = async (articleId: string): Promise<void> => { /** Deletes an article from the database. */
export const deleteArticle: (articleId: string) => Promise<TypedResult<void>> =
wrap(async (articleId: string): Promise<void> => {
const articleRepository = await getRepository(ArticleEntity); const articleRepository = await getRepository(ArticleEntity);
const existingArticle = await articleRepository.findOneBy({ const existingArticle = await articleRepository.findOneBy({
@@ -139,42 +167,4 @@ const _deleteArticle = async (articleId: string): Promise<void> => {
} }
await articleRepository.remove(existingArticle); await articleRepository.remove(existingArticle);
}; });
/** Retrieves an article by its external ID. */
export const getArticleByExternalId: (
externalId: UUIDv4
) => Promise<TypedResult<ArticleModel | null>> = wrap(_getArticleByExternalId);
/** Retrieves an article by its slug. */
export const getArticleBySlug: (
slug: string
) => Promise<TypedResult<ArticleModel | null>> = wrap(_getArticleBySlug);
/** Retrieves all articles by a given author ID. */
export const getArticlesByAuthorId: (
authorId: string
) => Promise<TypedResult<ArticleModel[]>> = wrap(_getArticlesByAuthorId);
/** Retrieves a paginated list of articles ordered by creation date descending. */
export const getArticlesPaginated: (
page?: number,
pageSize?: number
) => Promise<TypedResult<PaginatedArticlesResult>> = wrap(
_getArticlesPaginated
);
/** Saves a new article to the database. */
export const saveArticle: (
article: CreateArticleModel
) => Promise<TypedResult<ArticleModel>> = wrap(_saveArticle);
/** Updates an existing article in the database. */
export const updateArticle: (
articleId: string,
article: UpdateArticleModel
) => Promise<TypedResult<ArticleModel>> = wrap(_updateArticle);
/** Deletes an article from the database. */
export const deleteArticle: (articleId: string) => Promise<TypedResult<void>> =
wrap(_deleteArticle);

View File

@@ -8,9 +8,10 @@ import { getSessionData } from '@/lib/session/session-storage';
import { TypedResult, wrap } from '@/utils/types/results'; import { TypedResult, wrap } from '@/utils/types/results';
import { UUIDv4 } from '@/utils/types/uuid'; import { UUIDv4 } from '@/utils/types/uuid';
const _getUserByExternalId = async ( export const getUserByExternalId: (
externalId: UUIDv4 externalId: UUIDv4
): Promise<UserModel | null> => { ) => Promise<TypedResult<UserModel | null>> = wrap(
async (externalId: UUIDv4): Promise<UserModel | null> => {
const sessionData = await getSessionData(); const sessionData = await getSessionData();
if ( if (
!sessionData || !sessionData ||
@@ -31,8 +32,5 @@ const _getUserByExternalId = async (
} }
return userEntityToModel(userEntity); return userEntityToModel(userEntity);
}; }
);
export const getUserByExternalId: (
externalId: UUIDv4
) => Promise<TypedResult<UserModel | null>> = wrap(_getUserByExternalId);

View File

@@ -18,7 +18,13 @@ export const userEntityToModel = (userEntity: UserEntity): UserModel => {
}; };
}; };
const _getUserByEmail = async (email: string): Promise<UserModel | null> => { /**
* Retrieves a user by their email address.
* @param email - The email address of the user to retrieve.
* @returns {Promise<TypedResult<UserModel | null>>} The user model if found, otherwise null.
*/
export const getUserByEmail = wrap(
async (email: string): Promise<UserModel | null> => {
const userRepository = await getRepository(UserEntity); const userRepository = await getRepository(UserEntity);
const userEntity = await userRepository.findOneBy({ email }); const userEntity = await userRepository.findOneBy({ email });
@@ -28,9 +34,16 @@ const _getUserByEmail = async (email: string): Promise<UserModel | null> => {
} }
return userEntityToModel(userEntity); return userEntityToModel(userEntity);
}; }
);
const _saveUser = async (user: CreateUserModel): Promise<UserModel> => { /**
* Saves a new user to the database.
* @param user - The user data to save.
* @returns {Promise<TypedResult<UserModel>>} The saved user model.
*/
export const saveUser = wrap(
async (user: CreateUserModel): Promise<UserModel> => {
const userRepository = await getRepository(UserEntity); const userRepository = await getRepository(UserEntity);
if (!!(await userRepository.findOneBy({ email: user.email }))) { if (!!(await userRepository.findOneBy({ email: user.email }))) {
@@ -39,12 +52,17 @@ const _saveUser = async (user: CreateUserModel): Promise<UserModel> => {
const newUser = userRepository.create(user); const newUser = userRepository.create(user);
return userEntityToModel(await userRepository.save(newUser)); return userEntityToModel(await userRepository.save(newUser));
}; }
);
const _updateUser = async ( /**
userId: string, * Updates an existing user in the database.
user: UpdateUserModel * @param userId - The ID of the user to update.
): Promise<UserModel> => { * @param user - The new user data.
* @returns {Promise<TypedResult<UserModel>>} The updated user model.
*/
export const updateUser = wrap(
async (userId: string, user: UpdateUserModel): Promise<UserModel> => {
const userRepository = await getRepository(UserEntity); const userRepository = await getRepository(UserEntity);
const existingUser = await userRepository.findOneBy({ id: userId }); const existingUser = await userRepository.findOneBy({ id: userId });
@@ -57,49 +75,8 @@ const _updateUser = async (
if (!!user.role) existingUser.role = user.role; if (!!user.role) existingUser.role = user.role;
return userEntityToModel(await userRepository.save(existingUser)); return userEntityToModel(await userRepository.save(existingUser));
};
const _syncUser = async (sessionClaims: SessionClaims): Promise<UserModel> => {
const { full_name, email } = sessionClaims.user;
const role = sessionClaims.user.public_metadata.role;
const existingUser = await _getUserByEmail(email);
if (!existingUser) {
return await _saveUser({
name: full_name,
email: email,
role: role,
});
} }
);
return await _updateUser(existingUser.id, {
name: full_name,
email: existingUser.email,
role: role,
});
};
/**
* Retrieves a user by their email address.
* @param email - The email address of the user to retrieve.
* @returns {Promise<TypedResult<UserModel | null>>} The user model if found, otherwise null.
*/
export const getUserByEmail = wrap(_getUserByEmail);
/**
* Saves a new user to the database.
* @param user - The user data to save.
* @returns {Promise<TypedResult<UserModel>>} The saved user model.
*/
export const saveUser = wrap(_saveUser);
/**
* Updates an existing user in the database.
* @param userId - The ID of the user to update.
* @param user - The new user data.
* @returns {Promise<TypedResult<UserModel>>} The updated user model.
*/
export const updateUser = wrap(_updateUser);
/** /**
* Synchronizes a user with the database. * Synchronizes a user with the database.
@@ -107,7 +84,38 @@ export const updateUser = wrap(_updateUser);
* @param sessionClaims Session Claims from the Auth Provider * @param sessionClaims Session Claims from the Auth Provider
* @returns {Promise<TypedResult<UserModel>>} The synchronized user model. * @returns {Promise<TypedResult<UserModel>>} The synchronized user model.
*/ */
export const syncUser = wrap(_syncUser); export const syncUser = wrap(
async (sessionClaims: SessionClaims): Promise<UserModel> => {
const { full_name, email } = sessionClaims.user;
const role = sessionClaims.user.public_metadata.role;
const existingUserResult = await getUserByEmail(email);
if (!existingUserResult.ok || !existingUserResult.value) {
const saveResult = await saveUser({
name: full_name,
email: email,
role: role,
});
if (!saveResult.ok) {
throw new Error(`User with email ${email} already exists`);
}
return saveResult.value;
}
const existingUser = existingUserResult.value;
const updateResult = await updateUser(existingUser.id, {
name: full_name,
email: existingUser.email,
role: role,
});
if (!updateResult.ok) {
throw new Error(
`Failed to update user with email ${email}: ${updateResult.error}`
);
}
return updateResult.value;
}
);
// Explicit re-export for TypeScript consumers who need the result type // Explicit re-export for TypeScript consumers who need the result type
export type { TypedResult }; export type { TypedResult };