From 22d84a044fd1a1768e22aba00a470d9ac71cff68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Mon, 21 Oct 2024 14:28:43 +0200 Subject: [PATCH 01/14] [FEATURE]: Connect front-bo auth to the bff --- .../src/resolvers/account/AccountQueries.ts | 5 +- .../src/types/account/AccountLoginOutput.ts | 2 + src/v6y-commons/src/core/PassportUtils.ts | 1 - .../providers/GraphQLProvider.ts | 101 +++++++++++++----- 4 files changed, 83 insertions(+), 26 deletions(-) diff --git a/src/v6y-bff/src/resolvers/account/AccountQueries.ts b/src/v6y-bff/src/resolvers/account/AccountQueries.ts index 5d2c64c2..040594d5 100644 --- a/src/v6y-bff/src/resolvers/account/AccountQueries.ts +++ b/src/v6y-bff/src/resolvers/account/AccountQueries.ts @@ -104,11 +104,14 @@ const loginAccount = async (_: unknown, params: { input: AccountLoginType }) => } const token = passportGenerateToken(accountDetails); - AppLogger.info(`[AccountMutations - loginAccount] login success : ${accountDetails._id}`); + AppLogger.info( + `[AccountMutations - loginAccount] login success : ${accountDetails._id} - ${accountDetails.role}`, + ); return { _id: accountDetails._id, token, + role: accountDetails.role, }; } catch (error) { AppLogger.info(`[AccountMutations - loginAccount] error : ${error}`); diff --git a/src/v6y-bff/src/types/account/AccountLoginOutput.ts b/src/v6y-bff/src/types/account/AccountLoginOutput.ts index 3dcf02db..d9c0db0c 100644 --- a/src/v6y-bff/src/types/account/AccountLoginOutput.ts +++ b/src/v6y-bff/src/types/account/AccountLoginOutput.ts @@ -4,6 +4,8 @@ const AccountLoginOutput = ` _id: Int! """ Account token """ token: String! + """ Account role """ + role: String! } `; diff --git a/src/v6y-commons/src/core/PassportUtils.ts b/src/v6y-commons/src/core/PassportUtils.ts index 7244ab2b..dcd7a7e0 100644 --- a/src/v6y-commons/src/core/PassportUtils.ts +++ b/src/v6y-commons/src/core/PassportUtils.ts @@ -30,7 +30,6 @@ passport.use( }); if (accountDetails) { - AppLogger.info(`[passport] Utilisateur trouvé : ${JSON.stringify(accountDetails)}`); return done(null, accountDetails); } else { return done(null, false); diff --git a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts index 4692fb3c..17763c73 100644 --- a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts +++ b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts @@ -8,12 +8,7 @@ const dataClient = new GraphQLClient(process.env.NEXT_PUBLIC_GQL_API_BASE_PATH a ...options, headers: { ...(options?.headers || {}), - /** - * For demo purposes, we're using `localStorage` to access the token. - * You can use your own authentication logic here. - * In real world applications, you'll need to handle it in sync with your `authProvider`. - */ - Authorization: `Bearer ${JSON.parse(Cookies.get('auth') || '')?.email}`, + Authorization: `Bearer ${JSON.parse(Cookies.get('auth') || '{}')?.token}`, }, }); }, @@ -49,28 +44,86 @@ const mockUsers = [ ]; export const gqlAuthProvider: AuthProvider = { - login: async ({ email /*username, password, remember*/ }) => { - // Suppose we actually send a request to the back end here. - const user = mockUsers.find((item) => item.email === email); - - if (user) { - Cookies.set('auth', JSON.stringify(user), { - expires: 30, // 30 days - path: '/', + login: async ({ email, password }) => { + try { + const apiUrl = process.env.NEXT_PUBLIC_GQL_API_BASE_PATH; + if (!apiUrl) { + throw new Error('NEXT_PUBLIC_GQL_API_BASE_PATH is not defined'); + } + + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + operationName: 'LoginAccount', + query: ` + query LoginAccount($input: AccountLoginInput!) { + loginAccount(input: $input) { + _id + role + token + } + } + `, + variables: { + input: { email, password }, + }, + }), }); + + const { data, errors } = await response.json(); + + if (errors) { + return { + success: false, + error: { + name: 'LoginError', + message: errors[0].message, + }, + }; + } + + if (data.loginAccount?.token) { + if (data.loginAccount.role !== 'ADMIN') { + console.log("You are not authorized to access this page"); + return { + success: false, + error: { + name: 'LoginError', + message: 'You are not authorized to login', + }, + }; + } + + Cookies.set('auth', JSON.stringify({ token: data.loginAccount.token, _id: data.loginAccount._id }), { + expires: 30, // 30 jours + path: '/', + }); + + return { + success: true, + redirectTo: '/', + }; + } + return { - success: true, - redirectTo: '/', + success: false, + error: { + name: 'LoginError', + message: 'Invalid username or password', + }, + }; + } catch (error) { + return { + success: false, + error: { + name: 'LoginError', + message: (error as Error).message, + }, }; } - - return { - success: false, - error: { - name: 'LoginError', - message: 'Invalid username or password', - }, - }; }, register: async (params) => { // Suppose we actually send a request to the back end here. From 43c50ed4275418104e4e42d99034255737d8f6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Tue, 22 Oct 2024 11:49:23 +0200 Subject: [PATCH 02/14] [FEATURE]: Added update-password feature --- .../src/resolvers/account/AccountMutations.ts | 67 +++++++++- .../src/resolvers/account/AccountQueries.ts | 5 - src/v6y-bff/src/types/VitalityTypes.ts | 4 + .../src/types/account/AccountMutationsType.ts | 1 + .../account/AccountUpdatePasswordInput.ts | 11 ++ .../account/AccountUpdatePasswordOutput.ts | 8 ++ src/v6y-commons/src/core/PassportUtils.ts | 5 +- .../src/database/AccountProvider.ts | 47 ++++++- src/v6y-commons/src/types/AccountType.ts | 5 + src/v6y-front-bo/src/app/register/page.tsx | 25 ---- .../src/app/update-password/page.tsx | 4 +- .../v6y-auth/VitalityAuthRegisterView.tsx | 21 --- .../providers/GraphQLProvider.ts | 120 +++++++++--------- 13 files changed, 209 insertions(+), 114 deletions(-) create mode 100644 src/v6y-bff/src/types/account/AccountUpdatePasswordInput.ts create mode 100644 src/v6y-bff/src/types/account/AccountUpdatePasswordOutput.ts delete mode 100644 src/v6y-front-bo/src/app/register/page.tsx delete mode 100644 src/v6y-front-bo/src/features/v6y-auth/VitalityAuthRegisterView.tsx diff --git a/src/v6y-bff/src/resolvers/account/AccountMutations.ts b/src/v6y-bff/src/resolvers/account/AccountMutations.ts index 3ccda1d6..b61425a6 100644 --- a/src/v6y-bff/src/resolvers/account/AccountMutations.ts +++ b/src/v6y-bff/src/resolvers/account/AccountMutations.ts @@ -1,9 +1,13 @@ import { AccountInputType, AccountProvider, + AccountType, + AccountUpdatePasswordType, AppLogger, PasswordUtils, SearchQueryType, + isAdmin, + isSuperAdmin, } from '@v6y/commons'; const { hashPassword } = PasswordUtils; @@ -12,9 +16,17 @@ const { hashPassword } = PasswordUtils; * Create or edit account * @param _ * @param params + * @param context */ -const createOrEditAccount = async (_: unknown, params: { accountInput: AccountInputType }) => { +const createOrEditAccount = async ( + _: unknown, + params: { accountInput: AccountInputType }, + context: { user: AccountType }, +) => { try { + if (!(isAdmin(context.user) || isSuperAdmin(context.user))) { + return null; + } const { _id, username, password, email, role, applications } = params?.accountInput || {}; AppLogger.info(`[AccountMutations - createOrEditAccount] _id : ${_id}`); @@ -74,6 +86,58 @@ const createOrEditAccount = async (_: unknown, params: { accountInput: AccountIn } }; +/** + * Update password + * @param _ + * @param params + * @param context + **/ +const updateAccountPassword = async ( + _: unknown, + params: { input: AccountUpdatePasswordType }, + context: { user: AccountType }, +) => { + try { + if ( + context.user._id !== params.input._id && + !isAdmin(context.user) && + !isSuperAdmin(context.user) + ) { + throw new Error('You are not authorized to update this account'); + return null; + } + const { _id, password } = params?.input || {}; + + AppLogger.info(`[AccountMutations - updatePassword] _id : ${_id}`); + + const accountDetails = await AccountProvider.getAccountDetailsByParams({ _id }); + + if (!accountDetails) { + throw new Error('Invalid account'); + } + + const updatedAccount = await AccountProvider.updateAccountPassword({ + _id, + password: await PasswordUtils.hashPassword(password), + }); + + if (!updatedAccount || !updatedAccount._id) { + throw new Error('Invalid account'); + } + + AppLogger.info( + `[AccountMutations - updatePassword] updatedAccount : ${updatedAccount._id}`, + ); + + return { + _id: updatedAccount._id, + }; + } catch (error) { + AppLogger.error(`[AccountMutations - updatePassword] error : ${error}`); + return null; + } +}; + /** * Delete account * @param _ @@ -102,6 +166,7 @@ const deleteAccount = async (_: unknown, params: { input: SearchQueryType }) => const AccountMutations = { createOrEditAccount, + updateAccountPassword, deleteAccount, }; diff --git a/src/v6y-bff/src/resolvers/account/AccountQueries.ts b/src/v6y-bff/src/resolvers/account/AccountQueries.ts index 040594d5..fe960c10 100644 --- a/src/v6y-bff/src/resolvers/account/AccountQueries.ts +++ b/src/v6y-bff/src/resolvers/account/AccountQueries.ts @@ -28,11 +28,6 @@ const getAccountDetailsByParams = async (_: unknown, args: AccountType) => { const accountDetails = await AccountProvider.getAccountDetailsByParams({ _id, }); - - AppLogger.info( - `[AccountQueries - getAccountDetailsByParams] accountDetails : ${accountDetails?._id}`, - ); - return accountDetails; } catch (error) { AppLogger.info(`[AccountQueries - getAccountDetailsByParams] error : ${error}`); diff --git a/src/v6y-bff/src/types/VitalityTypes.ts b/src/v6y-bff/src/types/VitalityTypes.ts index 831aa2de..95656661 100644 --- a/src/v6y-bff/src/types/VitalityTypes.ts +++ b/src/v6y-bff/src/types/VitalityTypes.ts @@ -9,6 +9,8 @@ import AccountLoginOutput from './account/AccountLoginOutput.ts'; import AccountMutationsType from './account/AccountMutationsType.ts'; import AccountQueriesType from './account/AccountQueriesType.ts'; import AccountType from './account/AccountType.ts'; +import AccountUpdatePasswordInput from './account/AccountUpdatePasswordInput.ts'; +import AccountUpdatePasswordOutput from './account/AccountUpdatePasswordOutput.ts'; import ApplicationCreateOrEditInput from './application/ApplicationCreateOrEditInput.ts'; import ApplicationDeleteInput from './application/ApplicationDeleteInput.ts'; import ApplicationDeleteOutput from './application/ApplicationDeleteOutput.ts'; @@ -134,6 +136,8 @@ const VitalityTypes = gql(` ${AccountCreateOrEditInput} ${AccountCreateOrEditOutput} + ${AccountUpdatePasswordInput} + ${AccountUpdatePasswordOutput} ${AccountDeleteInput} ${AccountDeleteOutput} ${AccountMutationsType} diff --git a/src/v6y-bff/src/types/account/AccountMutationsType.ts b/src/v6y-bff/src/types/account/AccountMutationsType.ts index f81341f0..d7563950 100644 --- a/src/v6y-bff/src/types/account/AccountMutationsType.ts +++ b/src/v6y-bff/src/types/account/AccountMutationsType.ts @@ -1,6 +1,7 @@ const AccountMutationsType = ` type Mutation { createOrEditAccount(accountInput: AccountCreateOrEditInput!): AccountCreateOrEditOutput + updateAccountPassword(input: AccountUpdatePasswordInput!): AccountUpdatePasswordOutput deleteAccount(input: AccountDeleteInput!): AccountDeleteOutput } `; diff --git a/src/v6y-bff/src/types/account/AccountUpdatePasswordInput.ts b/src/v6y-bff/src/types/account/AccountUpdatePasswordInput.ts new file mode 100644 index 00000000..3ae99816 --- /dev/null +++ b/src/v6y-bff/src/types/account/AccountUpdatePasswordInput.ts @@ -0,0 +1,11 @@ +const AccountUpdatePasswordInput = ` + input AccountUpdatePasswordInput { + """ Account Id """ + _id: Int! + + """ Account New Password """ + password : String! + } +`; + +export default AccountUpdatePasswordInput; diff --git a/src/v6y-bff/src/types/account/AccountUpdatePasswordOutput.ts b/src/v6y-bff/src/types/account/AccountUpdatePasswordOutput.ts new file mode 100644 index 00000000..8ce116bc --- /dev/null +++ b/src/v6y-bff/src/types/account/AccountUpdatePasswordOutput.ts @@ -0,0 +1,8 @@ +const AccountUpdatePasswordOutput = ` + type AccountUpdatePasswordOutput { + """ Account id """ + _id: Int! + } +`; + +export default AccountUpdatePasswordOutput; diff --git a/src/v6y-commons/src/core/PassportUtils.ts b/src/v6y-commons/src/core/PassportUtils.ts index dcd7a7e0..115c2ea8 100644 --- a/src/v6y-commons/src/core/PassportUtils.ts +++ b/src/v6y-commons/src/core/PassportUtils.ts @@ -20,7 +20,6 @@ const opts = { passport.use( new JwtStrategy(opts, async (jwtPayload, done) => { try { - // Vérifier si le token contient bien un _id ou email if (!jwtPayload._id) { return done(null, false); } @@ -57,3 +56,7 @@ export const passportGenerateToken = (account: AccountType) => { }; export const passportInitialize = () => passport.initialize(); + +export const isAdmin = (account: AccountType) => account?.role === 'ADMIN'; + +export const isSuperAdmin = (account: AccountType) => account?.role === 'SUPER_ADMIN'; diff --git a/src/v6y-commons/src/database/AccountProvider.ts b/src/v6y-commons/src/database/AccountProvider.ts index 731a5ee6..f4531c6c 100644 --- a/src/v6y-commons/src/database/AccountProvider.ts +++ b/src/v6y-commons/src/database/AccountProvider.ts @@ -1,7 +1,7 @@ import { FindOptions, Op, Sequelize } from 'sequelize'; import AppLogger from '../core/AppLogger.ts'; -import { AccountInputType, AccountType } from '../types/AccountType.ts'; +import { AccountInputType, AccountType, AccountUpdatePasswordType } from '../types/AccountType.ts'; import { SearchQueryType } from '../types/SearchQueryType.ts'; import { AccountModelType } from './models/AccountModel.ts'; @@ -85,6 +85,48 @@ const editAccount = async (account: AccountInputType) => { } }; +/** + * Update Account Password + * @param account + */ +const updateAccountPassword = async ({ _id, password }: AccountUpdatePasswordType) => { + try { + if (!_id || !password) { + return null; + } + + AppLogger.info(`[AccountProvider - updateAccountPassword] _id: ${_id}`); + + const accountDetails = await AccountModelType.findOne({ + where: { + _id, + }, + }); + + if (!accountDetails) { + return null; + } + + await AccountModelType.update( + { + password: password, + }, + { + where: { + _id, + }, + }, + ); + + return { + _id, + }; + } catch (error) { + AppLogger.info(`[AccountProvider - updateAccountPassword] error: ${error}`); + return null; + } +}; + /** * Delete an Account * @param _id @@ -133,7 +175,7 @@ const getAccountDetailsByParams = async ({ _id, email }: { _id?: number; email?: } AppLogger.info( - `[AccountProvider - getAccountDetailsByParams] accountDetails: ${accountDetails._id || accountDetails.email}`, + `[AccountProvider - getAccountDetailsByParams] accountDetails: ${accountDetails.dataValues._id}`, ); return accountDetails.dataValues; @@ -187,6 +229,7 @@ const getAccountListByPageAndParams = async ({ const AccountProvider = { createAccount, editAccount, + updateAccountPassword, deleteAccount, getAccountDetailsByParams, getAccountListByPageAndParams, diff --git a/src/v6y-commons/src/types/AccountType.ts b/src/v6y-commons/src/types/AccountType.ts index 08eeb84b..0334de8c 100644 --- a/src/v6y-commons/src/types/AccountType.ts +++ b/src/v6y-commons/src/types/AccountType.ts @@ -16,6 +16,11 @@ export interface AccountInputType { applications?: number[]; } +export interface AccountUpdatePasswordType { + _id: number; + password: string; +} + export interface AccountLoginType { email: string; password: string; diff --git a/src/v6y-front-bo/src/app/register/page.tsx b/src/v6y-front-bo/src/app/register/page.tsx deleted file mode 100644 index 0b73208e..00000000 --- a/src/v6y-front-bo/src/app/register/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { redirect } from 'next/navigation'; -import * as React from 'react'; - -import { VitalityAuthRegisterView } from '../../features/v6y-auth/VitalityAuthRegisterView'; -import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; - -export default async function Register() { - const data = await getData(); - - if (data.authenticated) { - redirect(data?.redirectTo || '/'); - } - - return ; -} - -async function getData() { - const { authenticated, redirectTo, error } = await AuthServerProvider.check(); - - return { - authenticated, - redirectTo, - error, - }; -} diff --git a/src/v6y-front-bo/src/app/update-password/page.tsx b/src/v6y-front-bo/src/app/update-password/page.tsx index 22fac5b2..4c891e46 100644 --- a/src/v6y-front-bo/src/app/update-password/page.tsx +++ b/src/v6y-front-bo/src/app/update-password/page.tsx @@ -7,8 +7,8 @@ import { AuthServerProvider } from '../../infrastructure/providers/AuthServerPro export default async function UpdatePassword() { const data = await getData(); - if (data.authenticated) { - redirect(data?.redirectTo || '/'); + if (!data.authenticated) { + redirect(data?.redirectTo || '/login'); } return ; diff --git a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthRegisterView.tsx b/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthRegisterView.tsx deleted file mode 100644 index 77b03c2a..00000000 --- a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthRegisterView.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; - -import { AuthPage as AuthPageBase } from '@refinedev/antd'; -import { Typography } from 'antd'; - -import { useTranslation } from '../../infrastructure/adapters/translation/TranslationAdapter'; - -export const VitalityAuthRegisterView = () => { - const { translate } = useTranslation(); - - return ( - - {translate('v6y-authentication.title')} - - } - /> - ); -}; diff --git a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts index 17763c73..c6783a6c 100644 --- a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts +++ b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts @@ -22,26 +22,6 @@ export const gqlDataProvider = dataProvider(dataClient); export const gqlLiveProvider = liveProvider(wsClient); -const mockUsers = [ - { - email: 'admin@refine.dev', - name: 'John Doe', - avatar: 'https://i.pravatar.cc/150?img=1', - roles: ['admin'], - }, - { - email: 'editor@refine.dev', - name: 'Jane Doe', - avatar: 'https://i.pravatar.cc/150?img=1', - roles: ['editor'], - }, - { - email: 'demo@refine.dev', - name: 'Jane Doe', - avatar: 'https://i.pravatar.cc/150?img=1', - roles: ['user'], - }, -]; export const gqlAuthProvider: AuthProvider = { login: async ({ email, password }) => { @@ -50,7 +30,7 @@ export const gqlAuthProvider: AuthProvider = { if (!apiUrl) { throw new Error('NEXT_PUBLIC_GQL_API_BASE_PATH is not defined'); } - + const response = await fetch(apiUrl, { method: 'POST', headers: { @@ -84,7 +64,7 @@ export const gqlAuthProvider: AuthProvider = { }, }; } - + if (data.loginAccount?.token) { if (data.loginAccount.role !== 'ADMIN') { console.log("You are not authorized to access this page"); @@ -125,31 +105,9 @@ export const gqlAuthProvider: AuthProvider = { }; } }, - register: async (params) => { + forgotPassword: async () => { // Suppose we actually send a request to the back end here. - const user = mockUsers.find((item) => item.email === params.email); - - if (user) { - Cookies.set('auth', JSON.stringify(user), { - expires: 30, // 30 days - path: '/', - }); - return { - success: true, - redirectTo: '/', - }; - } - return { - success: false, - error: { - message: 'Register failed', - name: 'Invalid email or password', - }, - }; - }, - forgotPassword: async (params) => { - // Suppose we actually send a request to the back end here. - const user = mockUsers.find((item) => item.email === params.email); + const user = null; if (user) { //we can send email with reset password link here @@ -165,23 +123,71 @@ export const gqlAuthProvider: AuthProvider = { }, }; }, - updatePassword: async (params) => { - // Suppose we actually send a request to the back end here. - const isPasswordInvalid = params.password === '123456' || !params.password; + updatePassword: async ({ password }) => { + try { + const apiUrl = process.env.NEXT_PUBLIC_GQL_API_BASE_PATH; + if (!apiUrl) { + throw new Error('NEXT_PUBLIC_GQL_API_BASE_PATH is not defined'); + } + + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${JSON.parse(Cookies.get('auth') || '{}')?.token}`, + }, + body: JSON.stringify({ + operationName: 'UpdateAccountPassword', + query: ` + mutation UpdateAccountPassword($input: AccountUpdatePasswordInput!) { + updateAccountPassword(input: $input) { + _id + } + } + `, + variables: { + "input": { + "_id": JSON.parse(Cookies.get('auth') || '{}')?._id, + "password": password, + } + }, + }), + }); + + const { data, errors } = await response.json(); + + if (errors) { + return { + success: false, + error: { + name: 'UpdateAccountPassword', + message: errors[0].message, + }, + }; + } + + if (data.updateAccountPassword) { + return { + success: true, + }; + } - if (isPasswordInvalid) { return { success: false, error: { - message: 'Update password failed', - name: 'Invalid password', + name: 'UpdateAccountPassword', + message: 'Invalid password', + }, + }; + } catch (error) { + return { + success: false, + error: { + name: 'UpdateAccountPassword', + message: (error as Error).message, }, }; } - - return { - success: true, - }; }, logout: async () => { Cookies.remove('auth', { path: '/' }); @@ -208,7 +214,7 @@ export const gqlAuthProvider: AuthProvider = { const auth = Cookies.get('auth'); if (auth) { const parsedUser = JSON.parse(auth); - return parsedUser.roles; + return parsedUser.role; } return null; }, From e53b6e9d4720a1c4b15786c25a489e74f49a3968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Wed, 23 Oct 2024 16:03:05 +0200 Subject: [PATCH 03/14] [FIX]: Fixed login and cookies --- src/v6y-commons/src/core/PassportUtils.ts | 2 +- .../v6y-auth/{ => components}/VitalityAuthLoginView.tsx | 2 +- .../src/infrastructure/providers/GraphQLProvider.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/v6y-front-bo/src/features/v6y-auth/{ => components}/VitalityAuthLoginView.tsx (90%) diff --git a/src/v6y-commons/src/core/PassportUtils.ts b/src/v6y-commons/src/core/PassportUtils.ts index 115c2ea8..b926c216 100644 --- a/src/v6y-commons/src/core/PassportUtils.ts +++ b/src/v6y-commons/src/core/PassportUtils.ts @@ -59,4 +59,4 @@ export const passportInitialize = () => passport.initialize(); export const isAdmin = (account: AccountType) => account?.role === 'ADMIN'; -export const isSuperAdmin = (account: AccountType) => account?.role === 'SUPER_ADMIN'; +export const isSuperAdmin = (account: AccountType) => account?.role === 'SUPERADMIN'; diff --git a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthLoginView.tsx b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx similarity index 90% rename from src/v6y-front-bo/src/features/v6y-auth/VitalityAuthLoginView.tsx rename to src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx index 738d2692..1071de76 100644 --- a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthLoginView.tsx +++ b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx @@ -3,7 +3,7 @@ import { AuthPage as AuthPageBase } from '@refinedev/antd'; import { Checkbox, Form, Typography } from 'antd'; -import { useTranslation } from '../../infrastructure/adapters/translation/TranslationAdapter'; +import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; export const VitalityAuthLoginView = () => { const { translate } = useTranslation(); diff --git a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts index c6783a6c..a81deeef 100644 --- a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts +++ b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts @@ -66,7 +66,7 @@ export const gqlAuthProvider: AuthProvider = { } if (data.loginAccount?.token) { - if (data.loginAccount.role !== 'ADMIN') { + if (data.loginAccount.role !== 'ADMIN' && data.loginAccount.role !== 'SUPERADMIN') { console.log("You are not authorized to access this page"); return { success: false, @@ -77,7 +77,7 @@ export const gqlAuthProvider: AuthProvider = { }; } - Cookies.set('auth', JSON.stringify({ token: data.loginAccount.token, _id: data.loginAccount._id }), { + Cookies.set('auth', JSON.stringify({ token: data.loginAccount.token, _id: data.loginAccount._id, role: data.loginAccount.role }), { expires: 30, // 30 jours path: '/', }); From 3dfcaa5c5d8763b98fd3ede651edfdba554da9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Tue, 5 Nov 2024 11:55:27 +0100 Subject: [PATCH 04/14] [FEATURE]: Create account feature added in bff and front-bo --- .../src/resolvers/account/AccountMutations.ts | 55 ++++++-- .../application/ApplicationQueries.ts | 38 +++++- .../src/types/account/AccountDeleteInput.ts | 2 +- .../src/types/account/AccountMutationsType.ts | 2 +- .../application/ApplicationQueriesType.ts | 1 + .../public/locales/en/common.json | 44 ++++++ .../public/locales/fr/common.json | 44 ++++++ src/v6y-front-bo/src/app/login/page.tsx | 2 +- .../src/app/v6y-accounts/create/page.tsx | 9 ++ .../src/app/v6y-accounts/layout.tsx | 23 ++++ .../src/app/v6y-accounts/page.tsx | 9 ++ .../src/app/v6y-accounts/show/[id]/page.tsx | 9 ++ .../apis/getApplicationList.ts | 4 +- .../components/VitalityFormFieldSet.tsx | 7 + .../commons/config/VitalityDetailsConfig.tsx | 22 ++- .../src/commons/config/VitalityFormConfig.tsx | 127 +++++++++++++++++- .../config/VitalityNavigationConfig.ts | 10 ++ src/v6y-front-bo/src/commons/hooks/useRole.ts | 18 +++ .../src/commons/hooks/useToken.ts | 11 ++ .../v6y-accounts/apis/createOrEditAccount.ts | 11 ++ .../v6y-accounts/apis/deleteAccount.ts | 12 ++ .../apis/getAccountDetailsByParams.ts | 14 ++ .../apis/getAccountListByPageAndParams.ts | 14 ++ .../components/VitalityAccountCreateView.tsx | 60 +++++++++ .../components/VitalityAccountListView.tsx | 44 ++++++ .../VitalityApplicationListView.tsx | 2 +- .../src/infrastructure/adapters/api/.gitkeep | 0 .../adapters/api/GraphQLClient.ts | 27 ++++ .../components/RefineCreateWrapper.tsx | 14 +- .../components/RefineEditWrapper.tsx | 30 ++--- .../components/RefineSelectWrapper.tsx | 72 ++++++---- .../components/RefineShowWrapper.tsx | 11 +- .../providers/GraphQLProvider.ts | 17 +-- .../src/infrastructure/types/FormType.ts | 9 ++ 34 files changed, 678 insertions(+), 96 deletions(-) create mode 100644 src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx create mode 100644 src/v6y-front-bo/src/app/v6y-accounts/layout.tsx create mode 100644 src/v6y-front-bo/src/app/v6y-accounts/page.tsx create mode 100644 src/v6y-front-bo/src/app/v6y-accounts/show/[id]/page.tsx rename src/v6y-front-bo/src/{features/v6y-applications => commons}/apis/getApplicationList.ts (56%) create mode 100644 src/v6y-front-bo/src/commons/hooks/useRole.ts create mode 100644 src/v6y-front-bo/src/commons/hooks/useToken.ts create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/apis/createOrEditAccount.ts create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountListByPageAndParams.ts create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx delete mode 100644 src/v6y-front-bo/src/infrastructure/adapters/api/.gitkeep create mode 100644 src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts diff --git a/src/v6y-bff/src/resolvers/account/AccountMutations.ts b/src/v6y-bff/src/resolvers/account/AccountMutations.ts index b61425a6..96904ba0 100644 --- a/src/v6y-bff/src/resolvers/account/AccountMutations.ts +++ b/src/v6y-bff/src/resolvers/account/AccountMutations.ts @@ -20,23 +20,26 @@ const { hashPassword } = PasswordUtils; */ const createOrEditAccount = async ( _: unknown, - params: { accountInput: AccountInputType }, + params: { input: AccountInputType }, context: { user: AccountType }, ) => { try { if (!(isAdmin(context.user) || isSuperAdmin(context.user))) { - return null; + throw new Error('You are not authorized to create an account'); + } + const { _id, username, password, email, role, applications } = params?.input || {}; + + if (!isSuperAdmin(context.user) && role === 'ADMIN') { + AppLogger.info(`[AccountMutations - createOrEditAccount] role : ${role}`); + throw new Error('You are not authorized to create an admin account'); } - const { _id, username, password, email, role, applications } = params?.accountInput || {}; AppLogger.info(`[AccountMutations - createOrEditAccount] _id : ${_id}`); AppLogger.info(`[AccountMutations - createOrEditAccount] username : ${username}`); AppLogger.info(`[AccountMutations - createOrEditAccount] password : ${password}`); AppLogger.info(`[AccountMutations - createOrEditAccount] email : ${email}`); AppLogger.info(`[AccountMutations - createOrEditAccount] role : ${role}`); - AppLogger.info( - `[AccountMutations - createOrEditAccount] applications : ${applications?.join(',')}`, - ); + AppLogger.info(`[AccountMutations - createOrEditAccount] applications : ${applications}`); if (_id) { const editedAccount = await AccountProvider.editAccount({ @@ -49,7 +52,7 @@ const createOrEditAccount = async ( }); if (!editedAccount || !editedAccount._id) { - return null; + throw new Error('Invalid account'); } AppLogger.info( @@ -61,6 +64,10 @@ const createOrEditAccount = async ( }; } + const user = await AccountProvider.getAccountDetailsByParams({ email }); + if (user) { + throw new Error('User already exists with this email'); + } const createdAccount = await AccountProvider.createAccount({ username, password: await hashPassword(password), @@ -104,7 +111,6 @@ const updateAccountPassword = async ( !isSuperAdmin(context.user) ) { throw new Error('You are not authorized to update this account'); - return null; } const { _id, password } = params?.input || {}; @@ -143,16 +149,39 @@ const updateAccountPassword = async ( * @param _ * @param params */ -const deleteAccount = async (_: unknown, params: { input: SearchQueryType }) => { +const deleteAccount = async ( + _: unknown, + params: { accountInput: SearchQueryType }, + context: { user: AccountType }, +) => { try { - const whereClause = params?.input?.where; - if (!whereClause) { + const whereClause = params?.accountInput?.where; + + if (!whereClause?.id) { return null; } - - const accountId = whereClause._id; + const accountId = parseInt(whereClause.id, 10); AppLogger.info(`[AccountMutations - deleteAccount] accountId : ${accountId}`); + const userToDelete = await AccountProvider.getAccountDetailsByParams({ + _id: accountId, + }); + if (!userToDelete) { + throw new Error('User does not exist'); + } + + if (!(isSuperAdmin(context.user) || isAdmin(context.user))) { + throw new Error('You are not authorized to delete an account'); + } + + if (context.user._id === userToDelete._id) { + throw new Error('You cannot delete your own account'); + } + + if (userToDelete.role === 'ADMIN' && !isSuperAdmin(context.user)) { + throw new Error('You are not authorized to delete an admin account'); + } + await AccountProvider.deleteAccount({ _id: accountId }); return { diff --git a/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts b/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts index 09f52416..13569a4d 100644 --- a/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts +++ b/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts @@ -150,8 +150,7 @@ const getApplicationListByPageAndParams = async (_: unknown, args: SearchQueryTy ); AppLogger.info(`[ApplicationQueries - getApplicationListByPageAndParams] limit : ${limit}`); AppLogger.info( - `[ApplicationQueries - getApplicationListByPageAndParams] keywords : ${ - keywords?.join?.(',') || '' + `[ApplicationQueries - getApplicationListByPageAndParams] keywords : ${keywords?.join?.(',') || '' }`, ); AppLogger.info( @@ -179,6 +178,34 @@ const getApplicationListByPageAndParams = async (_: unknown, args: SearchQueryTy } }; +/** + * Get application list + * @param _ + * @param args + */ +const getApplicationList = async (_: unknown, args: SearchQueryType) => { + try { + const { where, sort } = args || {}; + + AppLogger.info(`[ApplicationQueries - getApplicationListByPageAndParams] where : ${where}`); + AppLogger.info(`[ApplicationQueries - getApplicationListByPageAndParams] sort : ${sort}`); + + const appList = await ApplicationProvider.getApplicationListByPageAndParams({ + where, + sort, + }); + + AppLogger.info( + `[ApplicationQueries - getApplicationListByPageAndParams] appList : ${appList?.length}`, + ); + + return appList; + } catch (error) { + AppLogger.info(`[ApplicationQueries - getApplicationListByPageAndParams] error : ${error}`); + return []; + } +}; + /** * Get application stats by params * @param _ @@ -189,8 +216,7 @@ const getApplicationStatsByParams = async (_: unknown, args: SearchQueryType) => const { keywords } = args || {}; AppLogger.info( - `[ApplicationQueries - getApplicationStatsByParams] keywords : ${ - keywords?.join?.(',') || '' + `[ApplicationQueries - getApplicationStatsByParams] keywords : ${keywords?.join?.(',') || '' }`, ); @@ -219,8 +245,7 @@ const getApplicationTotalByParams = async (_: unknown, args: SearchQueryType) => const { keywords, searchText } = args || {}; AppLogger.info( - `[ApplicationQueries - getApplicationTotalByParams] keywords : ${ - keywords?.join?.(',') || '' + `[ApplicationQueries - getApplicationTotalByParams] keywords : ${keywords?.join?.(',') || '' }`, ); AppLogger.info( @@ -251,6 +276,7 @@ const ApplicationQueries = { getApplicationDetailsKeywordsByParams, getApplicationTotalByParams, getApplicationListByPageAndParams, + getApplicationList, getApplicationStatsByParams, }; diff --git a/src/v6y-bff/src/types/account/AccountDeleteInput.ts b/src/v6y-bff/src/types/account/AccountDeleteInput.ts index 09cc39a4..65baae25 100644 --- a/src/v6y-bff/src/types/account/AccountDeleteInput.ts +++ b/src/v6y-bff/src/types/account/AccountDeleteInput.ts @@ -1,7 +1,7 @@ const AccountDeleteInput = ` input AccountDeleteInputClause { """ Account to delete id """ - _id: Int! + id: String! } input AccountDeleteInput { diff --git a/src/v6y-bff/src/types/account/AccountMutationsType.ts b/src/v6y-bff/src/types/account/AccountMutationsType.ts index d7563950..c3d937c9 100644 --- a/src/v6y-bff/src/types/account/AccountMutationsType.ts +++ b/src/v6y-bff/src/types/account/AccountMutationsType.ts @@ -1,6 +1,6 @@ const AccountMutationsType = ` type Mutation { - createOrEditAccount(accountInput: AccountCreateOrEditInput!): AccountCreateOrEditOutput + createOrEditAccount(input: AccountCreateOrEditInput!): AccountCreateOrEditOutput updateAccountPassword(input: AccountUpdatePasswordInput!): AccountUpdatePasswordOutput deleteAccount(input: AccountDeleteInput!): AccountDeleteOutput } diff --git a/src/v6y-bff/src/types/application/ApplicationQueriesType.ts b/src/v6y-bff/src/types/application/ApplicationQueriesType.ts index bd42f410..bcd8504a 100644 --- a/src/v6y-bff/src/types/application/ApplicationQueriesType.ts +++ b/src/v6y-bff/src/types/application/ApplicationQueriesType.ts @@ -1,5 +1,6 @@ const ApplicationQueriesType = ` type Query { + getApplicationList(where: JSON, sort: [String]): [ApplicationType] getApplicationListByPageAndParams(start: Int, offset: Int, limit: Int, keywords: [String], searchText: String, where: JSON, sort: String): [ApplicationType] getApplicationStatsByParams(keywords: [String]): [KeywordStatsType] getApplicationTotalByParams(keywords: [String], searchText: String): Int diff --git a/src/v6y-front-bo/public/locales/en/common.json b/src/v6y-front-bo/public/locales/en/common.json index 844665ea..635bb7b9 100644 --- a/src/v6y-front-bo/public/locales/en/common.json +++ b/src/v6y-front-bo/public/locales/en/common.json @@ -58,6 +58,43 @@ "submit": "Update" } }, + "createAccount" : { + "title": "Create an account", + "fields": { + "account-infos-group": "Account Information", + "account-email" : { + "label": "Account email", + "placeholder": "Enter an account email", + "error": "Account email is a mandatory field!" + }, + "account-username" : { + "label": "Account username", + "placeholder": "Enter an account username", + "error": "Account username is a mandatory field!" + }, + "account-role" : { + "label": "Account role", + "placeholder": "Select an account role", + "options": { + "admin": "Administrator", + "user": "User" + }, + "error": "Account role is a mandatory field!" + }, + "account-password" : { + "label": "Account password", + "placeholder": "Enter your account password", + "error": "Account password is a mandatory field!" + }, + "applications-group": "Applications", + "account-applications" : { + "label": "Account applications", + "placeholder": "Select account applications", + "error": "You need to select at least one application!" + } + + } + }, "error": { "info": "You may have forgotten to add the {{action}} component to {{resource}} resource.", "404": "Sorry, the page you visited does not exist.", @@ -222,6 +259,13 @@ "show": "Show Notification" } }, + "v6y-accounts": { + "v6y-accounts":"Accounts", + "titles" : { + "list": "Accounts", + "create": "Create Account" + } + }, "v6y-faqs": { "v6y-faqs": "FAQ", "fields": { diff --git a/src/v6y-front-bo/public/locales/fr/common.json b/src/v6y-front-bo/public/locales/fr/common.json index 844665ea..635bb7b9 100644 --- a/src/v6y-front-bo/public/locales/fr/common.json +++ b/src/v6y-front-bo/public/locales/fr/common.json @@ -58,6 +58,43 @@ "submit": "Update" } }, + "createAccount" : { + "title": "Create an account", + "fields": { + "account-infos-group": "Account Information", + "account-email" : { + "label": "Account email", + "placeholder": "Enter an account email", + "error": "Account email is a mandatory field!" + }, + "account-username" : { + "label": "Account username", + "placeholder": "Enter an account username", + "error": "Account username is a mandatory field!" + }, + "account-role" : { + "label": "Account role", + "placeholder": "Select an account role", + "options": { + "admin": "Administrator", + "user": "User" + }, + "error": "Account role is a mandatory field!" + }, + "account-password" : { + "label": "Account password", + "placeholder": "Enter your account password", + "error": "Account password is a mandatory field!" + }, + "applications-group": "Applications", + "account-applications" : { + "label": "Account applications", + "placeholder": "Select account applications", + "error": "You need to select at least one application!" + } + + } + }, "error": { "info": "You may have forgotten to add the {{action}} component to {{resource}} resource.", "404": "Sorry, the page you visited does not exist.", @@ -222,6 +259,13 @@ "show": "Show Notification" } }, + "v6y-accounts": { + "v6y-accounts":"Accounts", + "titles" : { + "list": "Accounts", + "create": "Create Account" + } + }, "v6y-faqs": { "v6y-faqs": "FAQ", "fields": { diff --git a/src/v6y-front-bo/src/app/login/page.tsx b/src/v6y-front-bo/src/app/login/page.tsx index b6f618ff..2d6f6c48 100644 --- a/src/v6y-front-bo/src/app/login/page.tsx +++ b/src/v6y-front-bo/src/app/login/page.tsx @@ -1,7 +1,7 @@ import { redirect } from 'next/navigation'; import * as React from 'react'; -import { VitalityAuthLoginView } from '../../features/v6y-auth/VitalityAuthLoginView'; +import { VitalityAuthLoginView } from '../../features/v6y-auth/components/VitalityAuthLoginView'; import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; export default async function Login() { diff --git a/src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx b/src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx new file mode 100644 index 00000000..9501d689 --- /dev/null +++ b/src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +import * as React from 'react'; +import VitalityAccountCreateView from '../../../features/v6y-accounts/components/VitalityAccountCreateView'; + +export default function VitalityAccountCreatePage() { + + return ; +} diff --git a/src/v6y-front-bo/src/app/v6y-accounts/layout.tsx b/src/v6y-front-bo/src/app/v6y-accounts/layout.tsx new file mode 100644 index 00000000..7cd9dc7f --- /dev/null +++ b/src/v6y-front-bo/src/app/v6y-accounts/layout.tsx @@ -0,0 +1,23 @@ +import { redirect } from 'next/navigation'; +import { ReactNode } from 'react'; + +import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; + +export default async function Layout({ children }: { children: ReactNode }) { + const data = await getData(); + + if (!data.authenticated) { + return redirect(data?.redirectTo || '/login'); + } + + return children; +} + +async function getData() { + const { authenticated, redirectTo } = await AuthServerProvider.check(); + + return { + authenticated, + redirectTo, + }; +} diff --git a/src/v6y-front-bo/src/app/v6y-accounts/page.tsx b/src/v6y-front-bo/src/app/v6y-accounts/page.tsx new file mode 100644 index 00000000..faf62a55 --- /dev/null +++ b/src/v6y-front-bo/src/app/v6y-accounts/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +import * as React from 'react'; + +import VitalityAccountListView from '../../features/v6y-accounts/components/VitalityAccountListView'; + +export default function AccountList() { + return ; +} diff --git a/src/v6y-front-bo/src/app/v6y-accounts/show/[id]/page.tsx b/src/v6y-front-bo/src/app/v6y-accounts/show/[id]/page.tsx new file mode 100644 index 00000000..e9f5cd93 --- /dev/null +++ b/src/v6y-front-bo/src/app/v6y-accounts/show/[id]/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +import * as React from 'react'; + +import VitalityAccountDetailsView from '../../../../features/v6y-accounts/components/VitalityAccountDetailsView'; + +export default function VitalityAccountDetailsPage() { + return ; +} diff --git a/src/v6y-front-bo/src/features/v6y-applications/apis/getApplicationList.ts b/src/v6y-front-bo/src/commons/apis/getApplicationList.ts similarity index 56% rename from src/v6y-front-bo/src/features/v6y-applications/apis/getApplicationList.ts rename to src/v6y-front-bo/src/commons/apis/getApplicationList.ts index b0f83bcc..1a1a8d82 100644 --- a/src/v6y-front-bo/src/features/v6y-applications/apis/getApplicationList.ts +++ b/src/v6y-front-bo/src/commons/apis/getApplicationList.ts @@ -1,8 +1,8 @@ import { gql } from 'graphql-request'; const GetApplicationList = gql` - query GetApplicationList($start: Int, $limit: Int, $sort: String) { - getApplicationListByPageAndParams(start: $start, limit: $limit, sort: $sort) { + query GetApplicationList($sort: [String]) { + getApplicationList(sort: $sort) { _id acronym name diff --git a/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx b/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx index 547aa875..e994dcab 100644 --- a/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx +++ b/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx @@ -17,12 +17,15 @@ const VitalityFormFieldSet = ({ groupTitle, items, selectOptions }: VitalityForm label={item.label} name={item.name} rules={item.rules} + initialValue={item.defaultValue} > {item.type === 'select' && ( )} diff --git a/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx b/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx index 73a1a918..e9b87578 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx +++ b/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx @@ -1,4 +1,5 @@ import { + AccountType, ApplicationType, AuditHelpType, DependencyStatusHelpType, @@ -13,6 +14,23 @@ import { ReactNode } from 'react'; import { TranslateType } from '../../infrastructure/types/TranslationType'; import VitalityLinks from '../components/VitalityLinks'; +export const formatAccountDetails = ( + translate: TranslateType, + details: AccountType, +): Record => { + if (!Object.keys(details || {})?.length) { + return {}; + } + + return { + [translate('v6y-accounts.fields.account-username.label') || '']: details.username, + [translate('v6y-accounts.fields.account-email.label') || '']: details.email, + [translate('v6y-accounts.fields.account-role.label') || '']: details.role, + [translate('v6y-accounts.fields.account-applications.label') || '']: details.applications, + + }; +} + export const formatApplicationDetails = ( translate: TranslateType, details: ApplicationType, @@ -108,11 +126,11 @@ export const formatDependencyStatusHelpDetails = ( return { [translate('v6y-dependency-status-helps.fields.dependency-status-help-category.label') || - '']: details.category, + '']: details.category, [translate('v6y-dependency-status-helps.fields.dependency-status-help-title.label') || '']: details.title, [translate('v6y-dependency-status-helps.fields.dependency-status-help-description.label') || - '']: details.description, + '']: details.description, [translate('v6y-dependency-status-helps.fields.dependency-status-help-links.label') || '']: , }; diff --git a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx index 1d7389de..8e97e753 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx +++ b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx @@ -1,7 +1,6 @@ import { ApplicationType } from '@v6y/commons'; import { DefaultOptionType } from 'antd/es/select'; import { Variables } from 'graphql-request'; - import { TranslateType } from '../../infrastructure/types/TranslationType'; import VitalityFormFieldSet from '../components/VitalityFormFieldSet'; @@ -754,3 +753,129 @@ export const deprecatedDependencyCreateOrEditFormOutputAdapter = ( name: params?.['deprecated-dependency-name'], }, }); + + +export const accountCreateOrEditFormOutputAdapter = (params: Record) => ({ + accountInput: { + _id: params?.['_id'], + email: params?.['account-email'], + username: params?.['account-username'], + role: params?.['account-role'], + password: params?.['account-password'], + applications: params?.['account-applications'], + }, +}); + +export const accountCreateEditItems = (translate: TranslateType, role: string, applications: DefaultOptionType[]) => { + + const roles = [ + { label: translate('pages.createAccount.fields.account-role.options.admin'), value: 'ADMIN' }, + { label: translate('pages.createAccount.fields.account-role.options.user'), value: 'USER' }, + ]; + + return [ + , + , + ]; +}; + +export const accountInfosFormItems = (translate: TranslateType, role: string) => { + + return [ + { + id: 'account-email', + name: 'account-email', + label: translate('pages.createAccount.fields.account-email.label'), + placeholder: translate('pages.createAccount.fields.account-email.placeholder'), + rules: [ + { + required: true, + message: translate('pages.createAccount.fields.account-email.error'), + }, + { + type: 'email', + message: translate('pages.createAccount.fields.account-email.error'), + }, + ], + }, + { + id: 'account-username', + name: 'account-username', + label: translate('pages.createAccount.fields.account-username.label'), + placeholder: translate('pages.createAccount.fields.account-username.placeholder'), + rules: [ + { + required: true, + message: translate('pages.createAccount.fields.account-username.error'), + }, + ], + }, + { + id: 'account-role', + name: 'account-role', + label: translate('pages.createAccount.fields.account-role.label'), + placeholder: translate('pages.createAccount.fields.account-role.placeholder'), + type: 'select', + disabled: role !== 'SUPERADMIN', + defaultValue: role !== 'SUPERADMIN' ? 'USER' : undefined, + rules: [ + { + required: true, + message: translate('pages.createAccount.fields.account-role.error'), + }, + ], + options: [ + { label: translate('pages.createAccount.fields.account-role.options.admin'), value: 'ADMIN' }, + { label: translate('pages.createAccount.fields.account-role.options.user'), value: 'USER' }, + ], + }, + { + id: 'account-password', + name: 'account-password', + type: 'password', + label: translate('pages.createAccount.fields.account-password.label'), + placeholder: translate('pages.createAccount.fields.account-password.placeholder'), + rules: [ + { + required: true, + message: translate('pages.createAccount.fields.account-password.error'), + }, + ], + }, + ]; +} + +export const accountApplicationsFormItems = (translate: TranslateType) => { + + return [ + { + id: 'account-applications', + name: 'account-applications', + label: translate('pages.createAccount.fields.account-applications.label'), + placeholder: translate('pages.createAccount.fields.account-applications.placeholder'), + type: 'select', + mode: 'multiple', + rules: [ + { + required: true, + message: translate('pages.createAccount.fields.account-applications.error'), + validator: (_: unknown, value: string[]) => + value && value.length > 0 + ? Promise.resolve() + : Promise.reject(new Error(translate('pages.createAccount.fields.account-applications.error'))), + }, + + ], + }, + ]; +}; \ No newline at end of file diff --git a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts index ff682ac8..a3fceeaa 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts +++ b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts @@ -1,4 +1,14 @@ export const VitalityRoutes = [ + { + name: 'v6y-accounts', + list: '/v6y-accounts', + create: '/v6y-accounts/create', + edit: '/v6y-accounts/edit/:id', + show: '/v6y-accounts/show/:id', + meta: { + canDelete: true, + } + }, { name: 'v6y-applications', list: '/v6y-applications', diff --git a/src/v6y-front-bo/src/commons/hooks/useRole.ts b/src/v6y-front-bo/src/commons/hooks/useRole.ts new file mode 100644 index 00000000..f507a14a --- /dev/null +++ b/src/v6y-front-bo/src/commons/hooks/useRole.ts @@ -0,0 +1,18 @@ +import Cookies from 'js-cookie'; + +export const useRole = () => { + + const getRole = () => { + const auth = Cookies.get('auth'); + return JSON.parse(auth || '{}')?.role; + } + + const setRole = () => { + // set role + } + + return ({ + getRole, + setRole + }); +}; \ No newline at end of file diff --git a/src/v6y-front-bo/src/commons/hooks/useToken.ts b/src/v6y-front-bo/src/commons/hooks/useToken.ts new file mode 100644 index 00000000..c474b6fa --- /dev/null +++ b/src/v6y-front-bo/src/commons/hooks/useToken.ts @@ -0,0 +1,11 @@ + +import Cookies from 'js-cookie'; + +const useToken = () => { + const auth = Cookies.get('auth'); + const token = JSON.parse(auth || '{}')?.token; + + return token; +} + +export default useToken; \ No newline at end of file diff --git a/src/v6y-front-bo/src/features/v6y-accounts/apis/createOrEditAccount.ts b/src/v6y-front-bo/src/features/v6y-accounts/apis/createOrEditAccount.ts new file mode 100644 index 00000000..9aad4a40 --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/apis/createOrEditAccount.ts @@ -0,0 +1,11 @@ +import { gql } from 'graphql-request'; + +const CreateOrEditAccount = gql` + mutation CreateOrEditAccount($accountInput: AccountCreateOrEditInput!) { + createOrEditAccount(input: $accountInput) { + _id + } + } +`; + +export default CreateOrEditAccount; diff --git a/src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts b/src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts new file mode 100644 index 00000000..adb0d8cf --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts @@ -0,0 +1,12 @@ +import { gql } from 'graphql-request'; + +const DeleteAccount = gql` + mutation DeleteAccount($input: AccountDeleteInput!) { + deleteAccount(input: $input) { + _id + } + } +`; + +export default DeleteAccount; + diff --git a/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts new file mode 100644 index 00000000..553331cb --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts @@ -0,0 +1,14 @@ +import { gql } from 'graphql-request'; + +const GetAccountDetailsByParams = gql` + query GetAccountDetailsByParams($_id: Int!) { + getAccountDetailsByParams(_id: $_id) { + _id + username + email + role + } + } +`; + +export default GetAccountDetailsByParams; diff --git a/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountListByPageAndParams.ts b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountListByPageAndParams.ts new file mode 100644 index 00000000..28e06cd5 --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountListByPageAndParams.ts @@ -0,0 +1,14 @@ +import { gql } from 'graphql-request'; + +const GetAccountListByPageAndParams = gql` + query GetAccountListByPageAndParams { + getAccountListByPageAndParams { + _id + username + email + role + } + } +`; + +export default GetAccountListByPageAndParams; diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx new file mode 100644 index 00000000..10b313fb --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { Typography } from 'antd'; +import { + accountCreateEditItems, + accountCreateOrEditFormOutputAdapter, +} from '../../../commons/config/VitalityFormConfig'; +import RefineSelectWrapper from '../../../infrastructure/components/RefineSelectWrapper'; +import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; +import CreateOrEditAccount from '../apis/createOrEditAccount'; +import { useRole } from '../../../commons/hooks/useRole'; +import GetApplicationList from '../../../commons/apis/getApplicationList'; +import { ApplicationType } from '@v6y/commons/src/types/ApplicationType'; +import { useEffect, useState } from 'react'; + +export default function VitalityAccountCreateView() { + const { translate } = useTranslation(); + const [userRole, setUserRole] = useState(null); + const { getRole } = useRole(); + + useEffect(() => { + setUserRole(getRole()); + }, [getRole]); + + console.log(userRole); + + if (!userRole) { + // fallback view (EmptyView) + return null; + } + + return ( + + {translate('v6y-accounts.titles.create')} + + } + createOptions={{ + createResource: 'createOrEditAccount', + createFormAdapter: accountCreateOrEditFormOutputAdapter, + createQuery: CreateOrEditAccount, + createQueryParams: {}, + }} + selectOptions={{ + resource: 'getApplicationList', + query: GetApplicationList, + }} + renderSelectOption={(applications: ApplicationType[]) => { + // TODO: dans config + const applicationsValues = applications?.map((application: ApplicationType) => ({ + value: application._id, + label: application.name, + })); + + return accountCreateEditItems(translate, userRole, applicationsValues) + }} + /> + ); +} \ No newline at end of file diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx new file mode 100644 index 00000000..80f0cbbb --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx @@ -0,0 +1,44 @@ +import VitalityTable from '../../../commons/components/VitalityTable'; +import { + buildCommonTableColumns, + buildCommonTableDataSource, +} from '../../../commons/config/VitalityTableConfig'; +import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; +import RefineTableWrapper from '../../../infrastructure/components/RefineTableWrapper'; +import DeleteAccount from '../apis/deleteAccount'; +import GetAccountListByPageAndParams from '../apis/getAccountListByPageAndParams'; + +export default function VitalityAccountListView() { + const { translate } = useTranslation(); + + return ( + ( + + )} + /> + ); +} diff --git a/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx b/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx index 1b824321..64e41f68 100644 --- a/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx +++ b/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx @@ -8,7 +8,7 @@ import { import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; import RefineTableWrapper from '../../../infrastructure/components/RefineTableWrapper'; import DeleteApplication from '../apis/deleteApplication'; -import GetApplicationList from '../apis/getApplicationList'; +import GetApplicationList from '../../../commons/apis/getApplicationList'; export default function VitalityApplicationListView() { const { translate } = useTranslation(); diff --git a/src/v6y-front-bo/src/infrastructure/adapters/api/.gitkeep b/src/v6y-front-bo/src/infrastructure/adapters/api/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts b/src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts new file mode 100644 index 00000000..58672b55 --- /dev/null +++ b/src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts @@ -0,0 +1,27 @@ +import { GraphQLClient } from 'graphql-request'; +import Cookies from 'js-cookie' + +export const gqlClient = new GraphQLClient(process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, { + fetch: (url: RequestInfo | URL, options?: RequestInit) => { + return fetch(url, { + ...options, + headers: { + ...(options?.headers || {}), + Authorization: `Bearer ${JSON.parse(Cookies.get('auth') || '{}')?.token}`, + }, + }); + }, +}); + +type GqlClientRequestParams = { + gqlQueryPath?: string; + gqlQueryParams?: Record; +}; + +export const gqlClientRequest = ({ + gqlQueryPath, + gqlQueryParams +}: GqlClientRequestParams): Promise => gqlClient.request( + gqlQueryPath, + gqlQueryParams +); \ No newline at end of file diff --git a/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx b/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx index e352149b..10ee7367 100644 --- a/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx +++ b/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx @@ -2,9 +2,10 @@ import { Create, useForm } from '@refinedev/antd'; import { Form } from 'antd'; -import GraphqlClientRequest from 'graphql-request'; import { FormCreateOptionsType } from '../types/FormType'; +import { gqlClientRequest } from '../adapters/api/GraphQLClient'; +import { BaseRecord, GetOneResponse } from '@refinedev/core'; export default function RefineCreateWrapper({ title, @@ -14,17 +15,16 @@ export default function RefineCreateWrapper({ const { form, formProps, saveButtonProps } = useForm({ defaultFormValues: {}, createMutationOptions: { - mutationFn: async () => - GraphqlClientRequest( - process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, - createOptions?.createQuery, - createOptions?.createFormAdapter?.({ + mutationFn: async (): Promise> => + gqlClientRequest({ + gqlQueryPath: createOptions?.createQuery, + gqlQueryParams: createOptions?.createFormAdapter?.({ ...(createOptions?.createQueryParams || {}), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error ...(form?.getFieldsValue() || {}), }) || {}, - ), + }) }, }); diff --git a/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx b/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx index d9ef0f88..412bdfc6 100644 --- a/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx +++ b/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx @@ -3,10 +3,10 @@ import { Edit, useForm } from '@refinedev/antd'; import { BaseRecord, GetOneResponse } from '@refinedev/core'; import { Form } from 'antd'; -import GraphqlClientRequest from 'graphql-request'; import { useEffect } from 'react'; import { FormWrapperProps } from '../types/FormType'; +import { gqlClientRequest } from '../adapters/api/GraphQLClient'; export default function RefineEditWrapper({ title, @@ -16,29 +16,25 @@ export default function RefineEditWrapper({ }: FormWrapperProps) { const { form, formProps, saveButtonProps, query } = useForm({ queryOptions: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error queryKey: [queryOptions?.resource, queryOptions?.queryParams], queryFn: async (): Promise> => - GraphqlClientRequest( - process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, - queryOptions?.query as string, - queryOptions?.queryParams, - ), + gqlClientRequest({ + gqlQueryPath: queryOptions?.query, + gqlQueryParams: queryOptions?.queryParams + }), }, updateMutationOptions: { mutationKey: [mutationOptions?.resource, mutationOptions?.editQuery], - mutationFn: async () => - GraphqlClientRequest( - process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, - mutationOptions?.editQuery, - mutationOptions?.editFormAdapter?.({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + mutationFn: async (): Promise> => + gqlClientRequest({ + gqlQueryPath: mutationOptions?.editQuery, + gqlQueryParams: mutationOptions?.editFormAdapter?.({ ...(mutationOptions?.editQueryParams || {}), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error ...(form?.getFieldsValue() || {}), }) || {}, - ), + }), }, }); @@ -47,8 +43,6 @@ export default function RefineEditWrapper({ queryOptions?.queryResource as keyof typeof query.data ] as Record | undefined; if (formDetails && Object.keys(formDetails).length) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error form?.setFieldsValue( queryOptions?.queryFormAdapter?.(formDetails as Record) || {}, ); diff --git a/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx b/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx index 4e28e3cb..f909348a 100644 --- a/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx +++ b/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx @@ -3,49 +3,71 @@ import { Edit, useForm, useSelect } from '@refinedev/antd'; import { BaseRecord, GetOneResponse } from '@refinedev/core'; import { Form } from 'antd'; -import GraphqlClientRequest from 'graphql-request'; import { ReactNode, useEffect } from 'react'; import { FormWrapperProps } from '../types/FormType'; +import { gqlClientRequest } from '../adapters/api/GraphQLClient'; export default function RefineSelectWrapper({ title, queryOptions, mutationOptions, + createOptions, selectOptions, renderSelectOption, }: FormWrapperProps) { - const { form, formProps, saveButtonProps, query } = useForm({ - queryOptions: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - enabled: true, - queryKey: [queryOptions?.resource, queryOptions?.queryParams], - queryFn: async (): Promise> => - GraphqlClientRequest( - process.env.NEXT_PUBLIC_GQL_API_BASE_PATH || '', - queryOptions?.query, - queryOptions?.queryParams, - ), - }, + const formQueryOptions = queryOptions ? { + ...queryOptions, + queryFn: async (): Promise> => + gqlClientRequest({ + gqlQueryPath: queryOptions?.query, + gqlQueryParams: queryOptions?.queryParams + }), + } : {}; + + const formMutationOptions = mutationOptions ? { updateMutationOptions: { - mutationKey: ['update', mutationOptions?.editQuery], - mutationFn: async () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error + mutationKey: [mutationOptions?.editResource, mutationOptions?.editQuery], + mutationFn: async (): Promise> => { const { editQuery, editFormAdapter, editQueryParams } = mutationOptions; - return GraphqlClientRequest( - process.env.NEXT_PUBLIC_GQL_API_BASE_PATH || '', - editQuery, - editFormAdapter?.({ + return gqlClientRequest({ + gqlQueryPath: editQuery, + gqlQueryParams: editFormAdapter?.({ ...(editQueryParams || {}), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error ...(form?.getFieldsValue() || {}), }) || {}, - ); + }); + }, + }, + } : {}; + + const formCreateOptions = createOptions ? { + createMutationOptions: { + mutationKey: [createOptions?.createResource, createOptions?.createQuery], + mutationFn: async (): Promise> => { + const { createQuery, createFormAdapter, createQueryParams } = createOptions; + console.log(createOptions) + return gqlClientRequest({ + gqlQueryPath: createQuery, + gqlQueryParams: createFormAdapter?.({ + ...(createQueryParams || {}), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ...(form?.getFieldsValue() || {}), + }) || {}, + }); }, }, + } : {}; + + + const { form, formProps, saveButtonProps, query } = useForm({ + ...formQueryOptions, + ...formMutationOptions, + ...formCreateOptions, + defaultFormValues: {}, }); const { query: selectQueryResult } = useSelect({ @@ -66,9 +88,11 @@ export default function RefineSelectWrapper({ } }, [form, query?.data, queryOptions]); + const isLoading = selectQueryResult?.isLoading || (queryOptions && query?.isLoading) + return ( > => - GraphqlClientRequest( - process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, - queryOptions?.query as string, - queryOptions?.queryParams, - ), + gqlClientRequest({ + gqlQueryPath: queryOptions?.query, + gqlQueryParams: queryOptions?.queryParams + }), }, }); diff --git a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts index a81deeef..f21462e5 100644 --- a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts +++ b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts @@ -1,18 +1,10 @@ import type { AuthProvider } from '@refinedev/core'; -import dataProvider, { GraphQLClient, graphqlWS, liveProvider } from '@refinedev/graphql'; +import dataProvider, { graphqlWS, liveProvider } from '@refinedev/graphql'; import Cookies from 'js-cookie'; -const dataClient = new GraphQLClient(process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, { - fetch: (url: RequestInfo | URL, options?: RequestInit) => { - return fetch(url, { - ...options, - headers: { - ...(options?.headers || {}), - Authorization: `Bearer ${JSON.parse(Cookies.get('auth') || '{}')?.token}`, - }, - }); - }, -}); +import { gqlClient } from '../adapters/api/GraphQLClient'; + +const dataClient = gqlClient; const wsClient = graphqlWS.createClient({ url: process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, @@ -22,7 +14,6 @@ export const gqlDataProvider = dataProvider(dataClient); export const gqlLiveProvider = liveProvider(wsClient); - export const gqlAuthProvider: AuthProvider = { login: async ({ email, password }) => { try { diff --git a/src/v6y-front-bo/src/infrastructure/types/FormType.ts b/src/v6y-front-bo/src/infrastructure/types/FormType.ts index 358f890b..9a9bb946 100644 --- a/src/v6y-front-bo/src/infrastructure/types/FormType.ts +++ b/src/v6y-front-bo/src/infrastructure/types/FormType.ts @@ -14,11 +14,19 @@ export interface FormQueryOptionsType { export interface FormMutationOptionsType { resource?: string; + editResource?: string; editQuery: string; editQueryParams?: Record; editFormAdapter?: (data: unknown) => Variables; } +export interface FormCreateOptionsType { + createResource?: string; + createQuery: string; + createQueryParams?: Record; + createFormAdapter?: (data: unknown) => Variables; +} + export interface FormCreateOptionsType { title: string | ReactNode; createOptions: { @@ -33,6 +41,7 @@ export interface FormWrapperProps { title?: string | ReactNode; queryOptions?: FormQueryOptionsType; mutationOptions?: FormMutationOptionsType; + createOptions?: FormCreateOptionsType; formItems?: ReactNode[]; selectOptions?: { resource: string; From b94ff3b17f6b9837033a7895a886540e8d368f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Tue, 12 Nov 2024 14:07:49 +0100 Subject: [PATCH 05/14] [FIX]: Account edit and creation fix. Fixed the application list select. --- .../application/ApplicationQueries.ts | 9 ++- src/v6y-commons/src/types/SearchQueryType.ts | 2 +- .../public/locales/en/common.json | 18 ++++- .../public/locales/fr/common.json | 18 ++++- .../src/app/v6y-accounts/edit/[id]/page.tsx | 9 +++ .../commons/config/VitalityDetailsConfig.tsx | 2 +- .../src/commons/config/VitalityFormConfig.tsx | 16 ++++- .../apis/getAccountDetailsByParams.ts | 3 +- .../components/VitalityAccountCreateView.tsx | 12 +--- .../components/VitalityAccountDetailsView.tsx | 60 ++++++++++++++++ .../components/VitalityAccountEditView.tsx | 70 +++++++++++++++++++ .../components/RefineSelectWrapper.tsx | 33 +++++---- 12 files changed, 219 insertions(+), 33 deletions(-) create mode 100644 src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountDetailsView.tsx create mode 100644 src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx diff --git a/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts b/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts index 13569a4d..4d8a112b 100644 --- a/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts +++ b/src/v6y-bff/src/resolvers/application/ApplicationQueries.ts @@ -150,7 +150,8 @@ const getApplicationListByPageAndParams = async (_: unknown, args: SearchQueryTy ); AppLogger.info(`[ApplicationQueries - getApplicationListByPageAndParams] limit : ${limit}`); AppLogger.info( - `[ApplicationQueries - getApplicationListByPageAndParams] keywords : ${keywords?.join?.(',') || '' + `[ApplicationQueries - getApplicationListByPageAndParams] keywords : ${ + keywords?.join?.(',') || '' }`, ); AppLogger.info( @@ -216,7 +217,8 @@ const getApplicationStatsByParams = async (_: unknown, args: SearchQueryType) => const { keywords } = args || {}; AppLogger.info( - `[ApplicationQueries - getApplicationStatsByParams] keywords : ${keywords?.join?.(',') || '' + `[ApplicationQueries - getApplicationStatsByParams] keywords : ${ + keywords?.join?.(',') || '' }`, ); @@ -245,7 +247,8 @@ const getApplicationTotalByParams = async (_: unknown, args: SearchQueryType) => const { keywords, searchText } = args || {}; AppLogger.info( - `[ApplicationQueries - getApplicationTotalByParams] keywords : ${keywords?.join?.(',') || '' + `[ApplicationQueries - getApplicationTotalByParams] keywords : ${ + keywords?.join?.(',') || '' }`, ); AppLogger.info( diff --git a/src/v6y-commons/src/types/SearchQueryType.ts b/src/v6y-commons/src/types/SearchQueryType.ts index 1acef6d2..9281d003 100644 --- a/src/v6y-commons/src/types/SearchQueryType.ts +++ b/src/v6y-commons/src/types/SearchQueryType.ts @@ -5,5 +5,5 @@ export interface SearchQueryType { start?: number; limit?: number; where?: { _id: number; id?: string }; - sort?: string; + sort?: string | string[]; } diff --git a/src/v6y-front-bo/public/locales/en/common.json b/src/v6y-front-bo/public/locales/en/common.json index 635bb7b9..7c960fa3 100644 --- a/src/v6y-front-bo/public/locales/en/common.json +++ b/src/v6y-front-bo/public/locales/en/common.json @@ -263,7 +263,23 @@ "v6y-accounts":"Accounts", "titles" : { "list": "Accounts", - "create": "Create Account" + "create": "Create Account", + "edit": "Edit Account", + "show": "Show Account" + }, + "fields": { + "account-username":{ + "label": "Username" + }, + "account-email":{ + "label": "Email" + }, + "account-role":{ + "label": "Role" + }, + "account-applications":{ + "label": "Applications" + } } }, "v6y-faqs": { diff --git a/src/v6y-front-bo/public/locales/fr/common.json b/src/v6y-front-bo/public/locales/fr/common.json index 635bb7b9..7c960fa3 100644 --- a/src/v6y-front-bo/public/locales/fr/common.json +++ b/src/v6y-front-bo/public/locales/fr/common.json @@ -263,7 +263,23 @@ "v6y-accounts":"Accounts", "titles" : { "list": "Accounts", - "create": "Create Account" + "create": "Create Account", + "edit": "Edit Account", + "show": "Show Account" + }, + "fields": { + "account-username":{ + "label": "Username" + }, + "account-email":{ + "label": "Email" + }, + "account-role":{ + "label": "Role" + }, + "account-applications":{ + "label": "Applications" + } } }, "v6y-faqs": { diff --git a/src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx b/src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx new file mode 100644 index 00000000..b8926821 --- /dev/null +++ b/src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +import * as React from 'react'; +import VitalityAccountEditView from '../../../../features/v6y-accounts/components/VitalityAccountEditView'; + +export default function VitalityAccountEditPage() { + + return ; +} diff --git a/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx b/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx index e9b87578..6fa6e4b8 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx +++ b/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx @@ -26,7 +26,7 @@ export const formatAccountDetails = ( [translate('v6y-accounts.fields.account-username.label') || '']: details.username, [translate('v6y-accounts.fields.account-email.label') || '']: details.email, [translate('v6y-accounts.fields.account-role.label') || '']: details.role, - [translate('v6y-accounts.fields.account-applications.label') || '']: details.applications, + [translate('v6y-accounts.fields.account-applications.label') || '']: details.applications?.join(', '), }; } diff --git a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx index 8e97e753..ae2f135f 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx +++ b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx @@ -754,6 +754,14 @@ export const deprecatedDependencyCreateOrEditFormOutputAdapter = ( }, }); +export const accountCreateOrEditFormInAdapter = (params: Record) => ({ + _id: params?._id, + 'account-email': params?.['email'], + 'account-username': params?.['username'], + 'account-role': params?.['role'], + 'account-password': params?.['password'], + 'account-applications': params?.['applications'], +}); export const accountCreateOrEditFormOutputAdapter = (params: Record) => ({ accountInput: { @@ -766,7 +774,11 @@ export const accountCreateOrEditFormOutputAdapter = (params: Record { +export const accountCreateEditItems = (translate: TranslateType, role: string, applications: ApplicationType[]) => { + const applicationsValues = applications?.map((application) => ({ + value: application._id, + label: application.name, + })); const roles = [ { label: translate('pages.createAccount.fields.account-role.options.admin'), value: 'ADMIN' }, @@ -784,7 +796,7 @@ export const accountCreateEditItems = (translate: TranslateType, role: string, a key={translate('pages.createAccount.fields.applications-group')} groupTitle={translate('pages.createAccount.fields.applications-group')} items={accountApplicationsFormItems(translate)} - selectOptions={applications} + selectOptions={applicationsValues} />, ]; }; diff --git a/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts index 553331cb..25033abc 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts +++ b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts @@ -6,7 +6,8 @@ const GetAccountDetailsByParams = gql` _id username email - role + role, + applications } } `; diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx index 10b313fb..314029ec 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx @@ -6,6 +6,7 @@ import { accountCreateOrEditFormOutputAdapter, } from '../../../commons/config/VitalityFormConfig'; import RefineSelectWrapper from '../../../infrastructure/components/RefineSelectWrapper'; +import VitalityEmptyView from '../../../commons/components/VitalityEmptyView'; import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; import CreateOrEditAccount from '../apis/createOrEditAccount'; import { useRole } from '../../../commons/hooks/useRole'; @@ -25,8 +26,7 @@ export default function VitalityAccountCreateView() { console.log(userRole); if (!userRole) { - // fallback view (EmptyView) - return null; + return ; } return ( @@ -47,13 +47,7 @@ export default function VitalityAccountCreateView() { query: GetApplicationList, }} renderSelectOption={(applications: ApplicationType[]) => { - // TODO: dans config - const applicationsValues = applications?.map((application: ApplicationType) => ({ - value: application._id, - label: application.name, - })); - - return accountCreateEditItems(translate, userRole, applicationsValues) + return accountCreateEditItems(translate, userRole, applications) }} /> ); diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountDetailsView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountDetailsView.tsx new file mode 100644 index 00000000..f85083fb --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountDetailsView.tsx @@ -0,0 +1,60 @@ +import { HttpError, useParsed } from '@refinedev/core'; +import { AccountType } from '@v6y/commons'; +import { Typography } from 'antd'; +import * as React from 'react'; + +import VitalityDetailsView from '../../../commons/components/VitalityDetailsView'; +import { formatAccountDetails } from '../../../commons/config/VitalityDetailsConfig'; +import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; +import RefineShowWrapper from '../../../infrastructure/components/RefineShowWrapper'; +import Matcher from '../../../infrastructure/utils/Matcher'; +import GetAccountDetailsByParams from '../apis/getAccountDetailsByParams'; + +export default function VitalityAccountDetailsView() { + const { translate } = useTranslation(); + + const { id } = useParsed(); + + const renderShowView: ({ + data, + error, + }: { + data?: unknown; + error: HttpError | string | undefined; + }) => React.JSX.Element = ({ data, error }) => { + const errorMessage = Matcher() + .with( + () => (error as HttpError)?.message?.length > 0, + () => (error as HttpError)?.message, + ) + .with( + () => typeof error === 'string', + () => error, + ) + .otherwise(() => ''); + return ( + + ); + }; + + return ( + + {translate('v6y-account.titles.show')} + + } + queryOptions={{ + resource: 'getAccountDetailsByParams', + query: GetAccountDetailsByParams, + queryParams: { + _id: parseInt(id as string, 10), + }, + }} + renderShowView={renderShowView} + /> + ); +} diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx new file mode 100644 index 00000000..1e228555 --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { Typography } from 'antd'; +import { + accountCreateEditItems, + accountCreateOrEditFormInAdapter, + accountCreateOrEditFormOutputAdapter, +} from '../../../commons/config/VitalityFormConfig'; +import RefineSelectWrapper from '../../../infrastructure/components/RefineSelectWrapper'; +import VitalityEmptyView from '../../../commons/components/VitalityEmptyView'; +import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; +import CreateOrEditAccount from '../apis/createOrEditAccount'; +import { useRole } from '../../../commons/hooks/useRole'; + +import { ApplicationType } from '@v6y/commons/src/types/ApplicationType'; +import { useEffect, useState } from 'react'; +import { useParsed } from '@refinedev/core'; +import GetApplicationList from '../../../commons/apis/getApplicationList'; +import GetAccountDetailsByParams from '../apis/getAccountDetailsByParams'; +import React from 'react'; + +export default function VitalityAccountEditView() { + const { translate } = useTranslation(); + const [userRole, setUserRole] = useState(null); + const { getRole } = useRole(); + const { id } = useParsed(); + + useEffect(() => { + setUserRole(getRole()); + }, [getRole]); + + if (!userRole) { + return ; + } + + return ( + + {translate('v6y-accounts.titles.edit')} + + } + queryOptions={{ + queryFormAdapter: accountCreateOrEditFormInAdapter, + query: GetAccountDetailsByParams, + queryResource: 'getAccountDetailsByParams', + queryParams: { + _id: parseInt(id as string, 10), + }, + }} + + mutationOptions={{ + editResource: 'createOrEditAccount', + editFormAdapter: accountCreateOrEditFormOutputAdapter, + editQuery: CreateOrEditAccount, + editQueryParams: { + _id: parseInt(id as string, 10), + }, + }} + selectOptions={{ + resource: 'getApplicationList', + query: GetApplicationList, + }} + + renderSelectOption={(applications: ApplicationType[]) => { + return accountCreateEditItems(translate, userRole, applications) + }} + /> + ); +} \ No newline at end of file diff --git a/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx b/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx index f909348a..a65d793e 100644 --- a/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx +++ b/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx @@ -17,12 +17,15 @@ export default function RefineSelectWrapper({ renderSelectOption, }: FormWrapperProps) { const formQueryOptions = queryOptions ? { - ...queryOptions, - queryFn: async (): Promise> => - gqlClientRequest({ - gqlQueryPath: queryOptions?.query, - gqlQueryParams: queryOptions?.queryParams - }), + queryOptions: { + enabled: true, + queryKey: [queryOptions?.resource, queryOptions?.queryParams], + queryFn: async (): Promise> => + gqlClientRequest({ + gqlQueryPath: queryOptions?.query, + gqlQueryParams: queryOptions?.queryParams + }), + } } : {}; const formMutationOptions = mutationOptions ? { @@ -48,7 +51,6 @@ export default function RefineSelectWrapper({ mutationKey: [createOptions?.createResource, createOptions?.createQuery], mutationFn: async (): Promise> => { const { createQuery, createFormAdapter, createQueryParams } = createOptions; - console.log(createOptions) return gqlClientRequest({ gqlQueryPath: createQuery, gqlQueryParams: createFormAdapter?.({ @@ -71,12 +73,15 @@ export default function RefineSelectWrapper({ }); const { query: selectQueryResult } = useSelect({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - resource: selectOptions?.resource, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - meta: { gqlQuery: selectOptions?.query }, + queryOptions: { + enabled: true, + queryKey: [selectOptions?.resource, selectOptions?.queryParams], + queryFn: async (): Promise> => + gqlClientRequest({ + gqlQueryPath: selectOptions?.query, + gqlQueryParams: selectOptions?.queryParams + }), + } }); useEffect(() => { @@ -98,7 +103,7 @@ export default function RefineSelectWrapper({ saveButtonProps={saveButtonProps} >
- {renderSelectOption?.(selectQueryResult?.data?.data)?.map( + {renderSelectOption?.(selectQueryResult?.data?.[selectOptions?.resource])?.map( (item: ReactNode) => item, )}
From 7865ecf9fea18e40cb6eb9876ca175954a436a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Wed, 13 Nov 2024 14:48:06 +0100 Subject: [PATCH 06/14] [FIX]: Password field was mandatory when editing an account --- .../src/resolvers/account/AccountMutations.ts | 31 ++++++++++++++----- .../types/account/AccountCreateOrEditInput.ts | 2 +- src/v6y-commons/src/types/AccountType.ts | 2 +- .../src/commons/config/VitalityFormConfig.tsx | 10 +++--- .../components/VitalityAccountEditView.tsx | 2 +- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/v6y-bff/src/resolvers/account/AccountMutations.ts b/src/v6y-bff/src/resolvers/account/AccountMutations.ts index 96904ba0..7cf60c20 100644 --- a/src/v6y-bff/src/resolvers/account/AccountMutations.ts +++ b/src/v6y-bff/src/resolvers/account/AccountMutations.ts @@ -42,14 +42,25 @@ const createOrEditAccount = async ( AppLogger.info(`[AccountMutations - createOrEditAccount] applications : ${applications}`); if (_id) { - const editedAccount = await AccountProvider.editAccount({ - _id, - username, - password: await hashPassword(password), - email, - role, - applications, - }); + let editedAccount = null; + if (!password) { + editedAccount = await AccountProvider.editAccount({ + _id, + username, + email, + role, + applications, + }); + } else { + editedAccount = await AccountProvider.editAccount({ + _id, + username, + password: await hashPassword(password), + email, + role, + applications, + }); + } if (!editedAccount || !editedAccount._id) { throw new Error('Invalid account'); @@ -64,6 +75,10 @@ const createOrEditAccount = async ( }; } + if (!password) { + throw new Error('Password is required'); + } + const user = await AccountProvider.getAccountDetailsByParams({ email }); if (user) { throw new Error('User already exists with this email'); diff --git a/src/v6y-bff/src/types/account/AccountCreateOrEditInput.ts b/src/v6y-bff/src/types/account/AccountCreateOrEditInput.ts index c2d5ee42..d5dd4ede 100644 --- a/src/v6y-bff/src/types/account/AccountCreateOrEditInput.ts +++ b/src/v6y-bff/src/types/account/AccountCreateOrEditInput.ts @@ -10,7 +10,7 @@ const AccountCreateOrEditInput = ` username: String! """ Account Password """ - password: String! + password: String """ Account Role """ role: String! diff --git a/src/v6y-commons/src/types/AccountType.ts b/src/v6y-commons/src/types/AccountType.ts index 0334de8c..0aeb1de4 100644 --- a/src/v6y-commons/src/types/AccountType.ts +++ b/src/v6y-commons/src/types/AccountType.ts @@ -11,7 +11,7 @@ export interface AccountInputType { _id?: number; username: string; email: string; - password: string; + password?: string; role: string; applications?: number[]; } diff --git a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx index ae2f135f..8e556b6e 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx +++ b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx @@ -774,7 +774,7 @@ export const accountCreateOrEditFormOutputAdapter = (params: Record { +export const accountCreateEditItems = (translate: TranslateType, role: string, applications: ApplicationType[], edit: boolean = false) => { const applicationsValues = applications?.map((application) => ({ value: application._id, label: application.name, @@ -789,7 +789,7 @@ export const accountCreateEditItems = (translate: TranslateType, role: string, a , { +export const accountInfosFormItems = (translate: TranslateType, role: string, edit: boolean) => { return [ { @@ -857,12 +857,12 @@ export const accountInfosFormItems = (translate: TranslateType, role: string) => type: 'password', label: translate('pages.createAccount.fields.account-password.label'), placeholder: translate('pages.createAccount.fields.account-password.placeholder'), - rules: [ + rules: !edit ? [ { required: true, message: translate('pages.createAccount.fields.account-password.error'), }, - ], + ] : [], }, ]; } diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx index 1e228555..d959bb5e 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx @@ -63,7 +63,7 @@ export default function VitalityAccountEditView() { }} renderSelectOption={(applications: ApplicationType[]) => { - return accountCreateEditItems(translate, userRole, applications) + return accountCreateEditItems(translate, userRole, applications, true) }} /> ); From 8b33bc561ba7303d78e1ec6aa61e15360ef0d8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Mon, 18 Nov 2024 10:29:09 +0100 Subject: [PATCH 07/14] [FEATURE]: Added update password feature --- .gitignore | 3 +- .../src/app/forgot-password/page.tsx | 25 ------- .../src/app/update-password/layout.tsx | 23 +++++++ .../src/app/update-password/page.tsx | 2 +- .../config/VitalityNavigationConfig.ts | 7 ++ .../VitalityAuthForgotPasswordView.tsx | 21 ------ .../components/VitalityAuthLoginView.tsx | 18 ++--- .../VitalityAuthUpdatePasswordView.tsx | 2 +- src/v6y-front/src/app/login/page.tsx | 5 ++ .../layout/VitalityPageHeaderMenu.tsx | 39 ++++------- .../commons/config/VitalityCommonConfig.tsx | 65 +++++++++++++++++++ .../commons/config/VitalityNavigationPaths.ts | 1 + .../src/features/auth/VitalityLoginView.tsx | 7 ++ .../src/features/auth/api/loginAccount.ts | 13 ++++ .../auth/components/VitalityLoginForm.tsx | 63 ++++++++++++++++++ 15 files changed, 205 insertions(+), 89 deletions(-) delete mode 100644 src/v6y-front-bo/src/app/forgot-password/page.tsx create mode 100644 src/v6y-front-bo/src/app/update-password/layout.tsx delete mode 100644 src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx rename src/v6y-front-bo/src/features/v6y-auth/{ => components}/VitalityAuthUpdatePasswordView.tsx (82%) create mode 100644 src/v6y-front/src/app/login/page.tsx create mode 100644 src/v6y-front/src/features/auth/VitalityLoginView.tsx create mode 100644 src/v6y-front/src/features/auth/api/loginAccount.ts create mode 100644 src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx diff --git a/.gitignore b/.gitignore index 864b849c..653b0681 100644 --- a/.gitignore +++ b/.gitignore @@ -140,4 +140,5 @@ database/psql/** **/jest-stare/** **/v6y-logs/** -.vscode/ \ No newline at end of file +.vscode/ +.DS_Store \ No newline at end of file diff --git a/src/v6y-front-bo/src/app/forgot-password/page.tsx b/src/v6y-front-bo/src/app/forgot-password/page.tsx deleted file mode 100644 index 7f41d2ab..00000000 --- a/src/v6y-front-bo/src/app/forgot-password/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { redirect } from 'next/navigation'; -import * as React from 'react'; - -import { VitalityAuthForgotPasswordView } from '../../features/v6y-auth/VitalityAuthForgotPasswordView'; -import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; - -export default async function ForgotPassword() { - const data = await getData(); - - if (data.authenticated) { - redirect(data?.redirectTo || '/'); - } - - return ; -} - -async function getData() { - const { authenticated, redirectTo, error } = await AuthServerProvider.check(); - - return { - authenticated, - redirectTo, - error, - }; -} diff --git a/src/v6y-front-bo/src/app/update-password/layout.tsx b/src/v6y-front-bo/src/app/update-password/layout.tsx new file mode 100644 index 00000000..7cd9dc7f --- /dev/null +++ b/src/v6y-front-bo/src/app/update-password/layout.tsx @@ -0,0 +1,23 @@ +import { redirect } from 'next/navigation'; +import { ReactNode } from 'react'; + +import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; + +export default async function Layout({ children }: { children: ReactNode }) { + const data = await getData(); + + if (!data.authenticated) { + return redirect(data?.redirectTo || '/login'); + } + + return children; +} + +async function getData() { + const { authenticated, redirectTo } = await AuthServerProvider.check(); + + return { + authenticated, + redirectTo, + }; +} diff --git a/src/v6y-front-bo/src/app/update-password/page.tsx b/src/v6y-front-bo/src/app/update-password/page.tsx index 4c891e46..2e160581 100644 --- a/src/v6y-front-bo/src/app/update-password/page.tsx +++ b/src/v6y-front-bo/src/app/update-password/page.tsx @@ -1,7 +1,7 @@ import { redirect } from 'next/navigation'; import * as React from 'react'; -import { VitalityAuthUpdatePasswordView } from '../../features/v6y-auth/VitalityAuthUpdatePasswordView'; +import { VitalityAuthUpdatePasswordView } from '../../features/v6y-auth/components/VitalityAuthUpdatePasswordView'; import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; export default async function UpdatePassword() { diff --git a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts index a3fceeaa..3f0309a5 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts +++ b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts @@ -76,4 +76,11 @@ export const VitalityRoutes = [ canDelete: true, }, }, + { + name: "updatePassword", + list: "/update-password", + meta: { + canDelete: false + } + } ]; diff --git a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx b/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx deleted file mode 100644 index 5f6d8b5c..00000000 --- a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; - -import { AuthPage as AuthPageBase } from '@refinedev/antd'; -import { Typography } from 'antd'; - -import { useTranslation } from '../../infrastructure/adapters/translation/TranslationAdapter'; - -export const VitalityAuthForgotPasswordView = () => { - const { translate } = useTranslation(); - - return ( - - {translate('v6y-authentication.title')} - - } - /> - ); -}; diff --git a/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx index 1071de76..e9b6ee28 100644 --- a/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx +++ b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx @@ -1,7 +1,7 @@ 'use client'; import { AuthPage as AuthPageBase } from '@refinedev/antd'; -import { Checkbox, Form, Typography } from 'antd'; +import { Typography } from 'antd'; import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; @@ -11,23 +11,15 @@ export const VitalityAuthLoginView = () => { return ( {translate('v6y-authentication.title')} } - rememberMe={ -
- - Custom remember me - -
- } /> + ); }; diff --git a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthUpdatePasswordView.tsx b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthUpdatePasswordView.tsx similarity index 82% rename from src/v6y-front-bo/src/features/v6y-auth/VitalityAuthUpdatePasswordView.tsx rename to src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthUpdatePasswordView.tsx index a1d373c1..e54baaf0 100644 --- a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthUpdatePasswordView.tsx +++ b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthUpdatePasswordView.tsx @@ -3,7 +3,7 @@ import { AuthPage as AuthPageBase } from '@refinedev/antd'; import { Typography } from 'antd'; -import { useTranslation } from '../../infrastructure/adapters/translation/TranslationAdapter'; +import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; export const VitalityAuthUpdatePasswordView = () => { const { translate } = useTranslation(); diff --git a/src/v6y-front/src/app/login/page.tsx b/src/v6y-front/src/app/login/page.tsx new file mode 100644 index 00000000..4c92576c --- /dev/null +++ b/src/v6y-front/src/app/login/page.tsx @@ -0,0 +1,5 @@ +import VitalityLoginView from '../../features/auth/VitalityLoginView'; + +export default async function VitalityLoginPage() { + return ; +} diff --git a/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx b/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx index 7bc1548d..b6030ae1 100644 --- a/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx +++ b/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx @@ -1,35 +1,18 @@ 'use client'; -import { NotificationOutlined, QuestionOutlined } from '@ant-design/icons'; -import { Menu, Typography } from 'antd'; -import Link from 'next/link'; +import { Menu } from 'antd'; import * as React from 'react'; import { useState } from 'react'; -import VitalityNavigationPaths from '../../config/VitalityNavigationPaths'; - -const VITALITY_HEADER_MENU_ITEMS = [ - { - key: 'notification', - icon: , - label: ( - - Notifications - - ), - }, - { - key: 'FAQ', - icon: , - label: ( - - FAQ - - ), - }, -]; +//TEMPORARY +const useLogin = () => [false]; + +import { buildVitalityHeaderMenuItems } from '@/commons/config/VitalityCommonConfig'; + const VitalityPageHeaderMenu = () => { + + const [isLogged] = useLogin(); const [currentSelectedMenu, setCurrentSelectedMenu] = useState('mail'); const onMenuClicked = (event: { key: string }) => { @@ -39,11 +22,13 @@ const VitalityPageHeaderMenu = () => { return ( ); }; -export default VitalityPageHeaderMenu; + + +export default VitalityPageHeaderMenu; \ No newline at end of file diff --git a/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx b/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx index 9f347aa5..5b6236d7 100644 --- a/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx +++ b/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx @@ -5,7 +5,11 @@ import { DashboardOutlined, DislikeOutlined, LikeOutlined, + LoginOutlined, + LogoutOutlined, + NotificationOutlined, PieChartOutlined, + QuestionOutlined, SplitCellsOutlined, ThunderboltOutlined, } from '@ant-design/icons'; @@ -17,6 +21,7 @@ import { ReactNode } from 'react'; import VitalityNavigationPaths from './VitalityNavigationPaths'; import VitalityTerms from './VitalityTerms'; import VitalityTheme from './VitalityTheme'; +import { Typography } from 'antd'; export interface BreadCrumbItemType { currentPage: string; @@ -278,3 +283,63 @@ export const formatApplicationDataSource = ( return newDataSource; }; + +const VITALITY_HEADER_MENU_ITEMS = [ + { + key: 'notification', + icon: , + label: ( + + Notifications + + ), + }, + { + key: 'FAQ', + icon: , + label: ( + + FAQ + + ), + }, +]; + +export const buildVitalityHeaderMenuItems = (isLogged: boolean) => { + if (isLogged) { + return [ + ...VITALITY_HEADER_MENU_ITEMS, + { + key: 'logout', + icon: , + label: ( + { + console.log('logout'); + }} + style={{ textDecoration: 'none' }} + > + Logout + + ), + }, + ]; + } + + return [ + ...VITALITY_HEADER_MENU_ITEMS, + { + key: 'login', + icon: , + label: ( + + Login + + ), + }, + ]; +}; diff --git a/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts b/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts index 98fe077b..3fe4a5c8 100644 --- a/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts +++ b/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts @@ -6,6 +6,7 @@ const VitalityNavigationPaths = { NOTIFICATIONS: '/notifications', APPS_STATS: '/apps-stats', SEARCH: '/search', + LOGIN: '/login' }; export default VitalityNavigationPaths; diff --git a/src/v6y-front/src/features/auth/VitalityLoginView.tsx b/src/v6y-front/src/features/auth/VitalityLoginView.tsx new file mode 100644 index 00000000..f0481460 --- /dev/null +++ b/src/v6y-front/src/features/auth/VitalityLoginView.tsx @@ -0,0 +1,7 @@ +'use client'; + +import VitalityLoginForm from './components/VitalityLoginForm'; + +export default function VitalityFaqListPage() { + return ; +} diff --git a/src/v6y-front/src/features/auth/api/loginAccount.ts b/src/v6y-front/src/features/auth/api/loginAccount.ts new file mode 100644 index 00000000..93b4f7c9 --- /dev/null +++ b/src/v6y-front/src/features/auth/api/loginAccount.ts @@ -0,0 +1,13 @@ +import { gql } from 'graphql-request'; + +const LoginAccount = gql` + query loginAccount { + loginAccount { + _id + role + token + } + } +`; + +export default LoginAccount; diff --git a/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx b/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx new file mode 100644 index 00000000..41a1979d --- /dev/null +++ b/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx @@ -0,0 +1,63 @@ +import { Button, Checkbox, Form, Input } from 'antd'; +import type { FormProps } from 'antd'; + +type FieldType = { + email?: string; + password?: string; + remember?: string; +}; + +const onFinish: FormProps['onFinish'] = (values) => { + console.log('Success:', values); +}; + +const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { + console.log('Failed:', errorInfo); +}; + +const VitalityLoginForm = () => { + return ( +
+ + label="Email" + name="email" + rules={[{ required: true, message: 'Please input your email!' }]} + > + + + + + label="Password" + name="password" + rules={[{ required: true, message: 'Please input your password!' }]} + > + + + + + name="remember" + valuePropName="checked" + wrapperCol={{ offset: 8, span: 16 }} + > + Remember me + + + + + + + ); +} + +export default VitalityLoginForm; \ No newline at end of file From 568986fe78f01a809273be1c93da04bdd6f37b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Wed, 27 Nov 2024 18:09:09 +0100 Subject: [PATCH 08/14] temp --- .../src/core/__tests__/PassportUtils-test.ts | 55 +++++++++++++++++++ .../auth/components/VitalityLoginForm.tsx | 15 ++--- .../__tests__/VitalityLoginForm-test.tsx | 33 +++++++++++ 3 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/v6y-front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx diff --git a/src/v6y-commons/src/core/__tests__/PassportUtils-test.ts b/src/v6y-commons/src/core/__tests__/PassportUtils-test.ts index 25b31d03..40c71360 100644 --- a/src/v6y-commons/src/core/__tests__/PassportUtils-test.ts +++ b/src/v6y-commons/src/core/__tests__/PassportUtils-test.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines-per-function */ // PassportUtils.tests.ts import { Mock, describe, expect, it, vi } from 'vitest'; @@ -114,4 +115,58 @@ describe('PassportUtils', () => { expect(result).toEqual(account); }); + + it('should return true if the user is ADMIN, false otherwise', () => { + const accountAdmin = { + _id: 15, + email: 'admin@admin.admin', + role: 'ADMIN', + applications: [1, 2, 3], + }; + + const accountSuperAdmin = { + _id: 15, + email: 'superadmin@superadmin.superadmin', + role: 'SUPERADMIN', + applications: [1, 2, 3], + }; + + const accountUser = { + _id: 15, + email: 'user@user.user', + role: 'USER', + applications: [1, 2, 3], + }; + + expect(PassportUtils.isAdmin(accountAdmin)).toBe(true); + expect(PassportUtils.isAdmin(accountSuperAdmin)).toBe(false); + expect(PassportUtils.isAdmin(accountUser)).toBe(false); + }); + + it('should return true if the user is SUPERADMIN, false otherwise', () => { + const accountAdmin = { + _id: 15, + email: 'admin@admin.admin', + role: 'ADMIN', + applications: [1, 2, 3], + }; + + const accountSuperAdmin = { + _id: 15, + email: 'superadmin@superadmin.superadmin', + role: 'SUPERADMIN', + applications: [1, 2, 3], + }; + + const accountUser = { + _id: 15, + email: 'user@user.user', + role: 'USER', + applications: [1, 2, 3], + }; + + expect(PassportUtils.isSuperAdmin(accountAdmin)).toBe(false); + expect(PassportUtils.isSuperAdmin(accountSuperAdmin)).toBe(true); + expect(PassportUtils.isSuperAdmin(accountUser)).toBe(false); + }); }); diff --git a/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx b/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx index 41a1979d..099feab0 100644 --- a/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx +++ b/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx @@ -7,15 +7,16 @@ type FieldType = { remember?: string; }; -const onFinish: FormProps['onFinish'] = (values) => { - console.log('Success:', values); -}; +const VitalityLoginForm = () => { -const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { - console.log('Failed:', errorInfo); -}; + const onFinish: FormProps['onFinish'] = (values) => { + console.log('Success:', values); + }; + + const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { + console.log('Failed:', errorInfo); + }; -const VitalityLoginForm = () => { return (
{ + it('should render the component', () => { + render(); + expect(screen.getByText('Email')).toBeInTheDocument(); + expect(screen.getByText('Password')).toBeInTheDocument(); + expect(screen.getByText('Remember me')).toBeInTheDocument(); + expect(screen.getByText('Submit')).toBeInTheDocument(); + }); + + it('should call onFinish when submitting the form', () => { + const onFinish = vi.fn(); + render(); + expect(onFinish).not.toHaveBeenCalled(); + screen.getByRole('button', { name: /submit/i }).click(); + expect(onFinish).toHaveBeenCalled(); + }); + + it('should call onFinishFailed when submitting the form with empty fields', () => { + const onFinishFailed = vi.fn(); + render(); + expect(onFinishFailed).not.toHaveBeenCalled(); + screen.getByRole('button', { name: /submit/i }).click(); + expect(onFinishFailed).toHaveBeenCalled(); + }); +}); + From b062fe963dfd3baf2368edb41ffa91a0faeab223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Wed, 27 Nov 2024 18:25:07 +0100 Subject: [PATCH 09/14] Revert "[FEATURE]: Added update password feature" This reverts commit 8b33bc561ba7303d78e1ec6aa61e15360ef0d8f9. --- .gitignore | 1 - .../src/app/forgot-password/page.tsx | 25 +++++++ .../src/app/update-password/layout.tsx | 23 ------- .../src/app/update-password/page.tsx | 2 +- .../config/VitalityNavigationConfig.ts | 7 -- .../VitalityAuthForgotPasswordView.tsx | 21 ++++++ .../VitalityAuthUpdatePasswordView.tsx | 2 +- .../components/VitalityAuthLoginView.tsx | 18 +++-- src/v6y-front/src/app/login/page.tsx | 5 -- .../layout/VitalityPageHeaderMenu.tsx | 39 +++++++---- .../commons/config/VitalityCommonConfig.tsx | 65 ------------------- .../commons/config/VitalityNavigationPaths.ts | 1 - .../src/features/auth/VitalityLoginView.tsx | 7 -- .../src/features/auth/api/loginAccount.ts | 13 ---- 14 files changed, 88 insertions(+), 141 deletions(-) create mode 100644 src/v6y-front-bo/src/app/forgot-password/page.tsx delete mode 100644 src/v6y-front-bo/src/app/update-password/layout.tsx create mode 100644 src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx rename src/v6y-front-bo/src/features/v6y-auth/{components => }/VitalityAuthUpdatePasswordView.tsx (82%) delete mode 100644 src/v6y-front/src/app/login/page.tsx delete mode 100644 src/v6y-front/src/features/auth/VitalityLoginView.tsx delete mode 100644 src/v6y-front/src/features/auth/api/loginAccount.ts diff --git a/.gitignore b/.gitignore index dff1e3f5..90da4090 100644 --- a/.gitignore +++ b/.gitignore @@ -140,5 +140,4 @@ database/psql/** **/jest-stare/** **/v6y-logs/** .vscode/ -.DS_Store **/coverage** diff --git a/src/v6y-front-bo/src/app/forgot-password/page.tsx b/src/v6y-front-bo/src/app/forgot-password/page.tsx new file mode 100644 index 00000000..7f41d2ab --- /dev/null +++ b/src/v6y-front-bo/src/app/forgot-password/page.tsx @@ -0,0 +1,25 @@ +import { redirect } from 'next/navigation'; +import * as React from 'react'; + +import { VitalityAuthForgotPasswordView } from '../../features/v6y-auth/VitalityAuthForgotPasswordView'; +import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; + +export default async function ForgotPassword() { + const data = await getData(); + + if (data.authenticated) { + redirect(data?.redirectTo || '/'); + } + + return ; +} + +async function getData() { + const { authenticated, redirectTo, error } = await AuthServerProvider.check(); + + return { + authenticated, + redirectTo, + error, + }; +} diff --git a/src/v6y-front-bo/src/app/update-password/layout.tsx b/src/v6y-front-bo/src/app/update-password/layout.tsx deleted file mode 100644 index 7cd9dc7f..00000000 --- a/src/v6y-front-bo/src/app/update-password/layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { redirect } from 'next/navigation'; -import { ReactNode } from 'react'; - -import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; - -export default async function Layout({ children }: { children: ReactNode }) { - const data = await getData(); - - if (!data.authenticated) { - return redirect(data?.redirectTo || '/login'); - } - - return children; -} - -async function getData() { - const { authenticated, redirectTo } = await AuthServerProvider.check(); - - return { - authenticated, - redirectTo, - }; -} diff --git a/src/v6y-front-bo/src/app/update-password/page.tsx b/src/v6y-front-bo/src/app/update-password/page.tsx index 2e160581..4c891e46 100644 --- a/src/v6y-front-bo/src/app/update-password/page.tsx +++ b/src/v6y-front-bo/src/app/update-password/page.tsx @@ -1,7 +1,7 @@ import { redirect } from 'next/navigation'; import * as React from 'react'; -import { VitalityAuthUpdatePasswordView } from '../../features/v6y-auth/components/VitalityAuthUpdatePasswordView'; +import { VitalityAuthUpdatePasswordView } from '../../features/v6y-auth/VitalityAuthUpdatePasswordView'; import { AuthServerProvider } from '../../infrastructure/providers/AuthServerProvider'; export default async function UpdatePassword() { diff --git a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts index 3f0309a5..a3fceeaa 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts +++ b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts @@ -76,11 +76,4 @@ export const VitalityRoutes = [ canDelete: true, }, }, - { - name: "updatePassword", - list: "/update-password", - meta: { - canDelete: false - } - } ]; diff --git a/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx b/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx new file mode 100644 index 00000000..5f6d8b5c --- /dev/null +++ b/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthForgotPasswordView.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { AuthPage as AuthPageBase } from '@refinedev/antd'; +import { Typography } from 'antd'; + +import { useTranslation } from '../../infrastructure/adapters/translation/TranslationAdapter'; + +export const VitalityAuthForgotPasswordView = () => { + const { translate } = useTranslation(); + + return ( + + {translate('v6y-authentication.title')} + + } + /> + ); +}; diff --git a/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthUpdatePasswordView.tsx b/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthUpdatePasswordView.tsx similarity index 82% rename from src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthUpdatePasswordView.tsx rename to src/v6y-front-bo/src/features/v6y-auth/VitalityAuthUpdatePasswordView.tsx index e54baaf0..a1d373c1 100644 --- a/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthUpdatePasswordView.tsx +++ b/src/v6y-front-bo/src/features/v6y-auth/VitalityAuthUpdatePasswordView.tsx @@ -3,7 +3,7 @@ import { AuthPage as AuthPageBase } from '@refinedev/antd'; import { Typography } from 'antd'; -import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; +import { useTranslation } from '../../infrastructure/adapters/translation/TranslationAdapter'; export const VitalityAuthUpdatePasswordView = () => { const { translate } = useTranslation(); diff --git a/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx index e9b6ee28..1071de76 100644 --- a/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx +++ b/src/v6y-front-bo/src/features/v6y-auth/components/VitalityAuthLoginView.tsx @@ -1,7 +1,7 @@ 'use client'; import { AuthPage as AuthPageBase } from '@refinedev/antd'; -import { Typography } from 'antd'; +import { Checkbox, Form, Typography } from 'antd'; import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; @@ -11,15 +11,23 @@ export const VitalityAuthLoginView = () => { return ( {translate('v6y-authentication.title')} } + rememberMe={ +
+ + Custom remember me + +
+ } /> - ); }; diff --git a/src/v6y-front/src/app/login/page.tsx b/src/v6y-front/src/app/login/page.tsx deleted file mode 100644 index 4c92576c..00000000 --- a/src/v6y-front/src/app/login/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import VitalityLoginView from '../../features/auth/VitalityLoginView'; - -export default async function VitalityLoginPage() { - return ; -} diff --git a/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx b/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx index b6030ae1..7bc1548d 100644 --- a/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx +++ b/src/v6y-front/src/commons/components/layout/VitalityPageHeaderMenu.tsx @@ -1,18 +1,35 @@ 'use client'; -import { Menu } from 'antd'; +import { NotificationOutlined, QuestionOutlined } from '@ant-design/icons'; +import { Menu, Typography } from 'antd'; +import Link from 'next/link'; import * as React from 'react'; import { useState } from 'react'; -//TEMPORARY -const useLogin = () => [false]; - -import { buildVitalityHeaderMenuItems } from '@/commons/config/VitalityCommonConfig'; - +import VitalityNavigationPaths from '../../config/VitalityNavigationPaths'; + +const VITALITY_HEADER_MENU_ITEMS = [ + { + key: 'notification', + icon: , + label: ( + + Notifications + + ), + }, + { + key: 'FAQ', + icon: , + label: ( + + FAQ + + ), + }, +]; const VitalityPageHeaderMenu = () => { - - const [isLogged] = useLogin(); const [currentSelectedMenu, setCurrentSelectedMenu] = useState('mail'); const onMenuClicked = (event: { key: string }) => { @@ -22,13 +39,11 @@ const VitalityPageHeaderMenu = () => { return ( ); }; - - -export default VitalityPageHeaderMenu; \ No newline at end of file +export default VitalityPageHeaderMenu; diff --git a/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx b/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx index 5b6236d7..9f347aa5 100644 --- a/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx +++ b/src/v6y-front/src/commons/config/VitalityCommonConfig.tsx @@ -5,11 +5,7 @@ import { DashboardOutlined, DislikeOutlined, LikeOutlined, - LoginOutlined, - LogoutOutlined, - NotificationOutlined, PieChartOutlined, - QuestionOutlined, SplitCellsOutlined, ThunderboltOutlined, } from '@ant-design/icons'; @@ -21,7 +17,6 @@ import { ReactNode } from 'react'; import VitalityNavigationPaths from './VitalityNavigationPaths'; import VitalityTerms from './VitalityTerms'; import VitalityTheme from './VitalityTheme'; -import { Typography } from 'antd'; export interface BreadCrumbItemType { currentPage: string; @@ -283,63 +278,3 @@ export const formatApplicationDataSource = ( return newDataSource; }; - -const VITALITY_HEADER_MENU_ITEMS = [ - { - key: 'notification', - icon: , - label: ( - - Notifications - - ), - }, - { - key: 'FAQ', - icon: , - label: ( - - FAQ - - ), - }, -]; - -export const buildVitalityHeaderMenuItems = (isLogged: boolean) => { - if (isLogged) { - return [ - ...VITALITY_HEADER_MENU_ITEMS, - { - key: 'logout', - icon: , - label: ( - { - console.log('logout'); - }} - style={{ textDecoration: 'none' }} - > - Logout - - ), - }, - ]; - } - - return [ - ...VITALITY_HEADER_MENU_ITEMS, - { - key: 'login', - icon: , - label: ( - - Login - - ), - }, - ]; -}; diff --git a/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts b/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts index 3fe4a5c8..98fe077b 100644 --- a/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts +++ b/src/v6y-front/src/commons/config/VitalityNavigationPaths.ts @@ -6,7 +6,6 @@ const VitalityNavigationPaths = { NOTIFICATIONS: '/notifications', APPS_STATS: '/apps-stats', SEARCH: '/search', - LOGIN: '/login' }; export default VitalityNavigationPaths; diff --git a/src/v6y-front/src/features/auth/VitalityLoginView.tsx b/src/v6y-front/src/features/auth/VitalityLoginView.tsx deleted file mode 100644 index f0481460..00000000 --- a/src/v6y-front/src/features/auth/VitalityLoginView.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -import VitalityLoginForm from './components/VitalityLoginForm'; - -export default function VitalityFaqListPage() { - return ; -} diff --git a/src/v6y-front/src/features/auth/api/loginAccount.ts b/src/v6y-front/src/features/auth/api/loginAccount.ts deleted file mode 100644 index 93b4f7c9..00000000 --- a/src/v6y-front/src/features/auth/api/loginAccount.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { gql } from 'graphql-request'; - -const LoginAccount = gql` - query loginAccount { - loginAccount { - _id - role - token - } - } -`; - -export default LoginAccount; From 1c6157fc5a4491821bd87366895198a59e88fce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Wed, 27 Nov 2024 18:28:02 +0100 Subject: [PATCH 10/14] [FIX]: removed test --- .../__tests__/VitalityLoginForm-test.tsx | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/v6y-front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx diff --git a/src/v6y-front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx b/src/v6y-front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx deleted file mode 100644 index 00a4f507..00000000 --- a/src/v6y-front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; -import { describe, expect, it, vi } from 'vitest'; -import '@testing-library/jest-dom'; - -import VitalityLoginForm from '../VitalityLoginForm'; - -describe('VitalityLoginForm', () => { - it('should render the component', () => { - render(); - expect(screen.getByText('Email')).toBeInTheDocument(); - expect(screen.getByText('Password')).toBeInTheDocument(); - expect(screen.getByText('Remember me')).toBeInTheDocument(); - expect(screen.getByText('Submit')).toBeInTheDocument(); - }); - - it('should call onFinish when submitting the form', () => { - const onFinish = vi.fn(); - render(); - expect(onFinish).not.toHaveBeenCalled(); - screen.getByRole('button', { name: /submit/i }).click(); - expect(onFinish).toHaveBeenCalled(); - }); - - it('should call onFinishFailed when submitting the form with empty fields', () => { - const onFinishFailed = vi.fn(); - render(); - expect(onFinishFailed).not.toHaveBeenCalled(); - screen.getByRole('button', { name: /submit/i }).click(); - expect(onFinishFailed).toHaveBeenCalled(); - }); -}); - From b25e9698c6abd93c0b079333822a4e3158b38a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Wed, 27 Nov 2024 18:30:32 +0100 Subject: [PATCH 11/14] [FIX]: removed frontend component --- .../auth/components/VitalityLoginForm.tsx | 64 ------------------- 1 file changed, 64 deletions(-) delete mode 100644 src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx diff --git a/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx b/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx deleted file mode 100644 index 099feab0..00000000 --- a/src/v6y-front/src/features/auth/components/VitalityLoginForm.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button, Checkbox, Form, Input } from 'antd'; -import type { FormProps } from 'antd'; - -type FieldType = { - email?: string; - password?: string; - remember?: string; -}; - -const VitalityLoginForm = () => { - - const onFinish: FormProps['onFinish'] = (values) => { - console.log('Success:', values); - }; - - const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { - console.log('Failed:', errorInfo); - }; - - return ( - - - label="Email" - name="email" - rules={[{ required: true, message: 'Please input your email!' }]} - > - - - - - label="Password" - name="password" - rules={[{ required: true, message: 'Please input your password!' }]} - > - - - - - name="remember" - valuePropName="checked" - wrapperCol={{ offset: 8, span: 16 }} - > - Remember me - - - - - - - ); -} - -export default VitalityLoginForm; \ No newline at end of file From 0ad675e8e03b46f62ee4f45bf5f158a97df1ffa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Mon, 2 Dec 2024 17:59:01 +0100 Subject: [PATCH 12/14] [FIX]: moved authorization verification to the AccoutProviders (commons) instead of the AccountMutations (BFF) --- .../src/resolvers/account/AccountMutations.ts | 69 +++++++------------ .../src/database/AccountProvider.ts | 68 +++++++++++++++--- 2 files changed, 83 insertions(+), 54 deletions(-) diff --git a/src/v6y-bff/src/resolvers/account/AccountMutations.ts b/src/v6y-bff/src/resolvers/account/AccountMutations.ts index 8de18e61..9b266e56 100644 --- a/src/v6y-bff/src/resolvers/account/AccountMutations.ts +++ b/src/v6y-bff/src/resolvers/account/AccountMutations.ts @@ -6,8 +6,6 @@ import { AppLogger, PasswordUtils, SearchQueryType, - isAdmin, - isSuperAdmin, } from '@v6y/commons'; const { hashPassword } = PasswordUtils; @@ -24,16 +22,8 @@ const createOrEditAccount = async ( context: { user: AccountType }, ) => { try { - if (!(isAdmin(context.user) || isSuperAdmin(context.user))) { - throw new Error('You are not authorized to create an account'); - } const { _id, username, password, email, role, applications } = params?.input || {}; - if (!isSuperAdmin(context.user) && role === 'ADMIN') { - AppLogger.info(`[AccountMutations - createOrEditAccount] role : ${role}`); - throw new Error('You are not authorized to create an admin account'); - } - AppLogger.info(`[AccountMutations - createOrEditAccount] _id : ${_id}`); AppLogger.info(`[AccountMutations - createOrEditAccount] username : ${username}`); AppLogger.info(`[AccountMutations - createOrEditAccount] password : ${password}`); @@ -45,20 +35,26 @@ const createOrEditAccount = async ( let editedAccount = null; if (!password) { editedAccount = await AccountProvider.editAccount({ - _id, - username, - email, - role, - applications, + account: { + _id, + username, + email, + role, + applications, + }, + currentUser: context.user, }); } else { editedAccount = await AccountProvider.editAccount({ - _id, - username, - password: await hashPassword(password), - email, - role, - applications, + account: { + _id, + username, + password: await hashPassword(password), + email, + role, + applications, + }, + currentUser: context.user, }); } @@ -120,13 +116,6 @@ const updateAccountPassword = async ( context: { user: AccountType }, ) => { try { - if ( - context.user._id !== params.input._id && - !isAdmin(context.user) && - !isSuperAdmin(context.user) - ) { - throw new Error('You are not authorized to update this account'); - } const { _id, password } = params?.input || {}; AppLogger.info(`[AccountMutations - updatePassword] _id : ${_id}`); @@ -140,6 +129,7 @@ const updateAccountPassword = async ( const updatedAccount = await AccountProvider.updateAccountPassword({ _id, password: await PasswordUtils.hashPassword(password), + currentUser: context.user, }); if (!updatedAccount || !updatedAccount._id) { @@ -166,11 +156,12 @@ const updateAccountPassword = async ( */ const deleteAccount = async ( _: unknown, - params: { accountInput: SearchQueryType }, + params: { input: SearchQueryType }, context: { user: AccountType }, ) => { try { - const whereClause = params?.accountInput?.where; + console.log('params', params); + const whereClause = params?.input?.where; if (!whereClause?.id) { return null; @@ -178,26 +169,14 @@ const deleteAccount = async ( const accountId = parseInt(whereClause.id, 10); AppLogger.info(`[AccountMutations - deleteAccount] accountId : ${accountId}`); - const userToDelete = await AccountProvider.getAccountDetailsByParams({ + const user = await AccountProvider.getAccountDetailsByParams({ _id: accountId, }); - if (!userToDelete) { + if (!user) { throw new Error('User does not exist'); } - if (!(isSuperAdmin(context.user) || isAdmin(context.user))) { - throw new Error('You are not authorized to delete an account'); - } - - if (context.user._id === userToDelete._id) { - throw new Error('You cannot delete your own account'); - } - - if (userToDelete.role === 'ADMIN' && !isSuperAdmin(context.user)) { - throw new Error('You are not authorized to delete an admin account'); - } - - await AccountProvider.deleteAccount({ _id: accountId }); + await AccountProvider.deleteAccount({ userToDelete: user, currentUser: context.user }); AppLogger.info(`[AccountMutations - deleteAccount] deleted account : ${accountId}`); return { _id: accountId, diff --git a/src/v6y-commons/src/database/AccountProvider.ts b/src/v6y-commons/src/database/AccountProvider.ts index 84bb050d..6b92abae 100644 --- a/src/v6y-commons/src/database/AccountProvider.ts +++ b/src/v6y-commons/src/database/AccountProvider.ts @@ -1,7 +1,8 @@ import { FindOptions, Op, Sequelize } from 'sequelize'; import AppLogger from '../core/AppLogger.ts'; -import { AccountInputType, AccountType, AccountUpdatePasswordType } from '../types/AccountType.ts'; +import { isAdmin, isSuperAdmin } from '../core/PassportUtils.ts'; +import { AccountInputType, AccountType } from '../types/AccountType.ts'; import { SearchQueryType } from '../types/SearchQueryType.ts'; import { AccountModelType } from './models/AccountModel.ts'; @@ -82,8 +83,26 @@ const createAccount = async (account: AccountInputType) => { * Edit an Account * @param account */ -const editAccount = async (account: AccountInputType) => { +const editAccount = async ({ + account, + currentUser, +}: { + account: AccountInputType; + currentUser: AccountType; +}) => { try { + if (!(isAdmin(currentUser) || isSuperAdmin(currentUser))) { + throw new Error('You are not authorized to create an account'); + } + + if ( + !isSuperAdmin(currentUser) && + (account.role === 'ADMIN' || account.role === 'SUPERADMIN') + ) { + AppLogger.info(`[AccountProvider - createOrEditAccount] role : ${account.role}`); + throw new Error('You are not authorized to create an admin account'); + } + AppLogger.info(`[AccountProvider - editAccount] account id: ${account?._id}`); AppLogger.info(`[AccountProvider - editAccount] account username: ${account?.username}`); AppLogger.info(`[AccountProvider - editAccount] account role: ${account?.role}`); @@ -122,8 +141,21 @@ const editAccount = async (account: AccountInputType) => { * Update Account Password * @param account */ -const updateAccountPassword = async ({ _id, password }: AccountUpdatePasswordType) => { + +const updateAccountPassword = async ({ + _id, + password, + currentUser, +}: { + _id: number; + password: string; + currentUser: AccountType; +}) => { try { + if (currentUser._id !== _id && !isAdmin(currentUser) && !isSuperAdmin(currentUser)) { + throw new Error('You are not authorized to update this account'); + } + if (!_id || !password) { return null; } @@ -164,24 +196,42 @@ const updateAccountPassword = async ({ _id, password }: AccountUpdatePasswordTyp * Delete an Account * @param _id */ -const deleteAccount = async ({ _id }: AccountType) => { +const deleteAccount = async ({ + userToDelete, + currentUser, +}: { + userToDelete: AccountType; + currentUser: AccountType; +}) => { try { - AppLogger.info(`[AccountProvider - deleteAccount] _id: ${_id}`); + AppLogger.info(`[AccountProvider - deleteAccount] _id: ${userToDelete._id}`); - if (!_id) { + if (!(isSuperAdmin(currentUser) || isAdmin(currentUser))) { + throw new Error('You are not authorized to delete an account'); + } + + if (currentUser._id === userToDelete._id) { + throw new Error('You cannot delete your own account'); + } + + if (userToDelete.role === 'ADMIN' && !isSuperAdmin(currentUser)) { + throw new Error('You are not authorized to delete an admin account'); + } + + if (!userToDelete._id) { return null; } await AccountModelType.destroy({ where: { - _id, + _id: userToDelete._id, }, }); - AppLogger.info(`[AccountProvider - deleteAccount] deleted account: ${_id}`); + AppLogger.info(`[AccountProvider - deleteAccount] deleted account: ${userToDelete._id}`); return { - _id, + _id: userToDelete._id, }; } catch (error) { AppLogger.info(`[AccountProvider - deleteAccount] error: ${error}`); From ffcb5b848860e12a59d55b71d70a0dd54dd65872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Mon, 2 Dec 2024 18:00:27 +0100 Subject: [PATCH 13/14] [FIX]: remove unused function and log --- src/v6y-bff/src/resolvers/account/AccountMutations.ts | 1 - src/v6y-front-bo/src/commons/hooks/useRole.ts | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/v6y-bff/src/resolvers/account/AccountMutations.ts b/src/v6y-bff/src/resolvers/account/AccountMutations.ts index 9b266e56..180e7062 100644 --- a/src/v6y-bff/src/resolvers/account/AccountMutations.ts +++ b/src/v6y-bff/src/resolvers/account/AccountMutations.ts @@ -160,7 +160,6 @@ const deleteAccount = async ( context: { user: AccountType }, ) => { try { - console.log('params', params); const whereClause = params?.input?.where; if (!whereClause?.id) { diff --git a/src/v6y-front-bo/src/commons/hooks/useRole.ts b/src/v6y-front-bo/src/commons/hooks/useRole.ts index f507a14a..253bbfea 100644 --- a/src/v6y-front-bo/src/commons/hooks/useRole.ts +++ b/src/v6y-front-bo/src/commons/hooks/useRole.ts @@ -7,12 +7,7 @@ export const useRole = () => { return JSON.parse(auth || '{}')?.role; } - const setRole = () => { - // set role - } - return ({ - getRole, - setRole + getRole }); }; \ No newline at end of file From ffe11df2a1042cdf9041bfab32bba30e676540e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20AUBERT?= Date: Tue, 3 Dec 2024 17:53:35 +0100 Subject: [PATCH 14/14] [FIX]: Fix merge conflicts and format code --- .../src/core/AuthenticationHelper.ts | 14 +++ .../__tests__/AuthenticationHelper-test.ts | 77 +++++++++++-- .../src/database/AccountProvider.ts | 2 +- .../src/app/v6y-accounts/create/page.tsx | 2 +- .../src/app/v6y-accounts/edit/[id]/page.tsx | 2 +- .../components/VitalityFormFieldSet.tsx | 5 +- .../commons/config/VitalityDetailsConfig.tsx | 10 +- .../src/commons/config/VitalityFormConfig.tsx | 52 ++++++--- .../config/VitalityNavigationConfig.ts | 2 +- src/v6y-front-bo/src/commons/hooks/useRole.ts | 11 +- .../src/commons/hooks/useToken.ts | 5 +- .../v6y-accounts/apis/deleteAccount.ts | 1 - .../apis/getAccountDetailsByParams.ts | 2 +- .../components/VitalityAccountCreateView.tsx | 19 ++-- .../components/VitalityAccountEditView.tsx | 26 ++--- .../components/VitalityAccountListView.tsx | 2 +- .../VitalityApplicationListView.tsx | 2 +- .../adapters/api/GraphQLClient.ts | 9 +- .../components/RefineCreateWrapper.tsx | 19 ++-- .../components/RefineEditWrapper.tsx | 13 ++- .../components/RefineSelectWrapper.tsx | 107 ++++++++++-------- .../components/RefineShowWrapper.tsx | 4 +- .../providers/GraphQLProvider.ts | 26 +++-- 23 files changed, 260 insertions(+), 152 deletions(-) diff --git a/src/v6y-commons/src/core/AuthenticationHelper.ts b/src/v6y-commons/src/core/AuthenticationHelper.ts index 9bcc2e50..ee5728b6 100644 --- a/src/v6y-commons/src/core/AuthenticationHelper.ts +++ b/src/v6y-commons/src/core/AuthenticationHelper.ts @@ -106,3 +106,17 @@ passport.use(new JwtStrategy(createJwtOptions(), createJwtStrategyVerify())); * Initializes Authentication middleware. */ export const configureAuthMiddleware = (): T => passport.initialize() as T; + +/** + * Check if user role is ADMIN. + * @param {object} account + * @returns {boolean} + */ +export const isAdmin = (account: AccountType) => account?.role === 'ADMIN'; + +/** + * Check if user role is SUPERADMIN. + * @param {object} account + * @returns {boolean} + */ +export const isSuperAdmin = (account: AccountType) => account?.role === 'SUPERADMIN'; diff --git a/src/v6y-commons/src/core/__tests__/AuthenticationHelper-test.ts b/src/v6y-commons/src/core/__tests__/AuthenticationHelper-test.ts index 5d4063c3..a2cdfe4b 100644 --- a/src/v6y-commons/src/core/__tests__/AuthenticationHelper-test.ts +++ b/src/v6y-commons/src/core/__tests__/AuthenticationHelper-test.ts @@ -1,8 +1,15 @@ -// AuthenticationHelper.tests.ts +/* eslint-disable max-lines-per-function */ +// tests.ts import { Mock, describe, expect, it, vi } from 'vitest'; import AccountProvider from '../../database/AccountProvider.ts'; -import * as AuthenticationHelper from '../AuthenticationHelper.ts'; +import { + createJwtOptions, + generateAuthenticationToken, + isAdmin, + isSuperAdmin, + validateCredentials, +} from '../AuthenticationHelper.ts'; vi.mock('../../database/AccountProvider.ts', async () => { const actualModule = (await vi.importActual( @@ -19,7 +26,7 @@ vi.mock('../../database/AccountProvider.ts', async () => { describe('AuthenticationHelper', () => { it('should create jwt options', () => { - const result = AuthenticationHelper.createJwtOptions(); + const result = createJwtOptions(); expect(result).toEqual({ jwtFromRequest: expect.any(Function), secretOrKey: expect.any(String), @@ -28,14 +35,14 @@ describe('AuthenticationHelper', () => { it('should generate token', () => { const account = { _id: 12 }; - const result = AuthenticationHelper.generateAuthenticationToken(account); + const result = generateAuthenticationToken(account); expect(result).toEqual(expect.any(String)); }); it('should return null if token is invalid', async () => { const account = {}; - const token = AuthenticationHelper.generateAuthenticationToken(account); + const token = generateAuthenticationToken(account); const request = { cache: 'default' as RequestCache, @@ -64,7 +71,7 @@ describe('AuthenticationHelper', () => { text: async () => '', } as unknown as Request; - const result = await AuthenticationHelper.validateCredentials(request); + const result = await validateCredentials(request); expect(result).toBe(null); }); @@ -82,7 +89,7 @@ describe('AuthenticationHelper', () => { role: 'ADMIN', applications: [1, 2, 3], }; - const token = AuthenticationHelper.generateAuthenticationToken(account); + const token = generateAuthenticationToken(account); const request = { cache: 'default' as RequestCache, credentials: 'same-origin', @@ -110,8 +117,62 @@ describe('AuthenticationHelper', () => { text: async () => '', } as unknown as Request; - const result = await AuthenticationHelper.validateCredentials(request); + const result = await validateCredentials(request); expect(result).toEqual(account); }); + + it('should return true if the user is ADMIN, false otherwise', () => { + const accountAdmin = { + _id: 15, + email: 'admin@admin.admin', + role: 'ADMIN', + applications: [1, 2, 3], + }; + + const accountSuperAdmin = { + _id: 15, + email: 'superadmin@superadmin.superadmin', + role: 'SUPERADMIN', + applications: [1, 2, 3], + }; + + const accountUser = { + _id: 15, + email: 'user@user.user', + role: 'USER', + applications: [1, 2, 3], + }; + + expect(isAdmin(accountAdmin)).toBe(true); + expect(isAdmin(accountSuperAdmin)).toBe(false); + expect(isAdmin(accountUser)).toBe(false); + }); + + it('should return true if the user is SUPERADMIN, false otherwise', () => { + const accountAdmin = { + _id: 15, + email: 'admin@admin.admin', + role: 'ADMIN', + applications: [1, 2, 3], + }; + + const accountSuperAdmin = { + _id: 15, + email: 'superadmin@superadmin.superadmin', + role: 'SUPERADMIN', + applications: [1, 2, 3], + }; + + const accountUser = { + _id: 15, + email: 'user@user.user', + role: 'USER', + applications: [1, 2, 3], + }; + + expect(isSuperAdmin(accountAdmin)).toBe(false); + expect(isSuperAdmin(accountSuperAdmin)).toBe(true); + expect(isSuperAdmin(accountUser)).toBe(false); + }); }); diff --git a/src/v6y-commons/src/database/AccountProvider.ts b/src/v6y-commons/src/database/AccountProvider.ts index 6b92abae..463d636f 100644 --- a/src/v6y-commons/src/database/AccountProvider.ts +++ b/src/v6y-commons/src/database/AccountProvider.ts @@ -1,7 +1,7 @@ import { FindOptions, Op, Sequelize } from 'sequelize'; import AppLogger from '../core/AppLogger.ts'; -import { isAdmin, isSuperAdmin } from '../core/PassportUtils.ts'; +import { isAdmin, isSuperAdmin } from '../core/AuthenticationHelper.ts'; import { AccountInputType, AccountType } from '../types/AccountType.ts'; import { SearchQueryType } from '../types/SearchQueryType.ts'; import { AccountModelType } from './models/AccountModel.ts'; diff --git a/src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx b/src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx index 9501d689..dd02e679 100644 --- a/src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx +++ b/src/v6y-front-bo/src/app/v6y-accounts/create/page.tsx @@ -1,9 +1,9 @@ 'use client'; import * as React from 'react'; + import VitalityAccountCreateView from '../../../features/v6y-accounts/components/VitalityAccountCreateView'; export default function VitalityAccountCreatePage() { - return ; } diff --git a/src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx b/src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx index b8926821..1073b664 100644 --- a/src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx +++ b/src/v6y-front-bo/src/app/v6y-accounts/edit/[id]/page.tsx @@ -1,9 +1,9 @@ 'use client'; import * as React from 'react'; + import VitalityAccountEditView from '../../../../features/v6y-accounts/components/VitalityAccountEditView'; export default function VitalityAccountEditPage() { - return ; } diff --git a/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx b/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx index e994dcab..5dddce23 100644 --- a/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx +++ b/src/v6y-front-bo/src/commons/components/VitalityFormFieldSet.tsx @@ -38,7 +38,10 @@ const VitalityFormFieldSet = ({ groupTitle, items, selectOptions }: VitalityForm )} {item.type === 'password' && ( - + )} {!item.type?.length && ( diff --git a/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx b/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx index 6fa6e4b8..a8a61543 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx +++ b/src/v6y-front-bo/src/commons/config/VitalityDetailsConfig.tsx @@ -26,10 +26,10 @@ export const formatAccountDetails = ( [translate('v6y-accounts.fields.account-username.label') || '']: details.username, [translate('v6y-accounts.fields.account-email.label') || '']: details.email, [translate('v6y-accounts.fields.account-role.label') || '']: details.role, - [translate('v6y-accounts.fields.account-applications.label') || '']: details.applications?.join(', '), - + [translate('v6y-accounts.fields.account-applications.label') || '']: + details.applications?.join(', '), }; -} +}; export const formatApplicationDetails = ( translate: TranslateType, @@ -126,11 +126,11 @@ export const formatDependencyStatusHelpDetails = ( return { [translate('v6y-dependency-status-helps.fields.dependency-status-help-category.label') || - '']: details.category, + '']: details.category, [translate('v6y-dependency-status-helps.fields.dependency-status-help-title.label') || '']: details.title, [translate('v6y-dependency-status-helps.fields.dependency-status-help-description.label') || - '']: details.description, + '']: details.description, [translate('v6y-dependency-status-helps.fields.dependency-status-help-links.label') || '']: , }; diff --git a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx index 406bf5ab..4f31a31d 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx +++ b/src/v6y-front-bo/src/commons/config/VitalityFormConfig.tsx @@ -1,6 +1,7 @@ import { ApplicationType } from '@v6y/commons'; import { DefaultOptionType } from 'antd/es/select'; import { Variables } from 'graphql-request'; + import { TranslateType } from '../../infrastructure/types/TranslationType'; import VitalityFormFieldSet from '../components/VitalityFormFieldSet'; @@ -774,14 +775,22 @@ export const accountCreateOrEditFormOutputAdapter = (params: Record { +export const accountCreateEditItems = ( + translate: TranslateType, + role: string, + applications: ApplicationType[], + edit: boolean = false, +) => { const applicationsValues = applications?.map((application) => ({ value: application._id, label: application.name, })); const roles = [ - { label: translate('pages.createAccount.fields.account-role.options.admin'), value: 'ADMIN' }, + { + label: translate('pages.createAccount.fields.account-role.options.admin'), + value: 'ADMIN', + }, { label: translate('pages.createAccount.fields.account-role.options.user'), value: 'USER' }, ]; @@ -802,7 +811,6 @@ export const accountCreateEditItems = (translate: TranslateType, role: string, a }; export const accountInfosFormItems = (translate: TranslateType, role: string, edit: boolean) => { - return [ { id: 'account-email', @@ -847,8 +855,14 @@ export const accountInfosFormItems = (translate: TranslateType, role: string, ed }, ], options: [ - { label: translate('pages.createAccount.fields.account-role.options.admin'), value: 'ADMIN' }, - { label: translate('pages.createAccount.fields.account-role.options.user'), value: 'USER' }, + { + label: translate('pages.createAccount.fields.account-role.options.admin'), + value: 'ADMIN', + }, + { + label: translate('pages.createAccount.fields.account-role.options.user'), + value: 'USER', + }, ], }, { @@ -857,18 +871,19 @@ export const accountInfosFormItems = (translate: TranslateType, role: string, ed type: 'password', label: translate('pages.createAccount.fields.account-password.label'), placeholder: translate('pages.createAccount.fields.account-password.placeholder'), - rules: !edit ? [ - { - required: true, - message: translate('pages.createAccount.fields.account-password.error'), - }, - ] : [], + rules: !edit + ? [ + { + required: true, + message: translate('pages.createAccount.fields.account-password.error'), + }, + ] + : [], }, ]; -} +}; export const accountApplicationsFormItems = (translate: TranslateType) => { - return [ { id: 'account-applications', @@ -884,10 +899,15 @@ export const accountApplicationsFormItems = (translate: TranslateType) => { validator: (_: unknown, value: string[]) => value && value.length > 0 ? Promise.resolve() - : Promise.reject(new Error(translate('pages.createAccount.fields.account-applications.error'))), + : Promise.reject( + new Error( + translate( + 'pages.createAccount.fields.account-applications.error', + ), + ), + ), }, - ], }, ]; -}; \ No newline at end of file +}; diff --git a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts index a3fceeaa..b06712f6 100644 --- a/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts +++ b/src/v6y-front-bo/src/commons/config/VitalityNavigationConfig.ts @@ -7,7 +7,7 @@ export const VitalityRoutes = [ show: '/v6y-accounts/show/:id', meta: { canDelete: true, - } + }, }, { name: 'v6y-applications', diff --git a/src/v6y-front-bo/src/commons/hooks/useRole.ts b/src/v6y-front-bo/src/commons/hooks/useRole.ts index 253bbfea..50abae1b 100644 --- a/src/v6y-front-bo/src/commons/hooks/useRole.ts +++ b/src/v6y-front-bo/src/commons/hooks/useRole.ts @@ -1,13 +1,12 @@ import Cookies from 'js-cookie'; export const useRole = () => { - const getRole = () => { const auth = Cookies.get('auth'); return JSON.parse(auth || '{}')?.role; - } + }; - return ({ - getRole - }); -}; \ No newline at end of file + return { + getRole, + }; +}; diff --git a/src/v6y-front-bo/src/commons/hooks/useToken.ts b/src/v6y-front-bo/src/commons/hooks/useToken.ts index c474b6fa..5f6cec7a 100644 --- a/src/v6y-front-bo/src/commons/hooks/useToken.ts +++ b/src/v6y-front-bo/src/commons/hooks/useToken.ts @@ -1,4 +1,3 @@ - import Cookies from 'js-cookie'; const useToken = () => { @@ -6,6 +5,6 @@ const useToken = () => { const token = JSON.parse(auth || '{}')?.token; return token; -} +}; -export default useToken; \ No newline at end of file +export default useToken; diff --git a/src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts b/src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts index adb0d8cf..5f26643e 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts +++ b/src/v6y-front-bo/src/features/v6y-accounts/apis/deleteAccount.ts @@ -9,4 +9,3 @@ const DeleteAccount = gql` `; export default DeleteAccount; - diff --git a/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts index 25033abc..9d7eb2a5 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts +++ b/src/v6y-front-bo/src/features/v6y-accounts/apis/getAccountDetailsByParams.ts @@ -6,7 +6,7 @@ const GetAccountDetailsByParams = gql` _id username email - role, + role applications } } diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx index 314029ec..79226374 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountCreateView.tsx @@ -1,18 +1,19 @@ -"use client"; +'use client'; +import { ApplicationType } from '@v6y/commons/src/types/ApplicationType'; import { Typography } from 'antd'; +import { useEffect, useState } from 'react'; + +import GetApplicationList from '../../../commons/apis/getApplicationList'; +import VitalityEmptyView from '../../../commons/components/VitalityEmptyView'; import { accountCreateEditItems, accountCreateOrEditFormOutputAdapter, } from '../../../commons/config/VitalityFormConfig'; -import RefineSelectWrapper from '../../../infrastructure/components/RefineSelectWrapper'; -import VitalityEmptyView from '../../../commons/components/VitalityEmptyView'; +import { useRole } from '../../../commons/hooks/useRole'; import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; +import RefineSelectWrapper from '../../../infrastructure/components/RefineSelectWrapper'; import CreateOrEditAccount from '../apis/createOrEditAccount'; -import { useRole } from '../../../commons/hooks/useRole'; -import GetApplicationList from '../../../commons/apis/getApplicationList'; -import { ApplicationType } from '@v6y/commons/src/types/ApplicationType'; -import { useEffect, useState } from 'react'; export default function VitalityAccountCreateView() { const { translate } = useTranslation(); @@ -47,8 +48,8 @@ export default function VitalityAccountCreateView() { query: GetApplicationList, }} renderSelectOption={(applications: ApplicationType[]) => { - return accountCreateEditItems(translate, userRole, applications) + return accountCreateEditItems(translate, userRole, applications); }} /> ); -} \ No newline at end of file +} diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx index d959bb5e..a2587bad 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountEditView.tsx @@ -1,23 +1,23 @@ -"use client"; +'use client'; +import { useParsed } from '@refinedev/core'; +import { ApplicationType } from '@v6y/commons/src/types/ApplicationType'; import { Typography } from 'antd'; +import { useEffect, useState } from 'react'; +import React from 'react'; + +import GetApplicationList from '../../../commons/apis/getApplicationList'; +import VitalityEmptyView from '../../../commons/components/VitalityEmptyView'; import { accountCreateEditItems, accountCreateOrEditFormInAdapter, accountCreateOrEditFormOutputAdapter, } from '../../../commons/config/VitalityFormConfig'; -import RefineSelectWrapper from '../../../infrastructure/components/RefineSelectWrapper'; -import VitalityEmptyView from '../../../commons/components/VitalityEmptyView'; +import { useRole } from '../../../commons/hooks/useRole'; import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; +import RefineSelectWrapper from '../../../infrastructure/components/RefineSelectWrapper'; import CreateOrEditAccount from '../apis/createOrEditAccount'; -import { useRole } from '../../../commons/hooks/useRole'; - -import { ApplicationType } from '@v6y/commons/src/types/ApplicationType'; -import { useEffect, useState } from 'react'; -import { useParsed } from '@refinedev/core'; -import GetApplicationList from '../../../commons/apis/getApplicationList'; import GetAccountDetailsByParams from '../apis/getAccountDetailsByParams'; -import React from 'react'; export default function VitalityAccountEditView() { const { translate } = useTranslation(); @@ -48,7 +48,6 @@ export default function VitalityAccountEditView() { _id: parseInt(id as string, 10), }, }} - mutationOptions={{ editResource: 'createOrEditAccount', editFormAdapter: accountCreateOrEditFormOutputAdapter, @@ -61,10 +60,9 @@ export default function VitalityAccountEditView() { resource: 'getApplicationList', query: GetApplicationList, }} - renderSelectOption={(applications: ApplicationType[]) => { - return accountCreateEditItems(translate, userRole, applications, true) + return accountCreateEditItems(translate, userRole, applications, true); }} /> ); -} \ No newline at end of file +} diff --git a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx index 80f0cbbb..936e5db2 100644 --- a/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx +++ b/src/v6y-front-bo/src/features/v6y-accounts/components/VitalityAccountListView.tsx @@ -34,7 +34,7 @@ export default function VitalityAccountListView() { enableDelete: true, deleteMetaQuery: { gqlMutation: DeleteAccount, - operation: 'deleteAccount' + operation: 'deleteAccount', }, })} /> diff --git a/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx b/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx index 64e41f68..02d7c832 100644 --- a/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx +++ b/src/v6y-front-bo/src/features/v6y-applications/components/VitalityApplicationListView.tsx @@ -1,5 +1,6 @@ import { ApplicationType } from '@v6y/commons'; +import GetApplicationList from '../../../commons/apis/getApplicationList'; import VitalityTable from '../../../commons/components/VitalityTable'; import { buildCommonTableColumns, @@ -8,7 +9,6 @@ import { import { useTranslation } from '../../../infrastructure/adapters/translation/TranslationAdapter'; import RefineTableWrapper from '../../../infrastructure/components/RefineTableWrapper'; import DeleteApplication from '../apis/deleteApplication'; -import GetApplicationList from '../../../commons/apis/getApplicationList'; export default function VitalityApplicationListView() { const { translate } = useTranslation(); diff --git a/src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts b/src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts index 58672b55..5814da3e 100644 --- a/src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts +++ b/src/v6y-front-bo/src/infrastructure/adapters/api/GraphQLClient.ts @@ -1,5 +1,5 @@ import { GraphQLClient } from 'graphql-request'; -import Cookies from 'js-cookie' +import Cookies from 'js-cookie'; export const gqlClient = new GraphQLClient(process.env.NEXT_PUBLIC_GQL_API_BASE_PATH as string, { fetch: (url: RequestInfo | URL, options?: RequestInit) => { @@ -20,8 +20,5 @@ type GqlClientRequestParams = { export const gqlClientRequest = ({ gqlQueryPath, - gqlQueryParams -}: GqlClientRequestParams): Promise => gqlClient.request( - gqlQueryPath, - gqlQueryParams -); \ No newline at end of file + gqlQueryParams, +}: GqlClientRequestParams): Promise => gqlClient.request(gqlQueryPath, gqlQueryParams); diff --git a/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx b/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx index 10ee7367..65a34ad1 100644 --- a/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx +++ b/src/v6y-front-bo/src/infrastructure/components/RefineCreateWrapper.tsx @@ -1,11 +1,11 @@ 'use client'; import { Create, useForm } from '@refinedev/antd'; +import { BaseRecord, GetOneResponse } from '@refinedev/core'; import { Form } from 'antd'; -import { FormCreateOptionsType } from '../types/FormType'; import { gqlClientRequest } from '../adapters/api/GraphQLClient'; -import { BaseRecord, GetOneResponse } from '@refinedev/core'; +import { FormCreateOptionsType } from '../types/FormType'; export default function RefineCreateWrapper({ title, @@ -18,13 +18,14 @@ export default function RefineCreateWrapper({ mutationFn: async (): Promise> => gqlClientRequest({ gqlQueryPath: createOptions?.createQuery, - gqlQueryParams: createOptions?.createFormAdapter?.({ - ...(createOptions?.createQueryParams || {}), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - ...(form?.getFieldsValue() || {}), - }) || {}, - }) + gqlQueryParams: + createOptions?.createFormAdapter?.({ + ...(createOptions?.createQueryParams || {}), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ...(form?.getFieldsValue() || {}), + }) || {}, + }), }, }); diff --git a/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx b/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx index 412bdfc6..dccdd835 100644 --- a/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx +++ b/src/v6y-front-bo/src/infrastructure/components/RefineEditWrapper.tsx @@ -5,8 +5,8 @@ import { BaseRecord, GetOneResponse } from '@refinedev/core'; import { Form } from 'antd'; import { useEffect } from 'react'; -import { FormWrapperProps } from '../types/FormType'; import { gqlClientRequest } from '../adapters/api/GraphQLClient'; +import { FormWrapperProps } from '../types/FormType'; export default function RefineEditWrapper({ title, @@ -20,7 +20,7 @@ export default function RefineEditWrapper({ queryFn: async (): Promise> => gqlClientRequest({ gqlQueryPath: queryOptions?.query, - gqlQueryParams: queryOptions?.queryParams + gqlQueryParams: queryOptions?.queryParams, }), }, updateMutationOptions: { @@ -30,10 +30,11 @@ export default function RefineEditWrapper({ mutationFn: async (): Promise> => gqlClientRequest({ gqlQueryPath: mutationOptions?.editQuery, - gqlQueryParams: mutationOptions?.editFormAdapter?.({ - ...(mutationOptions?.editQueryParams || {}), - ...(form?.getFieldsValue() || {}), - }) || {}, + gqlQueryParams: + mutationOptions?.editFormAdapter?.({ + ...(mutationOptions?.editQueryParams || {}), + ...(form?.getFieldsValue() || {}), + }) || {}, }), }, }); diff --git a/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx b/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx index a65d793e..91d1d56c 100644 --- a/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx +++ b/src/v6y-front-bo/src/infrastructure/components/RefineSelectWrapper.tsx @@ -5,8 +5,8 @@ import { BaseRecord, GetOneResponse } from '@refinedev/core'; import { Form } from 'antd'; import { ReactNode, useEffect } from 'react'; -import { FormWrapperProps } from '../types/FormType'; import { gqlClientRequest } from '../adapters/api/GraphQLClient'; +import { FormWrapperProps } from '../types/FormType'; export default function RefineSelectWrapper({ title, @@ -16,54 +16,61 @@ export default function RefineSelectWrapper({ selectOptions, renderSelectOption, }: FormWrapperProps) { - const formQueryOptions = queryOptions ? { - queryOptions: { - enabled: true, - queryKey: [queryOptions?.resource, queryOptions?.queryParams], - queryFn: async (): Promise> => - gqlClientRequest({ - gqlQueryPath: queryOptions?.query, - gqlQueryParams: queryOptions?.queryParams - }), - } - } : {}; - - const formMutationOptions = mutationOptions ? { - updateMutationOptions: { - mutationKey: [mutationOptions?.editResource, mutationOptions?.editQuery], - mutationFn: async (): Promise> => { - const { editQuery, editFormAdapter, editQueryParams } = mutationOptions; - return gqlClientRequest({ - gqlQueryPath: editQuery, - gqlQueryParams: editFormAdapter?.({ - ...(editQueryParams || {}), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - ...(form?.getFieldsValue() || {}), - }) || {}, - }); - }, - }, - } : {}; + const formQueryOptions = queryOptions + ? { + queryOptions: { + enabled: true, + queryKey: [queryOptions?.resource, queryOptions?.queryParams], + queryFn: async (): Promise> => + gqlClientRequest({ + gqlQueryPath: queryOptions?.query, + gqlQueryParams: queryOptions?.queryParams, + }), + }, + } + : {}; - const formCreateOptions = createOptions ? { - createMutationOptions: { - mutationKey: [createOptions?.createResource, createOptions?.createQuery], - mutationFn: async (): Promise> => { - const { createQuery, createFormAdapter, createQueryParams } = createOptions; - return gqlClientRequest({ - gqlQueryPath: createQuery, - gqlQueryParams: createFormAdapter?.({ - ...(createQueryParams || {}), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - ...(form?.getFieldsValue() || {}), - }) || {}, - }); - }, - }, - } : {}; + const formMutationOptions = mutationOptions + ? { + updateMutationOptions: { + mutationKey: [mutationOptions?.editResource, mutationOptions?.editQuery], + mutationFn: async (): Promise> => { + const { editQuery, editFormAdapter, editQueryParams } = mutationOptions; + return gqlClientRequest({ + gqlQueryPath: editQuery, + gqlQueryParams: + editFormAdapter?.({ + ...(editQueryParams || {}), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ...(form?.getFieldsValue() || {}), + }) || {}, + }); + }, + }, + } + : {}; + const formCreateOptions = createOptions + ? { + createMutationOptions: { + mutationKey: [createOptions?.createResource, createOptions?.createQuery], + mutationFn: async (): Promise> => { + const { createQuery, createFormAdapter, createQueryParams } = createOptions; + return gqlClientRequest({ + gqlQueryPath: createQuery, + gqlQueryParams: + createFormAdapter?.({ + ...(createQueryParams || {}), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ...(form?.getFieldsValue() || {}), + }) || {}, + }); + }, + }, + } + : {}; const { form, formProps, saveButtonProps, query } = useForm({ ...formQueryOptions, @@ -79,9 +86,9 @@ export default function RefineSelectWrapper({ queryFn: async (): Promise> => gqlClientRequest({ gqlQueryPath: selectOptions?.query, - gqlQueryParams: selectOptions?.queryParams + gqlQueryParams: selectOptions?.queryParams, }), - } + }, }); useEffect(() => { @@ -93,7 +100,7 @@ export default function RefineSelectWrapper({ } }, [form, query?.data, queryOptions]); - const isLoading = selectQueryResult?.isLoading || (queryOptions && query?.isLoading) + const isLoading = selectQueryResult?.isLoading || (queryOptions && query?.isLoading); return ( > => gqlClientRequest({ gqlQueryPath: queryOptions?.query, - gqlQueryParams: queryOptions?.queryParams + gqlQueryParams: queryOptions?.queryParams, }), }, }); diff --git a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts index f21462e5..a587e673 100644 --- a/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts +++ b/src/v6y-front-bo/src/infrastructure/providers/GraphQLProvider.ts @@ -58,7 +58,7 @@ export const gqlAuthProvider: AuthProvider = { if (data.loginAccount?.token) { if (data.loginAccount.role !== 'ADMIN' && data.loginAccount.role !== 'SUPERADMIN') { - console.log("You are not authorized to access this page"); + console.log('You are not authorized to access this page'); return { success: false, error: { @@ -68,10 +68,18 @@ export const gqlAuthProvider: AuthProvider = { }; } - Cookies.set('auth', JSON.stringify({ token: data.loginAccount.token, _id: data.loginAccount._id, role: data.loginAccount.role }), { - expires: 30, // 30 jours - path: '/', - }); + Cookies.set( + 'auth', + JSON.stringify({ + token: data.loginAccount.token, + _id: data.loginAccount._id, + role: data.loginAccount.role, + }), + { + expires: 30, // 30 jours + path: '/', + }, + ); return { success: true, @@ -137,10 +145,10 @@ export const gqlAuthProvider: AuthProvider = { } `, variables: { - "input": { - "_id": JSON.parse(Cookies.get('auth') || '{}')?._id, - "password": password, - } + input: { + _id: JSON.parse(Cookies.get('auth') || '{}')?._id, + password: password, + }, }, }), });