Compare commits
3 Commits
94e8058880
...
3addd38bba
| Author | SHA1 | Date | |
|---|---|---|---|
|
3addd38bba
|
|||
|
3ea1112369
|
|||
|
af17b6dc5a
|
@@ -70,7 +70,9 @@ const ArticleContentSkeleton = () => (
|
|||||||
|
|
||||||
const ArticleContent = async ({ params }: ArticleContentProps) => {
|
const ArticleContent = async ({ params }: ArticleContentProps) => {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const article = await getArticleBySlug(slug);
|
const articleResult = await getArticleBySlug(slug);
|
||||||
|
if (!articleResult.ok) throw articleResult.error;
|
||||||
|
const article = articleResult.value;
|
||||||
|
|
||||||
if (!article) notFound();
|
if (!article) notFound();
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,9 @@ const ArticleList = async ({ searchParams }: ArticleListProps) => {
|
|||||||
const page = Math.max(1, Number(pageParam) || 1);
|
const page = Math.max(1, Number(pageParam) || 1);
|
||||||
const pageSize = Number(pageSizeParam) || PAGE_SIZE;
|
const pageSize = Number(pageSizeParam) || PAGE_SIZE;
|
||||||
|
|
||||||
const {
|
const paginationResult = await getArticlesPaginated(page, pageSize);
|
||||||
data: articles,
|
if (!paginationResult.ok) throw paginationResult.error;
|
||||||
totalPages,
|
const { data: articles, totalPages, total } = paginationResult.value;
|
||||||
total,
|
|
||||||
} = await getArticlesPaginated(page, pageSize);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ export async function GET() {
|
|||||||
redirect('/');
|
redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncedUser = await syncUser(parsedClaims);
|
const syncResult = await syncUser(parsedClaims);
|
||||||
|
if (!syncResult.ok) {
|
||||||
await setSessionData('user', syncedUser);
|
console.error(syncResult.error);
|
||||||
|
redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
await setSessionData('user', syncResult.value);
|
||||||
redirect('/');
|
redirect('/');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,54 +8,91 @@ import {
|
|||||||
} from '@/lib/feature/article/article.model';
|
} from '@/lib/feature/article/article.model';
|
||||||
import * as service from '@/lib/feature/article/article.service';
|
import * as service from '@/lib/feature/article/article.service';
|
||||||
import { getSessionData } from '@/lib/session/session-storage';
|
import { getSessionData } from '@/lib/session/session-storage';
|
||||||
|
import { TypedResult, wrap } from '@/utils/types/results';
|
||||||
import { UUIDv4 } from '@/utils/types/uuid';
|
import { UUIDv4 } from '@/utils/types/uuid';
|
||||||
|
|
||||||
export const getArticleByExternalId = async (
|
export const getArticleByExternalId: (
|
||||||
externalId: UUIDv4
|
externalId: UUIDv4
|
||||||
): Promise<ArticleModel | null> => {
|
) => Promise<TypedResult<ArticleModel | null>> = wrap(
|
||||||
return await service.getArticleByExternalId(externalId);
|
async (externalId: UUIDv4): Promise<ArticleModel | null> => {
|
||||||
};
|
const result = await service.getArticleByExternalId(externalId);
|
||||||
|
if (!result.ok) throw result.error;
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const getArticleBySlug = async (
|
export const getArticleBySlug: (
|
||||||
slug: string
|
slug: string
|
||||||
): Promise<ArticleModel | null> => {
|
) => Promise<TypedResult<ArticleModel | null>> = wrap(
|
||||||
return await service.getArticleBySlug(slug);
|
async (slug: string): Promise<ArticleModel | null> => {
|
||||||
};
|
const result = await service.getArticleBySlug(slug);
|
||||||
|
if (!result.ok) throw result.error;
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export 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> => {
|
||||||
return await service.getArticlesPaginated(page, pageSize);
|
const result = await service.getArticlesPaginated(page, pageSize);
|
||||||
};
|
if (!result.ok) throw result.error;
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export 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;
|
||||||
|
|
||||||
return await service.saveArticle(article);
|
const result = await service.saveArticle(article);
|
||||||
};
|
if (!result.ok) throw result.error;
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export 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.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return await service.updateArticle(articleId, article);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteArticle = async (articleId: string): Promise<void> => {
|
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(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.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await service.deleteArticle(articleId);
|
|
||||||
};
|
const result = await service.deleteArticle(articleId);
|
||||||
|
if (!result.ok) throw result.error;
|
||||||
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
PaginatedArticlesResult,
|
PaginatedArticlesResult,
|
||||||
UpdateArticleModel,
|
UpdateArticleModel,
|
||||||
} from '@/lib/feature/article/article.model';
|
} from '@/lib/feature/article/article.model';
|
||||||
|
import { TypedResult, wrap } from '@/utils/types/results';
|
||||||
import { UUIDv4 } from '@/utils/types/uuid';
|
import { UUIDv4 } from '@/utils/types/uuid';
|
||||||
|
|
||||||
export const articleEntityToModel = (
|
export const articleEntityToModel = (
|
||||||
@@ -25,13 +26,11 @@ export const articleEntityToModel = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Retrieves an artible by its external ID.
|
/** Retrieves an article by its external ID. */
|
||||||
* @param externalId - The external ID of the article to retrieve.
|
export const getArticleByExternalId: (
|
||||||
* @returns {Promise<ArticleModel | null>} The article model if found, otherwise null.
|
|
||||||
* */
|
|
||||||
export const getArticleByExternalId = async (
|
|
||||||
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({
|
||||||
@@ -43,16 +42,14 @@ export const getArticleByExternalId = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return articleEntityToModel(articleEntity);
|
return articleEntityToModel(articleEntity);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/** Retrieves an article by its slug. */
|
||||||
* Retrieves an article by its slug.
|
export const getArticleBySlug: (
|
||||||
* @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
|
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 });
|
||||||
@@ -62,30 +59,28 @@ export const getArticleBySlug = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return articleEntityToModel(articleEntity);
|
return articleEntityToModel(articleEntity);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/** Retrieves all articles by a given author ID. */
|
||||||
* Retrieves all articles by a given author ID.
|
export const getArticlesByAuthorId: (
|
||||||
* @param authorId - The ID of the author.
|
|
||||||
* @returns {Promise<ArticleModel[]>} A list of article models.
|
|
||||||
*/
|
|
||||||
export const getArticlesByAuthorId = async (
|
|
||||||
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);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/** Retrieves a paginated list of articles ordered by creation date descending. */
|
||||||
* Retrieves a paginated list of articles ordered by creation date descending.
|
export const getArticlesPaginated: (
|
||||||
* @param page - The page number (1-based).
|
page?: number,
|
||||||
* @param pageSize - The number of articles per page.
|
pageSize?: number
|
||||||
* @returns {Promise<PaginatedArticlesResult>} The paginated result.
|
) => Promise<TypedResult<PaginatedArticlesResult>> = wrap(
|
||||||
*/
|
async (
|
||||||
export const getArticlesPaginated = async (
|
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 10
|
pageSize: number = 10
|
||||||
): Promise<PaginatedArticlesResult> => {
|
): Promise<PaginatedArticlesResult> => {
|
||||||
@@ -104,17 +99,14 @@ export const getArticlesPaginated = async (
|
|||||||
pageSize,
|
pageSize,
|
||||||
totalPages: Math.ceil(total / pageSize),
|
totalPages: Math.ceil(total / pageSize),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/** Saves a new article to the database. */
|
||||||
* Saves a new article to the database.
|
export const saveArticle: (
|
||||||
* @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
|
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) {
|
||||||
@@ -127,16 +119,15 @@ export 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));
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/** Updates an existing article in the database. */
|
||||||
* Updates an existing article in the database.
|
export const updateArticle: (
|
||||||
* @param articleId - The ID of the article to update.
|
articleId: string,
|
||||||
* @param article - The new article data.
|
article: UpdateArticleModel
|
||||||
* @returns {Promise<ArticleModel>} The updated article model.
|
) => Promise<TypedResult<ArticleModel>> = wrap(
|
||||||
* @throws {Error} If the article with the given ID does not exist.
|
async (
|
||||||
*/
|
|
||||||
export const updateArticle = async (
|
|
||||||
articleId: string,
|
articleId: string,
|
||||||
article: UpdateArticleModel
|
article: UpdateArticleModel
|
||||||
): Promise<ArticleModel> => {
|
): Promise<ArticleModel> => {
|
||||||
@@ -157,15 +148,15 @@ export 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/** Deletes an article from the database. */
|
||||||
* Deletes an article from the database.
|
export const deleteArticle: (articleId: string) => Promise<TypedResult<void>> =
|
||||||
* @param articleId - The ID of the article to delete.
|
wrap(async (articleId: string): Promise<void> => {
|
||||||
* @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 articleRepository = await getRepository(ArticleEntity);
|
||||||
|
|
||||||
const existingArticle = await articleRepository.findOneBy({
|
const existingArticle = await articleRepository.findOneBy({
|
||||||
@@ -176,4 +167,4 @@ export const deleteArticle = async (articleId: string): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await articleRepository.remove(existingArticle);
|
await articleRepository.remove(existingArticle);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
import { getRepository } from '@/lib/db/client';
|
import { getRepository } from '@/lib/db/client';
|
||||||
import { UserEntity } from '@/lib/db/entities';
|
import { UserEntity } from '@/lib/db/entities';
|
||||||
|
import { UserModel } from '@/lib/feature/user/user.model';
|
||||||
import { userEntityToModel } from '@/lib/feature/user/user.service';
|
import { userEntityToModel } from '@/lib/feature/user/user.service';
|
||||||
import { getSessionData } from '@/lib/session/session-storage';
|
import { getSessionData } from '@/lib/session/session-storage';
|
||||||
|
import { TypedResult, wrap } from '@/utils/types/results';
|
||||||
import { UUIDv4 } from '@/utils/types/uuid';
|
import { UUIDv4 } from '@/utils/types/uuid';
|
||||||
|
|
||||||
export const getUserByExternalId = async (externalId: UUIDv4) => {
|
export const getUserByExternalId: (
|
||||||
|
externalId: UUIDv4
|
||||||
|
) => Promise<TypedResult<UserModel | null>> = wrap(
|
||||||
|
async (externalId: UUIDv4): Promise<UserModel | null> => {
|
||||||
const sessionData = await getSessionData();
|
const sessionData = await getSessionData();
|
||||||
if (
|
if (
|
||||||
!sessionData ||
|
!sessionData ||
|
||||||
@@ -27,4 +32,5 @@ export const getUserByExternalId = async (externalId: UUIDv4) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return userEntityToModel(userEntity);
|
return userEntityToModel(userEntity);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
UpdateUserModel,
|
UpdateUserModel,
|
||||||
UserModel,
|
UserModel,
|
||||||
} from '@/lib/feature/user/user.model';
|
} from '@/lib/feature/user/user.model';
|
||||||
|
import { TypedResult, wrap } from '@/utils/types/results';
|
||||||
|
|
||||||
export const userEntityToModel = (userEntity: UserEntity): UserModel => {
|
export const userEntityToModel = (userEntity: UserEntity): UserModel => {
|
||||||
return {
|
return {
|
||||||
@@ -20,11 +21,10 @@ export const userEntityToModel = (userEntity: UserEntity): UserModel => {
|
|||||||
/**
|
/**
|
||||||
* Retrieves a user by their email address.
|
* Retrieves a user by their email address.
|
||||||
* @param email - The email address of the user to retrieve.
|
* @param email - The email address of the user to retrieve.
|
||||||
* @returns {Promise<UserModel | null>} The user model if found, otherwise null.
|
* @returns {Promise<TypedResult<UserModel | null>>} The user model if found, otherwise null.
|
||||||
*/
|
*/
|
||||||
export const getUserByEmail = async (
|
export const getUserByEmail = wrap(
|
||||||
email: string
|
async (email: string): Promise<UserModel | null> => {
|
||||||
): 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 });
|
||||||
@@ -34,15 +34,16 @@ export const getUserByEmail = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return userEntityToModel(userEntity);
|
return userEntityToModel(userEntity);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a new user to the database.
|
* Saves a new user to the database.
|
||||||
* @param user - The user data to save.
|
* @param user - The user data to save.
|
||||||
* @returns {Promise<UserModel>} The saved user model.
|
* @returns {Promise<TypedResult<UserModel>>} The saved user model.
|
||||||
* @throws {Error} If a user with the same email already exists.
|
|
||||||
*/
|
*/
|
||||||
export const saveUser = async (user: CreateUserModel): Promise<UserModel> => {
|
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 }))) {
|
||||||
@@ -51,19 +52,17 @@ export 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));
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates an existing user in the database.
|
* Updates an existing user in the database.
|
||||||
* @param userId - The ID of the user to update.
|
* @param userId - The ID of the user to update.
|
||||||
* @param user - The new user data.
|
* @param user - The new user data.
|
||||||
* @returns {Promise<UserModel>} The updated user model.
|
* @returns {Promise<TypedResult<UserModel>>} The updated user model.
|
||||||
* @throws {Error} If the user with the given ID does not exist.
|
|
||||||
*/
|
*/
|
||||||
export const updateUser = async (
|
export const updateUser = wrap(
|
||||||
userId: string,
|
async (userId: string, user: UpdateUserModel): Promise<UserModel> => {
|
||||||
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 });
|
||||||
@@ -76,35 +75,47 @@ export 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));
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes a user with the database.
|
* Synchronizes a user with the database.
|
||||||
* If the user already exists, it skips saving and returns the existing user.
|
* If the user already exists, updates it; if not, creates a new record.
|
||||||
* If the user does not exist, it creates a new user record.
|
|
||||||
* @returns {Promise<UserModel>} The synchronized user model.
|
|
||||||
* @throws {Error} If the user email is not provided or if there is an issue
|
|
||||||
* saving the user.
|
|
||||||
* @param sessionClaims Session Claims from the Auth Provider
|
* @param sessionClaims Session Claims from the Auth Provider
|
||||||
|
* @returns {Promise<TypedResult<UserModel>>} The synchronized user model.
|
||||||
*/
|
*/
|
||||||
export const syncUser = async (
|
export const syncUser = wrap(
|
||||||
sessionClaims: SessionClaims
|
async (sessionClaims: SessionClaims): Promise<UserModel> => {
|
||||||
): Promise<UserModel> => {
|
|
||||||
const { full_name, email } = sessionClaims.user;
|
const { full_name, email } = sessionClaims.user;
|
||||||
const role = sessionClaims.user.public_metadata.role;
|
const role = sessionClaims.user.public_metadata.role;
|
||||||
|
|
||||||
const existingUser = await getUserByEmail(email);
|
const existingUserResult = await getUserByEmail(email);
|
||||||
if (!existingUser) {
|
if (!existingUserResult.ok || !existingUserResult.value) {
|
||||||
return await saveUser({
|
const saveResult = await saveUser({
|
||||||
name: full_name,
|
name: full_name,
|
||||||
email: email,
|
email: email,
|
||||||
role: role,
|
role: role,
|
||||||
});
|
});
|
||||||
|
if (!saveResult.ok) {
|
||||||
|
throw new Error(`User with email ${email} already exists`);
|
||||||
}
|
}
|
||||||
|
return saveResult.value;
|
||||||
|
}
|
||||||
|
const existingUser = existingUserResult.value;
|
||||||
|
|
||||||
return await updateUser(existingUser.id, {
|
const updateResult = await updateUser(existingUser.id, {
|
||||||
name: full_name,
|
name: full_name,
|
||||||
email: existingUser.email,
|
email: existingUser.email,
|
||||||
role: role,
|
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
|
||||||
|
export type { TypedResult };
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
|
import { getSessionData } from '@/lib/session/session-storage';
|
||||||
import { createStorageProvider } from '@/lib/storage/storage.factory';
|
import { createStorageProvider } from '@/lib/storage/storage.factory';
|
||||||
import { StorageProvider } from '@/lib/storage/storage.interface';
|
import { StorageProvider } from '@/lib/storage/storage.interface';
|
||||||
import { TypedResult } from '@/utils/types/results';
|
import { TypedResult } from '@/utils/types/results';
|
||||||
@@ -13,6 +14,10 @@ export const getSignedUrl = async (
|
|||||||
if (!storageProvider) {
|
if (!storageProvider) {
|
||||||
storageProvider = storage;
|
storageProvider = storage;
|
||||||
}
|
}
|
||||||
|
const session = await getSessionData();
|
||||||
|
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||||
|
throw new Error('Unauthorized: Only admin users can delete articles.');
|
||||||
|
}
|
||||||
return await storageProvider.get(key);
|
return await storageProvider.get(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,6 +28,10 @@ export const checkExists = async (
|
|||||||
if (!storageProvider) {
|
if (!storageProvider) {
|
||||||
storageProvider = storage;
|
storageProvider = storage;
|
||||||
}
|
}
|
||||||
|
const session = await getSessionData();
|
||||||
|
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||||
|
throw new Error('Unauthorized: Only admin users can delete articles.');
|
||||||
|
}
|
||||||
return await storageProvider.exists(key);
|
return await storageProvider.exists(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,6 +43,10 @@ export const getPutUrl = async (
|
|||||||
if (!storageProvider) {
|
if (!storageProvider) {
|
||||||
storageProvider = storage;
|
storageProvider = storage;
|
||||||
}
|
}
|
||||||
|
const session = await getSessionData();
|
||||||
|
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||||
|
throw new Error('Unauthorized: Only admin users can delete articles.');
|
||||||
|
}
|
||||||
return await storageProvider.put(key, contentType);
|
return await storageProvider.put(key, contentType);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,5 +57,9 @@ export const deleteByKey = async (
|
|||||||
if (!storageProvider) {
|
if (!storageProvider) {
|
||||||
storageProvider = storage;
|
storageProvider = storage;
|
||||||
}
|
}
|
||||||
|
const session = await getSessionData();
|
||||||
|
if (!session || !session?.user || session?.user.role !== 'admin') {
|
||||||
|
throw new Error('Unauthorized: Only admin users can delete articles.');
|
||||||
|
}
|
||||||
return await storageProvider.delete(key);
|
return await storageProvider.delete(key);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,23 +101,20 @@ export const CreateArticleForm = () => {
|
|||||||
|
|
||||||
const handleFormSubmit = useCallback(
|
const handleFormSubmit = useCallback(
|
||||||
async (data: z.infer<typeof formSchema>) => {
|
async (data: z.infer<typeof formSchema>) => {
|
||||||
try {
|
|
||||||
const result = await saveArticle({ ...data });
|
const result = await saveArticle({ ...data });
|
||||||
|
if (!result.ok) {
|
||||||
|
toast.error('Failed to create article', {
|
||||||
|
description: result.error.message,
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
toast.success('Article created successfully!', {
|
toast.success('Article created successfully!', {
|
||||||
description: `Article "${result.title}" has been created.`,
|
description: `Article "${result.value.title}" has been created.`,
|
||||||
position: 'bottom-right',
|
position: 'bottom-right',
|
||||||
});
|
});
|
||||||
form.reset();
|
form.reset();
|
||||||
resetFiles();
|
resetFiles();
|
||||||
} catch (error) {
|
|
||||||
toast.error('Failed to create article', {
|
|
||||||
description:
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: 'An error occurred',
|
|
||||||
position: 'bottom-right',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[form, resetFiles]
|
[form, resetFiles]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ describe('ArticleService', () => {
|
|||||||
role: 'admin',
|
role: 'admin',
|
||||||
};
|
};
|
||||||
const savedAuthor = await saveUser(author);
|
const savedAuthor = await saveUser(author);
|
||||||
authorId = savedAuthor.id;
|
if (!savedAuthor.ok) throw savedAuthor.error;
|
||||||
|
authorId = savedAuthor.value.id;
|
||||||
}, 1_000_000);
|
}, 1_000_000);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -51,16 +52,18 @@ describe('ArticleService', () => {
|
|||||||
authorId: authorId,
|
authorId: authorId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedArticle = await saveArticle(articleToSave);
|
const result = await saveArticle(articleToSave);
|
||||||
|
|
||||||
expect(savedArticle.id).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(savedArticle.title).toBe(articleToSave.title);
|
if (!result.ok) return;
|
||||||
expect(savedArticle.slug).toBe(articleToSave.slug);
|
expect(result.value.id).toBeDefined();
|
||||||
expect(savedArticle.description).toBe(articleToSave.description);
|
expect(result.value.title).toBe(articleToSave.title);
|
||||||
expect(savedArticle.coverImageUrl).toBe(articleToSave.coverImageUrl);
|
expect(result.value.slug).toBe(articleToSave.slug);
|
||||||
expect(savedArticle.content).toBe(articleToSave.content);
|
expect(result.value.description).toBe(articleToSave.description);
|
||||||
expect(savedArticle.authorId).toBe(authorId);
|
expect(result.value.coverImageUrl).toBe(articleToSave.coverImageUrl);
|
||||||
expect(savedArticle.externalId).toBeDefined();
|
expect(result.value.content).toBe(articleToSave.content);
|
||||||
|
expect(result.value.authorId).toBe(authorId);
|
||||||
|
expect(result.value.externalId).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot save article with existing slug', async () => {
|
test('cannot save article with existing slug', async () => {
|
||||||
@@ -72,77 +75,99 @@ describe('ArticleService', () => {
|
|||||||
content: 'Duplicate content.',
|
content: 'Duplicate content.',
|
||||||
authorId: authorId,
|
authorId: authorId,
|
||||||
};
|
};
|
||||||
await expect(saveArticle(articleToSave)).rejects.toThrow(
|
|
||||||
`Article with slug ${articleToSave.slug} already exists`
|
const result = await saveArticle(articleToSave);
|
||||||
);
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) return;
|
||||||
|
expect(result.error.message).toContain(articleToSave.slug);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can getArticleBySlug', async () => {
|
test('can getArticleBySlug', async () => {
|
||||||
const article = await getArticleBySlug('test-article');
|
const result = await getArticleBySlug('test-article');
|
||||||
|
|
||||||
expect(article).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(article?.slug).toBe('test-article');
|
if (!result.ok) return;
|
||||||
expect(article?.title).toBe('Test Article');
|
expect(result.value).toBeDefined();
|
||||||
expect(article?.authorId).toBe(authorId);
|
expect(result.value?.slug).toBe('test-article');
|
||||||
expect(article?.externalId).toBeDefined();
|
expect(result.value?.title).toBe('Test Article');
|
||||||
|
expect(result.value?.authorId).toBe(authorId);
|
||||||
|
expect(result.value?.externalId).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot getArticleBySlug with non-existing slug', async () => {
|
test('cannot getArticleBySlug with non-existing slug', async () => {
|
||||||
await expect(getArticleBySlug('non-existing-slug')).resolves.toBeNull();
|
const result = await getArticleBySlug('non-existing-slug');
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
if (!result.ok) return;
|
||||||
|
expect(result.value).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can getArticlesByAuthorId', async () => {
|
test('can getArticlesByAuthorId', async () => {
|
||||||
const articles = await getArticlesByAuthorId(authorId);
|
const result = await getArticlesByAuthorId(authorId);
|
||||||
|
|
||||||
expect(articles).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(articles.length).toBeGreaterThanOrEqual(1);
|
if (!result.ok) return;
|
||||||
expect(articles[0].authorId).toBe(authorId);
|
expect(result.value).toBeDefined();
|
||||||
|
expect(result.value.length).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(result.value[0].authorId).toBe(authorId);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getArticlesByAuthorId returns empty for non-existing author', async () => {
|
test('getArticlesByAuthorId returns empty for non-existing author', async () => {
|
||||||
const articles = await getArticlesByAuthorId('9999');
|
const result = await getArticlesByAuthorId('9999');
|
||||||
|
|
||||||
expect(articles).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(articles.length).toBe(0);
|
if (!result.ok) return;
|
||||||
|
expect(result.value).toBeDefined();
|
||||||
|
expect(result.value.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can getArticleByExternalId', async () => {
|
test('can getArticleByExternalId', async () => {
|
||||||
const article = await getArticleBySlug('test-article');
|
const slugResult = await getArticleBySlug('test-article');
|
||||||
|
expect(slugResult.ok).toBe(true);
|
||||||
|
if (!slugResult.ok) return;
|
||||||
|
const article = slugResult.value;
|
||||||
expect(article).toBeDefined();
|
expect(article).toBeDefined();
|
||||||
|
|
||||||
const foundArticle = await getArticleByExternalId(
|
const result = await getArticleByExternalId(
|
||||||
article!.externalId as UUIDv4
|
article!.externalId as UUIDv4
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(foundArticle).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(foundArticle?.id).toBe(article!.id);
|
if (!result.ok) return;
|
||||||
expect(foundArticle?.title).toBe(article!.title);
|
expect(result.value).toBeDefined();
|
||||||
expect(foundArticle?.slug).toBe(article!.slug);
|
expect(result.value?.id).toBe(article!.id);
|
||||||
expect(foundArticle?.externalId).toBe(article!.externalId);
|
expect(result.value?.title).toBe(article!.title);
|
||||||
|
expect(result.value?.slug).toBe(article!.slug);
|
||||||
|
expect(result.value?.externalId).toBe(article!.externalId);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getArticleByExternalId returns null for non-existing id', async () => {
|
test('getArticleByExternalId returns null for non-existing id', async () => {
|
||||||
const result = await getArticleByExternalId(
|
const result = await getArticleByExternalId(
|
||||||
'00000000-0000-4000-a000-000000000000' as UUIDv4
|
'00000000-0000-4000-a000-000000000000' as UUIDv4
|
||||||
);
|
);
|
||||||
expect(result).toBeNull();
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
if (!result.ok) return;
|
||||||
|
expect(result.value).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can getArticlesPaginated with defaults', async () => {
|
test('can getArticlesPaginated with defaults', async () => {
|
||||||
const result = await getArticlesPaginated();
|
const result = await getArticlesPaginated();
|
||||||
|
|
||||||
expect(result).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(result.data.length).toBeGreaterThanOrEqual(1);
|
if (!result.ok) return;
|
||||||
expect(result.page).toBe(1);
|
expect(result.value.data.length).toBeGreaterThanOrEqual(1);
|
||||||
expect(result.pageSize).toBe(10);
|
expect(result.value.page).toBe(1);
|
||||||
expect(result.total).toBeGreaterThanOrEqual(1);
|
expect(result.value.pageSize).toBe(10);
|
||||||
expect(result.totalPages).toBeGreaterThanOrEqual(1);
|
expect(result.value.total).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(result.value.totalPages).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can getArticlesPaginated with custom page size', async () => {
|
test('can getArticlesPaginated with custom page size', async () => {
|
||||||
// Save extra articles to test pagination
|
// Save extra articles to test pagination
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
await saveArticle({
|
const r = await saveArticle({
|
||||||
title: `Paginated Article ${i}`,
|
title: `Paginated Article ${i}`,
|
||||||
slug: `paginated-article-${i}`,
|
slug: `paginated-article-${i}`,
|
||||||
description: `Description ${i}`,
|
description: `Description ${i}`,
|
||||||
@@ -150,29 +175,36 @@ describe('ArticleService', () => {
|
|||||||
content: `Content ${i}`,
|
content: `Content ${i}`,
|
||||||
authorId: authorId,
|
authorId: authorId,
|
||||||
});
|
});
|
||||||
|
if (!r.ok) throw r.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstPage = await getArticlesPaginated(1, 2);
|
const firstPageResult = await getArticlesPaginated(1, 2);
|
||||||
|
|
||||||
expect(firstPage.data.length).toBe(2);
|
expect(firstPageResult.ok).toBe(true);
|
||||||
expect(firstPage.page).toBe(1);
|
if (!firstPageResult.ok) return;
|
||||||
expect(firstPage.pageSize).toBe(2);
|
expect(firstPageResult.value.data.length).toBe(2);
|
||||||
expect(firstPage.total).toBeGreaterThanOrEqual(4);
|
expect(firstPageResult.value.page).toBe(1);
|
||||||
expect(firstPage.totalPages).toBeGreaterThanOrEqual(2);
|
expect(firstPageResult.value.pageSize).toBe(2);
|
||||||
|
expect(firstPageResult.value.total).toBeGreaterThanOrEqual(4);
|
||||||
|
expect(firstPageResult.value.totalPages).toBeGreaterThanOrEqual(2);
|
||||||
|
|
||||||
const secondPage = await getArticlesPaginated(2, 2);
|
const secondPageResult = await getArticlesPaginated(2, 2);
|
||||||
|
|
||||||
expect(secondPage.data.length).toBe(2);
|
expect(secondPageResult.ok).toBe(true);
|
||||||
expect(secondPage.page).toBe(2);
|
if (!secondPageResult.ok) return;
|
||||||
expect(secondPage.pageSize).toBe(2);
|
expect(secondPageResult.value.data.length).toBe(2);
|
||||||
|
expect(secondPageResult.value.page).toBe(2);
|
||||||
|
expect(secondPageResult.value.pageSize).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can getArticlesPaginated returns empty on out-of-range page', async () => {
|
test('can getArticlesPaginated returns empty on out-of-range page', async () => {
|
||||||
const result = await getArticlesPaginated(999, 10);
|
const result = await getArticlesPaginated(999, 10);
|
||||||
|
|
||||||
expect(result.data.length).toBe(0);
|
expect(result.ok).toBe(true);
|
||||||
expect(result.page).toBe(999);
|
if (!result.ok) return;
|
||||||
expect(result.total).toBeGreaterThanOrEqual(1);
|
expect(result.value.data.length).toBe(0);
|
||||||
|
expect(result.value.page).toBe(999);
|
||||||
|
expect(result.value.total).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can update article', async () => {
|
test('can update article', async () => {
|
||||||
@@ -181,18 +213,23 @@ describe('ArticleService', () => {
|
|||||||
description: 'Updated description',
|
description: 'Updated description',
|
||||||
};
|
};
|
||||||
|
|
||||||
const article = await getArticleBySlug('test-article');
|
const slugResult = await getArticleBySlug('test-article');
|
||||||
|
expect(slugResult.ok).toBe(true);
|
||||||
|
if (!slugResult.ok) return;
|
||||||
|
const article = slugResult.value;
|
||||||
expect(article).toBeDefined();
|
expect(article).toBeDefined();
|
||||||
|
|
||||||
const updatedArticle = await updateArticle(article!.id, dataToUpdate);
|
const result = await updateArticle(article!.id, dataToUpdate);
|
||||||
|
|
||||||
expect(updatedArticle).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(updatedArticle.id).toBe(article!.id);
|
if (!result.ok) return;
|
||||||
expect(updatedArticle.title).toBe(dataToUpdate.title);
|
expect(result.value).toBeDefined();
|
||||||
expect(updatedArticle.description).toBe(dataToUpdate.description);
|
expect(result.value.id).toBe(article!.id);
|
||||||
expect(updatedArticle.slug).toBe(article!.slug);
|
expect(result.value.title).toBe(dataToUpdate.title);
|
||||||
expect(updatedArticle.content).toBe(article!.content);
|
expect(result.value.description).toBe(dataToUpdate.description);
|
||||||
expect(updatedArticle.externalId).toBeDefined();
|
expect(result.value.slug).toBe(article!.slug);
|
||||||
|
expect(result.value.content).toBe(article!.content);
|
||||||
|
expect(result.value.externalId).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot update non-existing article', async () => {
|
test('cannot update non-existing article', async () => {
|
||||||
@@ -200,9 +237,11 @@ describe('ArticleService', () => {
|
|||||||
title: 'Updated Article Title',
|
title: 'Updated Article Title',
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(updateArticle('9999', dataToUpdate)).rejects.toThrow(
|
const result = await updateArticle('9999', dataToUpdate);
|
||||||
`Article with ID 9999 not found`
|
|
||||||
);
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) return;
|
||||||
|
expect(result.error.message).toContain('9999');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can delete article', async () => {
|
test('can delete article', async () => {
|
||||||
@@ -215,18 +254,25 @@ describe('ArticleService', () => {
|
|||||||
authorId: authorId,
|
authorId: authorId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedArticle = await saveArticle(articleToSave);
|
const saveResult = await saveArticle(articleToSave);
|
||||||
expect(savedArticle.id).toBeDefined();
|
expect(saveResult.ok).toBe(true);
|
||||||
|
if (!saveResult.ok) return;
|
||||||
|
expect(saveResult.value.id).toBeDefined();
|
||||||
|
|
||||||
await deleteArticle(savedArticle.id);
|
const deleteResult = await deleteArticle(saveResult.value.id);
|
||||||
|
expect(deleteResult.ok).toBe(true);
|
||||||
|
|
||||||
const deletedArticle = await getArticleBySlug('article-to-delete');
|
const getResult = await getArticleBySlug('article-to-delete');
|
||||||
expect(deletedArticle).toBeNull();
|
expect(getResult.ok).toBe(true);
|
||||||
|
if (!getResult.ok) return;
|
||||||
|
expect(getResult.value).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot delete non-existing article', async () => {
|
test('cannot delete non-existing article', async () => {
|
||||||
await expect(deleteArticle('9999')).rejects.toThrow(
|
const result = await deleteArticle('9999');
|
||||||
`Article with ID 9999 not found`
|
|
||||||
);
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) return;
|
||||||
|
expect(result.error.message).toContain('9999');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,14 +35,15 @@ describe('UserService', () => {
|
|||||||
role: 'user',
|
role: 'user',
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedUser = await saveUser(userToSave);
|
const result = await saveUser(userToSave);
|
||||||
|
|
||||||
expect(savedUser.id).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(savedUser.name).toBe(userToSave.name);
|
if (!result.ok) return;
|
||||||
expect(savedUser.email).toBe(userToSave.email);
|
expect(result.value.id).toBeDefined();
|
||||||
expect(savedUser.role).toBe(userToSave.role);
|
expect(result.value.name).toBe(userToSave.name);
|
||||||
expect(savedUser.role).toBe(userToSave.role);
|
expect(result.value.email).toBe(userToSave.email);
|
||||||
expect(savedUser.externalId).toBeDefined(); // Default to true if not set
|
expect(result.value.role).toBe(userToSave.role);
|
||||||
|
expect(result.value.externalId).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot save user with existing email', async () => {
|
test('cannot save user with existing email', async () => {
|
||||||
@@ -51,23 +52,31 @@ describe('UserService', () => {
|
|||||||
email: 'test@email.com',
|
email: 'test@email.com',
|
||||||
role: 'user',
|
role: 'user',
|
||||||
};
|
};
|
||||||
await expect(saveUser(userToSave)).rejects.toThrow(
|
|
||||||
`User with email ${userToSave.email} already exists`
|
const result = await saveUser(userToSave);
|
||||||
);
|
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) return;
|
||||||
|
expect(result.error.message).toContain(userToSave.email);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can getUserByEmail', async () => {
|
test('can getUserByEmail', async () => {
|
||||||
const user = await getUserByEmail('test@email.com');
|
const result = await getUserByEmail('test@email.com');
|
||||||
|
|
||||||
expect(user).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(user?.email).toBe('test@email.com');
|
if (!result.ok) return;
|
||||||
expect(user?.name).toBe('Test User');
|
expect(result.value?.email).toBe('test@email.com');
|
||||||
expect(user?.role).toBe('user');
|
expect(result.value?.name).toBe('Test User');
|
||||||
expect(user?.externalId).toBeDefined(); // Default to true if not set
|
expect(result.value?.role).toBe('user');
|
||||||
|
expect(result.value?.externalId).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot getUserByEmail with non-existing email', async () => {
|
test('cannot getUserByEmail with non-existing email', async () => {
|
||||||
await expect(getUserByEmail('missing@email.com')).resolves.toBeNull();
|
const result = await getUserByEmail('missing@email.com');
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
if (!result.ok) return;
|
||||||
|
expect(result.value).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can update user', async () => {
|
test('can update user', async () => {
|
||||||
@@ -76,17 +85,21 @@ describe('UserService', () => {
|
|||||||
role: 'admin',
|
role: 'admin',
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = await getUserByEmail('test@email.com');
|
const userResult = await getUserByEmail('test@email.com');
|
||||||
|
expect(userResult.ok).toBe(true);
|
||||||
|
if (!userResult.ok) return;
|
||||||
|
const user = userResult.value;
|
||||||
expect(user).toBeDefined();
|
expect(user).toBeDefined();
|
||||||
|
|
||||||
const updatedUser = await updateUser(user!.id, dataToUpdate);
|
const result = await updateUser(user!.id, dataToUpdate);
|
||||||
|
|
||||||
expect(updatedUser).toBeDefined();
|
expect(result.ok).toBe(true);
|
||||||
expect(updatedUser.id).toBe(user!.id);
|
if (!result.ok) return;
|
||||||
expect(updatedUser.name).toBe(dataToUpdate.name);
|
expect(result.value.id).toBe(user!.id);
|
||||||
expect(updatedUser.role).toBe(dataToUpdate.role);
|
expect(result.value.name).toBe(dataToUpdate.name);
|
||||||
expect(updatedUser.email).toBe(user!.email);
|
expect(result.value.role).toBe(dataToUpdate.role);
|
||||||
expect(updatedUser.externalId).toBeDefined(); // Default to true if not set
|
expect(result.value.email).toBe(user!.email);
|
||||||
|
expect(result.value.externalId).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot update non-existing user', async () => {
|
test('cannot update non-existing user', async () => {
|
||||||
@@ -95,9 +108,11 @@ describe('UserService', () => {
|
|||||||
role: 'admin',
|
role: 'admin',
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(updateUser('9999', dataToUpdate)).rejects.toThrow(
|
const result = await updateUser('9999', dataToUpdate);
|
||||||
`User with ID 9999 not found`
|
|
||||||
);
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) return;
|
||||||
|
expect(result.error.message).toContain('9999');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can sync admin user', async () => {
|
test('can sync admin user', async () => {
|
||||||
@@ -131,12 +146,14 @@ describe('UserService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as SessionClaims;
|
} as SessionClaims;
|
||||||
const syncedUser = await syncUser(sessionClaims);
|
|
||||||
|
|
||||||
expect(syncedUser).toBeDefined();
|
const result = await syncUser(sessionClaims);
|
||||||
expect(syncedUser.name).toBe('Updated Name');
|
|
||||||
expect(syncedUser.email).toBe('test@email.com');
|
expect(result.ok).toBe(true);
|
||||||
expect(syncedUser.role).toBe('admin');
|
if (!result.ok) return;
|
||||||
|
expect(result.value.name).toBe('Updated Name');
|
||||||
|
expect(result.value.email).toBe('test@email.com');
|
||||||
|
expect(result.value.role).toBe('admin');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can sync internal user', async () => {
|
test('can sync internal user', async () => {
|
||||||
@@ -170,12 +187,14 @@ describe('UserService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as SessionClaims;
|
} as SessionClaims;
|
||||||
const syncedUser = await syncUser(sessionClaims);
|
|
||||||
|
|
||||||
expect(syncedUser).toBeDefined();
|
const result = await syncUser(sessionClaims);
|
||||||
expect(syncedUser.name).toBe('Updated Name');
|
|
||||||
expect(syncedUser.email).toBe('test@email.com');
|
expect(result.ok).toBe(true);
|
||||||
expect(syncedUser.role).toBe('internal');
|
if (!result.ok) return;
|
||||||
|
expect(result.value.name).toBe('Updated Name');
|
||||||
|
expect(result.value.email).toBe('test@email.com');
|
||||||
|
expect(result.value.role).toBe('internal');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can sync user', async () => {
|
test('can sync user', async () => {
|
||||||
@@ -202,12 +221,14 @@ describe('UserService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as SessionClaims;
|
} as SessionClaims;
|
||||||
const syncedUser = await syncUser(sessionClaims);
|
|
||||||
|
|
||||||
expect(syncedUser).toBeDefined();
|
const result = await syncUser(sessionClaims);
|
||||||
expect(syncedUser.name).toBe('Updated Name');
|
|
||||||
expect(syncedUser.email).toBe('test@email.com');
|
expect(result.ok).toBe(true);
|
||||||
expect(syncedUser.role).toBe('user');
|
if (!result.ok) return;
|
||||||
|
expect(result.value.name).toBe('Updated Name');
|
||||||
|
expect(result.value.email).toBe('test@email.com');
|
||||||
|
expect(result.value.role).toBe('user');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can sync saving new user', async () => {
|
test('can sync saving new user', async () => {
|
||||||
@@ -234,11 +255,13 @@ describe('UserService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as SessionClaims;
|
} as SessionClaims;
|
||||||
const syncedUser = await syncUser(sessionClaims);
|
|
||||||
|
|
||||||
expect(syncedUser).toBeDefined();
|
const result = await syncUser(sessionClaims);
|
||||||
expect(syncedUser.name).toBe('Updated Name');
|
|
||||||
expect(syncedUser.email).toBe('new@email.com');
|
expect(result.ok).toBe(true);
|
||||||
expect(syncedUser.role).toBe('user');
|
if (!result.ok) return;
|
||||||
|
expect(result.value.name).toBe('Updated Name');
|
||||||
|
expect(result.value.email).toBe('new@email.com');
|
||||||
|
expect(result.value.role).toBe('user');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user