diff --git a/rair-front/Dockerfile b/rair-front/Dockerfile index 9e3df894e..4325f622b 100644 --- a/rair-front/Dockerfile +++ b/rair-front/Dockerfile @@ -9,4 +9,5 @@ WORKDIR /usr/src/minting EXPOSE 3001 +CMD node --max-old-space-size=8192 CMD npm start \ No newline at end of file diff --git a/rair-front/Dockerfile.prod b/rair-front/Dockerfile.prod index 78fff4eff..329f07991 100644 --- a/rair-front/Dockerfile.prod +++ b/rair-front/Dockerfile.prod @@ -10,6 +10,7 @@ COPY yarn.lock ./ RUN yarn install COPY . /usr/src/minting #RUN mkdir -p node_modules/.cache && chmod -R 777 node_modules/.cache +RUN node --max-old-space-size=8192 RUN yarn build diff --git a/rair-front/Dockerfile.prod-new b/rair-front/Dockerfile.prod-new index 7ff80e6b8..396cf2d79 100644 --- a/rair-front/Dockerfile.prod-new +++ b/rair-front/Dockerfile.prod-new @@ -10,6 +10,7 @@ COPY yarn.lock ./ RUN yarn install COPY . /usr/src/minting #RUN mkdir -p node_modules/.cache && chmod -R 777 node_modules/.cache +RUN node --max-old-space-size=8192 RUN yarn build diff --git a/rair-front/package.json b/rair-front/package.json index 8966c5001..bf17de3d3 100644 --- a/rair-front/package.json +++ b/rair-front/package.json @@ -13,49 +13,51 @@ "prepare": "cd ../ && husky install ./minting-marketplace/.husky" }, "dependencies": { + "@account-kit/core": "^4.11.0", + "@account-kit/infra": "^4.11.0", + "@account-kit/signer": "^4.11.0", "@alchemy/aa-accounts": "^3.19.0", "@alchemy/aa-alchemy": "^3.19.0", "@alchemy/aa-core": "^3.19.0", "@alchemy/aa-ethers": "^3.19.0", "@alchemy/aa-signers": "^3.19.0", "@analytics/google-analytics": "^1.0.7", - "@emotion/is-prop-valid": "^1.3.0", - "@emotion/react": "^11.13.3", - "@emotion/styled": "^11.13.0", - "@fortawesome/fontawesome-svg-core": "^6.6.0", - "@fortawesome/free-brands-svg-icons": "^6.6.0", - "@fortawesome/free-regular-svg-icons": "^6.6.0", - "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@emotion/is-prop-valid": "^1.3.1", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", "@getyoti/react-face-capture": "^2.3.1", "@metamask/onboarding": "^1.0.1", "@metamask/providers": "^14.0.2", "@mui/icons-material": "^5.16.7", "@mui/material": "^5.16.7", - "@reduxjs/toolkit": "^2.2.7", + "@reduxjs/toolkit": "^2.5.1", "@sentry/browser": "^7.85.0", "@sentry/react": "^7.85.0", + "@wagmi/core": "^2.16.3", "@walletconnect/sign-client": "^2.14.0", "@walletconnect/utils": "^2.14.0", - "@web3auth/base": "^8.12.4", - "@web3auth/modal": "^8.12.4", + "@web3auth/base": "^9.5.3", + "@web3auth/modal": "^9.5.3", "alchemy-sdk": "^3.4.1", "analytics": "^0.8.14", - "axios": "^1.7.5", + "axios": "^1.7.9", "bootstrap": "^5.3.3", - "ethers": "^6.13.2", + "ethers": "^6.13.5", "global": "^4.4.0", "moment-timezone": "^0.5.45", - "prettier": "^3.3.3", + "prettier": "^3.4.2", "react": "^18.3.1", "react-accessible-accordion": "^5.0.0", "react-dom": "^18.3.1", - "react-dropzone": "^14.2.3", + "react-dropzone": "^14.3.5", "react-ga": "^3.3.1", "react-helmet-async": "^2.0.5", - "react-hook-form": "^7.52.2", - "react-hot-toast": "^2.4.1", - "react-modal": "^3.16.1", + "react-hook-form": "^7.54.2", + "react-hot-toast": "^2.5.1", + "react-modal": "^3.16.3", "react-moment": "^1.1.3", "react-multi-carousel": "^2.8.5", "react-player": "^2.16.0", @@ -67,20 +69,22 @@ "react-webcam": "^7.2.0", "reactjs-popup": "^2.0.6", "slick-carousel": "^1.8.1", - "socket.io-client": "^4.7.5", - "styled-components": "^6.1.12", + "socket.io-client": "^4.8.1", + "styled-components": "^6.1.14", "sweetalert2": "^11.10.1", - "sweetalert2-react-content": "^5.0.7", - "use-debounce": "^10.0.3", + "sweetalert2-react-content": "^5.1.0", + "use-debounce": "^10.0.4", "use-state-if-mounted": "^1.0.7", - "uuid": "^10.0.0", + "uuid": "^11.0.4", "video.js": "^8.17.3", "videojs-contrib-quality-levels": "^4.0.0", "videojs-hls-quality-selector": "^1.1.4", - "viem": "^2.20.0", + "viem": "^2.22.17", "vite-plugin-radar": "^0.9.6", "vite-plugin-svgr": "^4.2.0", - "wicg-inert": "^3.1.3" + "wagmi": "^2.14.9", + "wicg-inert": "^3.1.3", + "zustand": "^5.0.3" }, "devDependencies": { "@types/react": "^18.3.4", @@ -93,7 +97,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", - "@vitejs/plugin-react": "^4.3.1", + "@vitejs/plugin-react": "^4.3.4", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", @@ -101,9 +105,9 @@ "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.9", "eslint-plugin-simple-import-sort": "^12.1.1", - "husky": "^9.1.5", - "typescript": "^5.5.4", - "vite": "^5.4.2", - "vite-plugin-node-polyfills": "^0.22.0" + "husky": "^9.1.7", + "typescript": "^5.7.3", + "vite": "^6.0.11", + "vite-plugin-node-polyfills": "^0.23.0" } } diff --git a/rair-front/src/App.tsx b/rair-front/src/App.tsx index 517b581a7..a2972bb90 100644 --- a/rair-front/src/App.tsx +++ b/rair-front/src/App.tsx @@ -309,6 +309,7 @@ function App() { */}
+
{/* diff --git a/rair-front/src/components/SplashPage/ImmersiVerse/ImmersiVerseSplashPage.tsx b/rair-front/src/components/SplashPage/ImmersiVerse/ImmersiVerseSplashPage.tsx index 921fd24b3..06a57c2d4 100644 --- a/rair-front/src/components/SplashPage/ImmersiVerse/ImmersiVerseSplashPage.tsx +++ b/rair-front/src/components/SplashPage/ImmersiVerse/ImmersiVerseSplashPage.tsx @@ -85,7 +85,7 @@ const ImmersiVerseSplashPage: FC = ({ setIsSplashPage }) => { const [carousel, setCarousel] = useState(carousel_match.matches); window.addEventListener('resize', () => setCarousel(carousel_match.matches)); - let subtitle: Modal; + const [subtitle] = useState(); function afterOpenModal() { if (subtitle?.props?.style?.content) { diff --git a/rair-front/src/components/SplashPage/Nipseyverse/index.tsx b/rair-front/src/components/SplashPage/Nipseyverse/index.tsx index a16946ce9..6bae2cd2d 100644 --- a/rair-front/src/components/SplashPage/Nipseyverse/index.tsx +++ b/rair-front/src/components/SplashPage/Nipseyverse/index.tsx @@ -101,7 +101,7 @@ const SplashPage: FC = ({ setIsSplashPage }) => { const targetBlockchain = '0x5'; // const nipseyAddress = '0xCB0252EeD5056De450Df4D8D291B4c5E8Af1D9A6'; - let subtitle: Modal; + const [subtitle] = useState(); const [modalIsOpen, setIsOpen] = useState(false); const [active, setActive] = useState({ policy: false, diff --git a/rair-front/src/hooks/useConnectUser.tsx b/rair-front/src/hooks/useConnectUser.tsx index 12a6b76cb..da4a23dd6 100644 --- a/rair-front/src/hooks/useConnectUser.tsx +++ b/rair-front/src/hooks/useConnectUser.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router'; +import { useLocation } from 'react-router-dom'; import { Action } from '@reduxjs/toolkit'; import axios from 'axios'; import { Hex } from 'viem'; @@ -13,6 +14,7 @@ import { OnboardingButton } from '../components/common/OnboardingButton/Onboardi import { dataStatuses } from '../redux/commonTypes'; import { loadCurrentUser } from '../redux/userSlice'; import { + connectChainAlchemyV4, connectChainMetamask, connectChainWeb3Auth, setConnectedChain, @@ -24,6 +26,7 @@ import { User } from '../types/databaseTypes'; import chainData from '../utils/blockchainData'; import { rFetch, + signWeb3MessageAlchemyV4, signWeb3MessageMetamask, signWeb3MessageWeb3Auth } from '../utils/rFetch'; @@ -74,6 +77,7 @@ const useConnectUser = () => { const reactSwal = useSwal(); const navigate = useNavigate(); + const location = useLocation(); const checkMetamask = useCallback(() => { setMetamaskInstalled(window?.ethereum && window?.ethereum?.isMetaMask); @@ -90,6 +94,29 @@ const useConnectUser = () => { }; }, [currentUserAddress]); + const loginWithAlchemySigner = useCallback(async () => { + const defaultChain: Hex = import.meta.env.VITE_DEFAULT_BLOCKCHAIN; + const chainInformation = getBlockchainData(defaultChain); + if ( + !chainInformation?.hash || + !chainInformation?.alchemy || + !chainInformation?.viem || + !chainInformation?.alchemyAppKey + ) { + return {}; + } + + const { connectedChain, currentUserAddress, userDetails } = await dispatch( + connectChainAlchemyV4(chainInformation as CombinedBlockchainData) + ).unwrap(); + + return { + userAddress: currentUserAddress, + blockchain: connectedChain, + userDetails + }; + }, [dispatch, getBlockchainData]); + const loginWithWeb3Auth = useCallback(async () => { const defaultChain: Hex = import.meta.env.VITE_DEFAULT_BLOCKCHAIN; const chainInformation = getBlockchainData(defaultChain); @@ -138,7 +165,7 @@ const useConnectUser = () => { return {}; } return { - userAddress: (await programmaticProvider.getAddress()) as Hex, + userAddress: (await programmaticProvider.address) as Hex, blockchain: connectedChain }; }, [connectedChain, programmaticProvider]); @@ -182,6 +209,12 @@ const useConnectUser = () => { onClick={() => resolve('web3auth')}> Social Logins +
+
Each social login creates a unique wallet address
@@ -209,147 +242,167 @@ const useConnectUser = () => { ] ); - const connectUserData = useCallback(async () => { - let loginData: { - userAddress?: Hex; - blockchain?: Hex; - userDetails?: any; - }; - const dispatchStack: Array = []; - const loginMethod: string = await selectMethod(); - reactSwal.close(); - try { - switch (loginMethod) { - case 'web3auth': - loginData = await loginWithWeb3Auth(); - break; - case 'metamask': - loginData = await loginWithMetamask(); - break; - case 'programmatic': - loginData = await loginWithProgrammaticProvider(); - break; - default: - reactSwal.fire({ - title: 'Please install a Crypto wallet', - html: ( -
- -
- ), - icon: 'error' - }); - return; - } - } catch (err) { - console.error('Login error', err); - return; - } - if (!loginData?.userAddress) { - reactSwal.fire('Error', 'No user address found', 'error'); - return; - } - - dispatchStack.push(setExchangeRates(await getCoingeckoRates())); - dispatchStack.push(setConnectedChain(loginData.blockchain)); - - let willUpdateUserData = false; - - try { - // Check if user exists in DB - const userDataResponse = await axios.get( - `/api/users/${loginData.userAddress}` - ); - let user = userDataResponse.data.user; - if (!userDataResponse.data.success || !user) { - // If the user doesn't exist, send a request to register him using a TEMP adminNFT - willUpdateUserData = true; - const relevantUserData = { publicAddress: loginData.userAddress }; - if (loginData?.userDetails?.email) { - relevantUserData['email'] = loginData.userDetails.email; - } - const userCreation = await axios.post( - '/api/users', - JSON.stringify(relevantUserData), - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json' - } - } - ); - user = userCreation.data.user; - } else if ( - !userDataResponse?.data?.user?.email && - loginData?.userDetails?.email - ) { - willUpdateUserData = true; + const connectUserData = useCallback( + async (loginMethod?: string) => { + let loginData: { + userAddress?: Hex; + blockchain?: Hex; + userDetails?: any; + }; + const dispatchStack: Array = []; + if (!loginMethod) { + loginMethod = await selectMethod(); } - - // Authorize user - if ( - adminRights === null || - adminRights === undefined || - !currentUserAddress - ) { - let loginResponse; + reactSwal.close(); + try { switch (loginMethod) { - case 'programmatic': - console.error('Programmatic support not available'); + case 'alchemyV4': + loginData = await loginWithAlchemySigner(); + break; + case 'web3auth': + loginData = await loginWithWeb3Auth(); break; case 'metamask': - loginResponse = await signWeb3MessageMetamask( - loginData.userAddress - ); + loginData = await loginWithMetamask(); break; - case 'web3auth': - loginResponse = await signWeb3MessageWeb3Auth( - loginData.userAddress - ); - reactSwal.close(); - if (willUpdateUserData) { - const userData = await loginData.userDetails; - const availableData: Partial = {}; - if (userData?.email && !loginResponse.user.email) { - availableData.email = userData.email; - } - if (userData?.email && !loginResponse.user.nickName) { - availableData.nickName = userData.email?.split('@')?.[0]; - } - if (userData.name && !userData.name.includes('@')) { - availableData.firstName = userData.name.split(' ')?.[0]; - availableData.lastName = userData.name.split(' ')?.[0]; + case 'programmatic': + loginData = await loginWithProgrammaticProvider(); + break; + default: + reactSwal.fire({ + title: 'Please install a Crypto wallet', + html: ( +
+ +
+ ), + icon: 'error' + }); + return; + } + } catch (err) { + console.error('Login error', err); + return; + } + if (!loginData?.userAddress) { + reactSwal.fire('Error', 'No user address found', 'error'); + return; + } + + dispatchStack.push(setExchangeRates(await getCoingeckoRates())); + dispatchStack.push(setConnectedChain(loginData.blockchain)); + + try { + // Check if user exists in DB + const userDataResponse = await axios.get( + `/api/users/${loginData.userAddress}` + ); + let user = userDataResponse.data.user; + if (!userDataResponse.data.success || !user) { + const userCreation = await axios.post( + '/api/users', + JSON.stringify({ publicAddress: loginData.userAddress }), + { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' } - const newUserResponse = await axios.patch( - `/api/users/${loginData.userAddress.toLowerCase()}`, - availableData - ); - user = newUserResponse.data.user; } - // provider.accountProvider.signTypedData - break; + ); + user = userCreation.data.user; } - dispatch(loadCurrentUser()); - if (loginResponse.success) { - dispatchStack.forEach((dispatchItem) => { - dispatch(dispatchItem); - }); - sockets.nodeSocket.connect(); + + // Authorize user + if ( + adminRights === null || + adminRights === undefined || + !currentUserAddress + ) { + let loginResponse; + switch (loginMethod) { + case 'programmatic': + console.error('Programmatic support not available'); + break; + case 'metamask': + loginResponse = await signWeb3MessageMetamask( + loginData.userAddress + ); + break; + case 'alchemyV4': + loginResponse = await signWeb3MessageAlchemyV4( + loginData.userAddress, + loginData.userDetails + ); + break; + case 'web3auth': + loginResponse = await signWeb3MessageWeb3Auth( + loginData.userAddress + ); + reactSwal.close(); + break; + } + + const updateData = {}; + if (!user?.gitHandle || !user?.email) { + // Try getting the git ID from social login + switch (loginMethod) { + case 'alchemyV4': + if (!user.email && loginData?.userDetails?.email) { + updateData['email'] = loginData.userDetails.email; + } + if (!user.gitHandle) { + updateData['gitId'] = ( + await loginData.userDetails.getAuthDetails() + ).claims.sub.split('|')[1]; + } + break; + case 'web3auth': + if (!user.email && loginData?.userDetails?.email) { + updateData['email'] = loginData.userDetails.email; + } + if (!user.gitHandle && loginData?.userDetails?.verifiedId) { + updateData['gitId'] = + loginData.userDetails.verifiedId.split('|')[1]; + } + break; + } + if (!user.nickName) { + updateData['nickName'] = updateData['email']?.split('@')?.[0]; + } + } + + if (Object.keys(updateData).length) { + console.info(updateData); + const newUserResponse = await axios.patch( + `/api/users/${loginData.userAddress.toLowerCase()}`, + updateData + ); + user = newUserResponse.data.user; + } + dispatch(loadCurrentUser()); + if (loginResponse.success) { + dispatchStack.forEach((dispatchItem) => { + dispatch(dispatchItem); + }); + sockets.nodeSocket.connect(); + } } + } catch (err) { + console.error('Error on login', err); } - } catch (err) { - console.error('Error on login', err); - } - }, [ - selectMethod, - loginWithMetamask, - loginWithProgrammaticProvider, - loginWithWeb3Auth, - reactSwal, - adminRights, - currentUserAddress, - dispatch - ]); + }, + [ + selectMethod, + reactSwal, + loginWithAlchemySigner, + loginWithWeb3Auth, + loginWithMetamask, + loginWithProgrammaticProvider, + adminRights, + currentUserAddress, + dispatch + ] + ); useEffect(() => { checkMetamask(); @@ -358,14 +411,25 @@ const useConnectUser = () => { const logoutUser = useCallback(async () => { const { success } = await rFetch('/api/auth/logout'); if (success) { + document.getElementById('rair-asif')?.replaceChildren(); dispatch(loadCurrentUser()); sockets.nodeSocket.emit('logout', currentUserAddress?.toLowerCase()); sockets.nodeSocket.disconnect(); dispatch(setProgrammaticProvider(undefined)); dispatch(setConnectedChain(import.meta.env.VITE_DEFAULT_BLOCKCHAIN)); - navigate('/'); + if ( + location.pathname.includes('creator') || + location.pathname.includes('demo') || + location.pathname.includes('settings') || + location.pathname.includes('admin') || + location.pathname.includes('on-sale') || + location.pathname.includes('user/videos') || + location.pathname.includes('license') + ) { + navigate('/', { replace: true }); + } } - }, [dispatch, navigate, currentUserAddress]); + }, [dispatch, navigate, currentUserAddress, location]); const checkLoginOnStart = useCallback(async () => { if (isLoggedIn || loginStatus !== dataStatuses.Uninitialized) { diff --git a/rair-front/src/hooks/useContracts.tsx b/rair-front/src/hooks/useContracts.tsx index 7d30aff69..c01865fa1 100644 --- a/rair-front/src/hooks/useContracts.tsx +++ b/rair-front/src/hooks/useContracts.tsx @@ -24,7 +24,9 @@ import { const useContracts = () => { const { getBlockchainData } = useServerSettings(); const { loginType, isLoggedIn } = useAppSelector((store) => store.user); - const { connectedChain } = useAppSelector((store) => store.web3); + const { connectedChain, programmaticProvider } = useAppSelector( + (store) => store.web3 + ); const [signer, setSigner] = useState< JsonRpcSigner | AccountSigner @@ -45,6 +47,19 @@ const useContracts = () => { Contract | undefined >(); + const createAlchemyV4Signer = useCallback(async () => { + const chainData = getBlockchainData(connectedChain); + + if (!chainData) { + return; + } + if (!programmaticProvider) { + return {}; + } + + return await programmaticProvider.account.getSigner(); + }, [connectedChain, getBlockchainData, programmaticProvider]); + const createWeb3AuthSigner = useCallback(async () => { const chainData = getBlockchainData(connectedChain); @@ -108,6 +123,9 @@ const useContracts = () => { return; } switch (loginType) { + case 'alchemyV4': + setSigner(await createAlchemyV4Signer()); + break; case 'metamask': if (!window.ethereum.isConnected()) { return; @@ -120,7 +138,7 @@ const useContracts = () => { setSigner(await createWeb3AuthSigner()); break; } - }, [loginType, isLoggedIn, createWeb3AuthSigner]); + }, [isLoggedIn, loginType, createAlchemyV4Signer, createWeb3AuthSigner]); useEffect(() => { refreshSigner(); diff --git a/rair-front/src/hooks/useWeb3Tx.ts b/rair-front/src/hooks/useWeb3Tx.ts index 6be6fd1dc..d005d3b94 100644 --- a/rair-front/src/hooks/useWeb3Tx.ts +++ b/rair-front/src/hooks/useWeb3Tx.ts @@ -342,6 +342,8 @@ const useWeb3Tx = () => { }); case 'web3auth': return programmaticProvider?.signMessage(message); + case 'alchemyV4': + return programmaticProvider?.signMessage(message); default: reactSwal.fire('Error', 'Please login.', 'error'); } @@ -367,6 +369,8 @@ const useWeb3Tx = () => { return metamaskCall(contract, method, args, options); case 'web3auth': return web3AuthCall(contract, method, args, options); + case 'alchemyV4': + return web3AuthCall(contract, method, args, options); default: reactSwal.fire('Error', 'Please login', 'error'); return undefined; diff --git a/rair-front/src/redux/web3Slice.ts b/rair-front/src/redux/web3Slice.ts index 037a6204e..9f5840908 100644 --- a/rair-front/src/redux/web3Slice.ts +++ b/rair-front/src/redux/web3Slice.ts @@ -1,10 +1,17 @@ +//@ts-nocheck +import { + alchemy, + AlchemySmartAccountClient, + createAlchemySmartAccountClient +} from '@account-kit/infra'; +import { AlchemyWebSigner } from '@account-kit/signer'; +import { createLightAccount } from '@account-kit/smart-contracts'; import { createModularAccountAlchemyClient } from '@alchemy/aa-alchemy'; -import { SmartContractAccount } from '@alchemy/aa-core'; -import { AccountSigner } from '@alchemy/aa-ethers'; import { Web3AuthSigner } from '@alchemy/aa-signers/web3auth'; import { Maybe } from '@metamask/providers/dist/utils'; import type { PayloadAction } from '@reduxjs/toolkit'; import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { WEB3AUTH_NETWORK } from '@web3auth/base'; import { BrowserProvider } from 'ethers'; import { Hex } from 'viem'; @@ -21,7 +28,7 @@ export interface ContractsState extends ChainData { web3Status: dataStatuses; connectedChain?: Hex; currentUserAddress?: Hex; - programmaticProvider?: AccountSigner; + programmaticProvider?: AlchemySmartAccountClient; requestedChain?: Hex; exchangeRates?: any; } @@ -37,9 +44,68 @@ const initialState: ContractsState = { exchangeRates: undefined }; +export const connectChainAlchemyV4 = createAsyncThunk( + 'web3/connectChainAlchemyV4', + async (chainData: CombinedBlockchainData) => { + if (!chainData.alchemyAppKey || !chainData.viem) { + return {}; + } + const signer = new AlchemyWebSigner({ + client: { + connection: { + // rpcUrl: chainData.viem.blockExplorers.alchemy.http[0], + apiKey: chainData.alchemyAppKey + // chain: chainInformation.viem, + // policyId: chainInformation.alchemyGasPolicy + }, + iframeConfig: { + iframeContainerId: 'rair-asif' // Alchemy signer iFrame + } + } + }); + if (!signer) { + return {}; + } + + const alchemyTransport = alchemy({ + apiKey: chainData.alchemyAppKey + }); + + await signer.preparePopupOauth(); + + const data = await signer.authenticate({ + type: 'oauth', + authProviderId: 'auth0', + auth0Connection: 'github', + mode: 'popup' + }); + + const client = createAlchemySmartAccountClient({ + transport: alchemyTransport, + policyId: chainData.alchemyGasPolicy, + chain: chainData.viem, + account: await createLightAccount({ + chain: chainData.viem, + transport: alchemyTransport, + signer + }) + }); + + return { + connectedChain: chainData.hash, + currentUserAddress: data.address, + userDetails: signer, + client + }; + } +); + export const connectChainWeb3Auth = createAsyncThunk( 'web3/connectChainWeb3Auth', async (chainData: CombinedBlockchainData) => { + if (!chainData.viem) { + return {}; + } const web3AuthSigner = new Web3AuthSigner({ clientId: import.meta.env.VITE_WEB3AUTH_CLIENT_ID, chainConfig: { @@ -52,8 +118,8 @@ export const connectChainWeb3Auth = createAsyncThunk( tickerName: chainData.name }, web3AuthNetwork: chainData.testnet - ? 'sapphire_devnet' - : 'sapphire_mainnet' + ? WEB3AUTH_NETWORK.SAPPHIRE_DEVNET + : WEB3AUTH_NETWORK.SAPPHIRE_MAINNET }); await web3AuthSigner.authenticate({ init: async () => { @@ -66,7 +132,7 @@ export const connectChainWeb3Auth = createAsyncThunk( const modularAccount = await createModularAccountAlchemyClient({ apiKey: chainData.alchemyAppKey, - chain: chainData.viem!, + chain: chainData.viem, signer: web3AuthSigner, gasManagerConfig: chainData.alchemyGasPolicy ? { @@ -152,7 +218,7 @@ export const web3Slice = createSlice({ }) .addCase(connectChainWeb3Auth.fulfilled, (state, action) => { state.web3Status = dataStatuses.Complete; - if (action.payload.connectedChain) { + if (action.payload?.connectedChain) { state.connectedChain = action.payload.connectedChain; } if (action.payload.currentUserAddress) { @@ -162,6 +228,25 @@ export const web3Slice = createSlice({ }) .addCase(connectChainWeb3Auth.rejected, (state) => { state.web3Status = dataStatuses.Failed; + }) + .addCase(connectChainAlchemyV4.pending, (state) => { + state.web3Status = dataStatuses.Loading; + }) + .addCase(connectChainAlchemyV4.fulfilled, (state, action) => { + state.web3Status = dataStatuses.Complete; + if (action.payload.connectedChain) { + state.connectedChain = action.payload.connectedChain; + } + if (action.payload.currentUserAddress) { + state.currentUserAddress = + action.payload.currentUserAddress.toLowerCase() as Hex; + } + if (action.payload.userDetails) { + state.programmaticProvider = action.payload.client; + } + }) + .addCase(connectChainAlchemyV4.rejected, (state) => { + state.web3Status = dataStatuses.Failed; }); } }); diff --git a/rair-front/src/types/databaseTypes.ts b/rair-front/src/types/databaseTypes.ts index e16414b15..569a9a324 100644 --- a/rair-front/src/types/databaseTypes.ts +++ b/rair-front/src/types/databaseTypes.ts @@ -106,6 +106,7 @@ export interface User extends MongoDocument { publicAddress?: Hex; creationDate?: string; blocked?: boolean; + gitHandle?: string; } export interface MetadataAttribute { diff --git a/rair-front/src/utils/blockchainData.ts b/rair-front/src/utils/blockchainData.ts index 9967db2a0..d62cbf032 100644 --- a/rair-front/src/utils/blockchainData.ts +++ b/rair-front/src/utils/blockchainData.ts @@ -1,4 +1,4 @@ -import { base, mainnet, polygon, sepolia } from '@alchemy/aa-core'; +import { base, mainnet, polygon, sepolia } from '@account-kit/infra'; import { Network } from 'alchemy-sdk'; import { Hex } from 'viem'; @@ -7,8 +7,8 @@ import { TChainData } from './utils.types'; import { AstarLogo, BaseLogo, - EthereumLogo, CoreIdLogo, + EthereumLogo, MaticLogo, SoniumLogo } from '../images'; @@ -133,7 +133,7 @@ const chainData: TChainData = { alchemyAppKey: undefined, alchemyGasPolicy: undefined } -} +}; export default chainData; diff --git a/rair-front/src/utils/rFetch.ts b/rair-front/src/utils/rFetch.ts index 569151130..7dee4787a 100644 --- a/rair-front/src/utils/rFetch.ts +++ b/rair-front/src/utils/rFetch.ts @@ -71,6 +71,21 @@ const signWeb3MessageMetamask = async (userAddress: Hex) => { } }; +const signWeb3MessageAlchemyV4 = async (userAddress: Hex, signer) => { + if (!signer) { + return; + } + + const challenge = await getChallenge(userAddress); + + const parsedResponse = JSON.parse(challenge); + const signedChallenge = await signer.signTypedData(parsedResponse); + + if (signedChallenge) { + return await respondChallenge(challenge, signedChallenge); + } +}; + const signWeb3MessageWeb3Auth = async (userAddress: Hex) => { const web3AuthSigner = new Web3AuthSigner({ clientId: import.meta.env.VITE_WEB3AUTH_CLIENT_ID, @@ -100,7 +115,7 @@ const signWeb3MessageWeb3Auth = async (userAddress: Hex) => { body: JSON.stringify({ MetaMessage: parsedResponse.message.challenge, MetaSignature: signedChallenge, - userAddress + userAddress: userAddress }), headers: { 'Content-Type': 'application/json' @@ -156,4 +171,10 @@ const rFetch = async ( return request; }; -export { rFetch, signIn, signWeb3MessageMetamask, signWeb3MessageWeb3Auth }; +export { + rFetch, + signIn, + signWeb3MessageAlchemyV4, + signWeb3MessageMetamask, + signWeb3MessageWeb3Auth +}; diff --git a/rair-front/src/utils/utils.types.ts b/rair-front/src/utils/utils.types.ts index c06a08385..f58e4098a 100644 --- a/rair-front/src/utils/utils.types.ts +++ b/rair-front/src/utils/utils.types.ts @@ -1,6 +1,5 @@ -import { chains } from '@alchemy/aa-core'; import { Network } from 'alchemy-sdk'; -import { Hex } from 'viem'; +import { Chain, Hex } from 'viem'; export type TNativeCurrency = { name: string; @@ -14,7 +13,7 @@ export type TChainItemData = { name: string; chainId: Hex; disabled?: boolean; - viem?: chains.Chain; + viem?: Chain; alchemy?: Network; coingecko?: string; alchemyAppKey?: string; diff --git a/rair-front/vite.config.ts b/rair-front/vite.config.ts index 5d433132c..01ba4f3ec 100644 --- a/rair-front/vite.config.ts +++ b/rair-front/vite.config.ts @@ -5,6 +5,9 @@ import svgr from 'vite-plugin-svgr'; // https://vitejs.dev/config/ export default defineConfig({ + optimizeDeps: { + include: ['@mui/material/Tooltip'] + }, plugins: [react(), nodePolyfills(), svgr()], server: { port: 3001, diff --git a/rair-infra/assets/sdk-tests/.env.sample b/rair-infra/assets/sdk-tests/.env.sample new file mode 100644 index 000000000..5a224f2bd --- /dev/null +++ b/rair-infra/assets/sdk-tests/.env.sample @@ -0,0 +1,21 @@ +SERVER_URL="http://34.171.253.61:5000" +SOCKET_URL="" + +USER_ADDRESS= +USER_NICKNAME= + +CONTRACT_ADDRESS1= +CONTRACT_ID1= +PRODUCT1_CONTRACT1= +PRODUCT1_OFFER1_CONTRACT1= +BLOCKCHAIN1= +CONTRACT_ADDRESS2= +CONTRACT_ID2= +BLOCKCHAIN2= + +MEDIAID1= +MEDIAID1_DESCRIPTION= + +CATEGORY_ID1= + +TOKENID1= diff --git a/rair-infra/assets/sdk-tests/test/test-util.ts b/rair-infra/assets/sdk-tests/test/test-util.ts new file mode 100644 index 000000000..8a570dda7 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/test-util.ts @@ -0,0 +1,20 @@ +const Dotenv = require('dotenv'); +Dotenv.config(); +module.exports = { + serverURL: process.env.SERVER_URL, + socketURL: process.env.SOCKET_URL, + User_Address: process.env.USER_ADDRESS, + User_NickName: process.env.USER_NICKNAME, + Contract_Address1: process.env.CONTRACT_ADDRESS1, + Contract_Id1: process.env.CONTRACT_ID1, + Product1_Contract1: process.env.PRODUCT1_CONTRACT1, + Product1_Offer1_Contract1: process.env.PRODUCT1_OFFER1_CONTRACT1, + Blockchain1: process.env.BLOCKCHAIN1, + Contract_Address2: process.env.CONTRACT_ADDRESS2, + Contract_Id2: process.env.CONTRACT_ID2, + Blockchain2: process.env.BLOCKCHAIN2, + MediaId1: process.env.MEDIAID1, + MediaId1_Description: process.env.MEDIAID1_DESCRIPTION, + Category_Id1: process.env.CATEGORY_ID1, + TokenId1: process.env.TOKENID1 +}; diff --git a/rair-infra/assets/sdk-tests/test/unit/analytics.test.ts b/rair-infra/assets/sdk-tests/test/unit/analytics.test.ts new file mode 100644 index 000000000..280fe3dbf --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/analytics.test.ts @@ -0,0 +1,24 @@ +const { serverURL } = require('../test-util'); +//import Api from '../src/common/Api'; +import { AnalyticsAPI } from '../../src/API/analytics'; +//console.log(process.env.SERVER_URL) + +const _analytics = new AnalyticsAPI(serverURL, "analytics"); + +//categories.getCategories().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it.skip('Get the analytics report for a specific file', async () => { + const __analytics = await _analytics.fromMedia({mediaId: 'NSdkxJPPY5FAfBMJtZhdnC8wQbExevzJMJQa8NAIEGDGu6'}); + expect(__analytics.totalCount).toEqual('3'); + }); + + it.skip('Get the analytics report for a specific file in downloadable CSV form', async () => { + const __analytics = await _analytics.fromMediaAsCSV({mediaId: 'NSdkxJPPY5FAfBMJtZhdnC8wQbExevzJMJQa8NAIEGDGu6'}); + expect(__analytics.totalCount).toEqual('3'); + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/auth.test.ts b/rair-infra/assets/sdk-tests/test/unit/auth.test.ts new file mode 100644 index 000000000..0cf4fef35 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/auth.test.ts @@ -0,0 +1,59 @@ +const { serverURL, User_Address, User_NickName, MediaId1 } = require('../test-util'); +//import Api from '../src/common/Api'; +import { AuthAPI } from '../../src/API/auth'; +import { Intents, FileTypes } from '../../src/types/auth'; +//console.log(process.env.SERVER_URL) + +const _auth = new AuthAPI(serverURL, "auth"); + +let __challenge = ""; + +//_auth.getChallenge({userAddress: User_Address, intent: Intents['Login']}).then(console.log); + +jest.setTimeout(50000); +describe('E2E integration tests', () => { + + it('checks if getChallenge matches', async () => { + const __auth = await _auth.getChallenge({userAddress: User_Address, intent: Intents['Login']}); + expect(__auth.response).toContain('EIP712Domain'); + const _challenge = JSON.parse(__auth.response); + //console.log(_challenge) + __challenge = _challenge.message["challenge"] + //console.log(__challenge) + }); + + it.skip('Login web3', async () => { + console.log(__challenge) + const __auth = await _auth.loginWeb3({MetaMessage: "__challenge", MetaSignature: '0x0d625145ecf9d89d3a1cc8195a9770626f0e695f636c261cda1255e680ccb17c5cf5c307d394f8d633cd9ad8a418d7f09ad602bde6abf690785a9c4612f679cb1c', userAddress: User_Address}); + expect(__auth.user.nickName).toContain(User_NickName); + }); + + it.skip('Get the signature challenge to login into the system with web3Auth', async () => { + //console.log(__challenge) + const __auth = await _auth.loginSmartAccount({MetaMessage: "__challenge", MetaSignature: '0x0d625145ecf9d89d3a1cc8195a9770626f0e695f636c261cda1255e680ccb17c5cf5c307d394f8d633cd9ad8a418d7f09ad602bde6abf690785a9c4612f679cb1c', userAddress: User_Address}); + expect(__auth.user.nickName).toContain(User_NickName); + + }); + + + it('Closes the current users session in the system', async () => { + const __auth = await _auth.logout(); + expect(__auth.success).toBeTruthy; + }); + + it('Returns the information of the current user', async () => { + const __auth = await _auth.currentUser(); + expect(__auth).toBeFalsy; + }); + + it('Stops any file stream', async () => { + const __auth = await _auth.endFileStream(); + expect(__auth.success).toBeTruthy; + }); + + it.skip('Verify NFT ownership to unlock media file', async () => { + const __auth = await _auth.unlock({type: FileTypes['file'], fileId: MediaId1}); + expect(__auth.success).toBeFalsy; + }); + +}); \ No newline at end of file diff --git a/rair-infra/assets/sdk-tests/test/unit/categories.test.ts b/rair-infra/assets/sdk-tests/test/unit/categories.test.ts new file mode 100644 index 000000000..2599876c7 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/categories.test.ts @@ -0,0 +1,28 @@ +const { serverURL } = require('../test-util'); +//const request = require('supertest'); + +//request('https://dog.ceo') + +//import Api from '../src/common/Api'; +import { CategoriesAPI } from '../../src/API/categories'; +//console.log(process.env.SERVER_URL) + +const _categories = new CategoriesAPI(serverURL, "categories"); + +//categories.getCategories().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('checks if category matches', async () => { + const __categories = await _categories.getCategories(); + expect(__categories.result[0].name).toEqual('Music'); + }); + + it.skip('update category', async () => { + const __categories = await _categories.updateCategory({list: []}); + expect(__categories.result[0].name).toEqual('Music'); + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/contracts.test.ts b/rair-infra/assets/sdk-tests/test/unit/contracts.test.ts new file mode 100644 index 000000000..2036f90a0 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/contracts.test.ts @@ -0,0 +1,67 @@ +const { serverURL, Contract_Address1, Contract_Id1, Product1_Contract1, Product1_Offer1_Contract1, Contract_Address2, Blockchain1 } = require('../test-util'); +//import Api from '../src/common/Api'; +import { ContractAPI } from '../../src/API/contracts'; +//import { GetContractListParams } from '../../src/types/contracts'; +//console.log(process.env.SERVER_URL) + +const _contracts = new ContractAPI(serverURL, "contracts"); + +//_contracts.getProductsById({id: "667e5e30a26ca7419095b588"}).then(console.log); + +jest.setTimeout(50000); +describe('E2E integration tests', () => { + + + it('get contract list', async () => { + const __contracts = await _contracts.getContractList({pageNum:1, itemsPerPage: 10}); + expect(__contracts.totalCount).toEqual(23); + }); + + it.skip('Fetch an extended list of contracts', async () => { + const __contracts = await _contracts.getFactoryList(); + expect(__contracts.contracts[0].contractAddress).toEqual(Contract_Address1); + }); + + it.skip('List all contracts made by the current user', async () => { + const __contracts = await _contracts.getMyContracts({userAddress: Contract_Address1}); + expect(__contracts.totalCount).toEqual(1); + }); + + + it('List contract data for the frontend catalog', async () => { + const __contracts = await _contracts.getFullListOfContracts({}); + expect(__contracts.contracts[0].contractAddress).toEqual(Contract_Address2); + }); + + it('Search for a contract using network and address', async () => { + const __contracts = await _contracts.findContract({networkId: Blockchain1, contractAddress: Contract_Address1}); + expect(__contracts.contract.contractAddress).toEqual(Contract_Address1); + }); + + it('Search for a contract using network and address, include products', async () => { + const __contracts = await _contracts.findContractAndProducts({networkId: Blockchain1, contractAddress: Contract_Address1}); + expect(__contracts.products[0].collectionIndexInContract).toEqual('0'); + }); + + it('Search for a contract using network and address, include offers', async () => { + const __contracts = await _contracts.findContractAndOffers({networkId: Blockchain1, contractAddress: Contract_Address1}); + expect(__contracts.products[0].offers[0].offerName).toContain(Product1_Offer1_Contract1); + }); + + it('Get a single contract by id', async () => { + const __contracts = await _contracts.getById({id: Contract_Id1}); + expect(__contracts.contract.blockchain).toEqual(Blockchain1); + }); + + it('Get a single contract by id, include product data', async () => { + const __products = await _contracts.getProductsById({id: Contract_Id1}); + expect(__products.products[0].name).toEqual(Product1_Contract1); + }); + + it.skip('Update information about a contract', async () => { + const __contracts = await _contracts.updateContract({id: Contract_Id1, blockSync: false, blockView: false}); + expect(__contracts.data).toContain(Product1_Contract1); + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/credits.test.ts b/rair-infra/assets/sdk-tests/test/unit/credits.test.ts new file mode 100644 index 000000000..eeb80fcc1 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/credits.test.ts @@ -0,0 +1,19 @@ +const { serverURL } = require('../test-util'); +//import Api from '../src/common/Api'; +import { CreditsAPI } from '../../src/API/credits'; +//console.log(process.env.SERVER_URL) + +const _credits = new CreditsAPI(serverURL, "credits"); + +//categories.getCategories().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it.skip('Get the analytics report for a specific file', async () => { + const __credits = await _credits.getUserCredits({blockchain: '0x2105', tokenAddress: '0x762BbcF6E6486fbee13a5CeE291F7aEE14f1CA77'}); + expect(__credits.credits).toEqual('3'); + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/favorites.test.ts b/rair-infra/assets/sdk-tests/test/unit/favorites.test.ts new file mode 100644 index 000000000..86aadfb42 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/favorites.test.ts @@ -0,0 +1,36 @@ +const { serverURL, User_Address } = require('../test-util'); +//import Api from '../src/common/Api'; +import { FavoritesAPI } from '../../src/API/favorites'; +//console.log(process.env.SERVER_URL) + +const _favorites = new FavoritesAPI(serverURL, "favorites"); + +//categories.getCategories().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it.skip('Register a token as a favorite', async () => { + const __favorites = await _favorites.createFavorite({token: '6748a7c84a7fa5cfeb6117d7'}); + expect(__favorites.success).toBeTruthy; + + }); + + it.skip('List an user favorite tokens', async () => { + const __favorites = await _favorites.getAllFavoritesForUser({pageNum:1, itemsPerPage:10}); + expect(__favorites.result).toEqual('3'); + }); + + it('List favorite token for a user', async () => { + const __favorites = await _favorites.getAllFavoritesOfAddress({userAddress: User_Address}); + expect(__favorites.success).toBeTruthy; + }); + + it.skip('Delete a favorite record', async () => { + const __favorites = await _favorites.deleteFavorite({id: '6748a7c84a7fa5cfeb6117d7'}); + expect(__favorites.success).toBeTruthy; + + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/files.test.ts b/rair-infra/assets/sdk-tests/test/unit/files.test.ts new file mode 100644 index 000000000..c60444ab1 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/files.test.ts @@ -0,0 +1,44 @@ +const { serverURL, MediaId1, MediaId1_Description, Category_Id1, TokenId1 } = require('../test-util'); +//import Api from '../src/common/Api'; +import { FilesAPI } from '../../src/API/files'; +//console.log(process.env.SERVER_URL) + +const _media = new FilesAPI(serverURL, "files"); + +//user.findUserByUserAddress({publicAddress: `0x8B1b77BF1a23951Ae0F1dff8162C5A67632aF224`}).then(console.log); + +//_media.getFileById({id: 'jAL8Q4_I_X3BQYcDwF-_yCPCb4qkTgZ3Jtb1Omcih9MJnw'}).then(console.log); + + +//console.log(new_user); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('List media', async () => { + const __media = await _media.listMedia({pageNum:1 , itemsPerPage: 10}) + expect(__media.totalNumber).toEqual(1); + }); + + it('Fetch information about a single file', async () => { + const __media = await _media.getFileById({id: MediaId1}) + expect(__media.file.description).toContain(MediaId1_Description); + }); + + it('List all files under a specific category', async () => { + const __media = await _media.getFilesByCategory({id: Category_Id1}) + expect(__media.success).toBeTruthy; + }); + + it('Search media files given a token ID', async () => { + const __media = await _media.getFilesByToken({id: TokenId1}) + expect(__media.results).toEqual(0); + }); + + it('Get all offers associated with a file', async () => { + const __media = await _media.getOffersByFile({id: MediaId1}) + expect(__media.success).toBeTruthy; + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/nft.test.ts b/rair-infra/assets/sdk-tests/test/unit/nft.test.ts new file mode 100644 index 000000000..7e986e00e --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/nft.test.ts @@ -0,0 +1,77 @@ +const { serverURL, User_Address, Contract_Address1 } = require('../test-util'); +//import Api from '../src/common/Api'; +import { NftAPI } from '../../src/API/nft'; +//console.log(process.env.SERVER_URL) + +const _nft = new NftAPI(serverURL, "nft"); + +//user.findUserByUserAddress({publicAddress: `0x8B1b77BF1a23951Ae0F1dff8162C5A67632aF224`}).then(console.log); +console.log(User_Address) + +jest.setTimeout(500000); +describe('Unit tests', () => { + + it('getTokensForUser', async () => { + const __nft = await _nft.getTokensForUser({userAddress: User_Address}) + expect(__nft.totalCount).toEqual((71)); + }); + + + it.skip('CSV Sample', async () => { + const __nft = await _nft.getCSVSample() + expect(__nft).toContain(('NFT')); + }); + + it.skip('findTokensForProduct', async () => { + const __nft = await _nft.findTokensForProduct({ + networkId: "0x2105", contract: Contract_Address1, product: 0}); + expect(__nft.totalCount).toEqual((10)); + }); + + it('findTokenNumbersForProduct', async () => { + const __nft = await _nft.findTokenNumbersForProduct({ + networkId: "0x2105", contract: "0xfc3666266d129504dd6c713f9bce107747ae4aee", product: 0, + fromToken: '0', + toToken: '10' + }); + expect(__nft.success).toBeTruthy; + }); + + it('findProductAttributes', async () => { + const __nft = await _nft.findProductAttributes({ + networkId: "0x2105", contract: "0xfc3666266d129504dd6c713f9bce107747ae4aee", product: 0}); + expect(__nft.success).toBeTruthy; + }); + + it('findFilesForProduct', async () => { + const __nft = await _nft.findFilesForProduct({ + networkId: "0x2105", contract: "0xfc3666266d129504dd6c713f9bce107747ae4aee", product: 0}); + expect(__nft.success).toBeTruthy; + }); + + it('findFilesForTokenInProduct', async () => { + const __nft = await _nft.findFilesForTokenInProduct({ + networkId: "0x2105", contract: "0xfc3666266d129504dd6c713f9bce107747ae4aee", product: 0, token: 1}); + expect(__nft.files[0].description).toEqual(("test")); + }); + + + it('findOffersForProduct', async () => { + const __nft = await _nft.findOffersForProduct({ + networkId: "0x2105", contract: "0xfc3666266d129504dd6c713f9bce107747ae4aee", product: 0}); + expect(__nft.product.copies).toEqual((10000)); + }); + + it('findLockedOffersForProduct', async () => { + const __nft = await _nft.findLockedOffersForProduct({ + networkId: "0x2105", contract: "0xfc3666266d129504dd6c713f9bce107747ae4aee", product: 0}); + expect(__nft.locks[0].lockedCopies).toEqual((1)); + }); + + it('findSingleToken', async () => { + const __nft = await _nft.findSingleToken({ + networkId: "0x2105", contract: "0xfc3666266d129504dd6c713f9bce107747ae4aee", product: 0, token: 1}); + expect(__nft.result.ownerAddress).toEqual(("0x9df2f5619e924c2f157b5153a040ab123f5a6420")); + }); + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/notifications.test.ts b/rair-infra/assets/sdk-tests/test/unit/notifications.test.ts new file mode 100644 index 000000000..51257259c --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/notifications.test.ts @@ -0,0 +1,33 @@ +const { serverURL } = require('../test-util'); +//import Api from '../src/common/Api'; +import { NotificationsAPI } from '../../src/API/notifications'; +//console.log(process.env.SERVER_URL) + +const _notifications = new NotificationsAPI(serverURL, "notifications"); + +//categories.getCategories().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it.skip('Fetch a list of notifications for the user', async () => { + const __notifications = await _notifications.listNotifications({onlyUnread: true}); + expect(__notifications.totalCount).toEqual('10'); + }); + + it.skip('Fetch a single notification', async () => { + const __notifications = await _notifications.getSingleNotification({id: '43223423'}); + expect(__notifications.success).toEqual('10'); + }); + + it.skip('mark the notification as read', async () => { + const __notifications = await _notifications.markNotificationAsRead({ids: ["66a39c2f24fe94ba7079691c"]}); + expect(__notifications.updated).toEqual(1); + }); + + it.skip('Delete notification', async () => { + const __notifications = await _notifications.deleteNotification({ids: ["66a39c2f24fe94ba7079691c"]}); + expect(__notifications.deleted).toEqual(1); + }); + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/offers.test.ts b/rair-infra/assets/sdk-tests/test/unit/offers.test.ts new file mode 100644 index 000000000..918162b70 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/offers.test.ts @@ -0,0 +1,23 @@ +const { serverURL } = require('../test-util'); +//import Api from '../src/common/Api'; +import { OffersAPI } from '../../src/API/offers'; +//console.log(process.env.SERVER_URL) + +const _offers = new OffersAPI(serverURL, "offers"); + +//user.findUserByUserAddress({publicAddress: `0x8B1b77BF1a23951Ae0F1dff8162C5A67632aF224`}).then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('getAllOffers', async () => { + const __offers = await _offers.getAllOffers({contract: '6731bc09ca3b458d029362fd'}); + expect(__offers.results).toEqual((1)); + }); + + it.skip('getAvailableOffers', async () => { + const __offers = await _offers.getAvailableOffers({id: '67351be174592808d65dfed6'}); + expect(__offers.availableTokens).toEqual((50)); + }); + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/products.test.ts b/rair-infra/assets/sdk-tests/test/unit/products.test.ts new file mode 100644 index 000000000..6fc1d545e --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/products.test.ts @@ -0,0 +1,24 @@ +const { serverURL, User_Address } = require('../test-util'); +//import Api from '../src/common/Api'; +import { ProductsAPI } from '../../src/API/products'; +//console.log(process.env.SERVER_URL) + +const _products = new ProductsAPI(serverURL, "products"); + +//user.findUserByUserAddress({publicAddress: `0x8B1b77BF1a23951Ae0F1dff8162C5A67632aF224`}).then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('listProduts', async () => { + const __products = await _products.listProduct({}); + expect(__products.results).toEqual((27)); //total products in Database + }); + + it('Get all products from an user', async () => { + const __products = await _products.getProductsByUser({userAddress: User_Address}); + expect(__products.products[0].copies).toEqual(500); + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/resales.test.ts b/rair-infra/assets/sdk-tests/test/unit/resales.test.ts new file mode 100644 index 000000000..c70fcfd70 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/resales.test.ts @@ -0,0 +1,19 @@ +const { serverURL, User_Address } = require('../test-util'); +//import Api from '../src/common/Api'; +import { ResalesAPI } from '../../src/API/resales'; +//console.log(process.env.SERVER_URL) + +const _resales = new ResalesAPI(serverURL, "resales"); + +//categories.getCategories().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('List all open resale offers', async () => { + const __resales = await _resales.openOffers({blockchain: '0x2105'}); + expect(__resales.data[0].seller).toEqual(User_Address); + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/search.test.ts b/rair-infra/assets/sdk-tests/test/unit/search.test.ts new file mode 100644 index 000000000..6d7c95098 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/search.test.ts @@ -0,0 +1,24 @@ +const { serverURL } = require('../test-util'); +//import Api from '../src/common/Api'; +import { SearchAPI } from '../../src/API/search'; +//console.log(process.env.SERVER_URL) + +const _search = new SearchAPI(serverURL, "search"); + +//categories.getCategories().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('checks if Text Search matches', async () => { + const __search = await _search.textSearch({textParam: 'Free'}); + expect(__search.data.products[0].name).toEqual('RAIR Free License'); + }); + + it('checks if Text Search All matches', async () => { + const __search = await _search.textSearchAll({textParam: 'user-1'}); + expect(__search.data.users[0].nickName).toContain('@user-1'); + }); + + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/settings.test.ts b/rair-infra/assets/sdk-tests/test/unit/settings.test.ts new file mode 100644 index 000000000..bbdd514e4 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/settings.test.ts @@ -0,0 +1,50 @@ +const { serverURL } = require('../test-util'); +//import Api from '../src/common/Api'; +import { SettingsAPI } from '../../src/API/settings'; +//console.log(process.env.SERVER_URL) + +const _settings = new SettingsAPI(serverURL, "settings"); + + +//_settings.getTheming().then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('Get settings', async () => { + const __settings = await _settings.getSettings() + expect(__settings.settings.signupMessage).toContain("Welcome"); + }); + + it('Get Theming', async () => { + const __settings = await _settings.getTheming() + expect(__settings.success).toBeTruthy; + }); + + it('Get Featured Collection', async () => { + const __settings = await _settings.getFeaturedCollection() + expect(__settings.data.collectionName).toContain("RAIR Free License"); + }); + + it.skip('Set Settings', async () => { + const __settings = await _settings.setSetting({darkModeText: "", onlyMintedTokensResult: false, demoUploadsEnabled: true, nodeAddress: ""}) + expect(__settings.success).toBeTruthy; + }); + + + it.skip('Add Blockchain', async () => { + const __settings = await _settings.addBlockchain({hash: '0x1', name: "Ethereum", alchemySupport: false}) + expect(__settings.success).toBeTruthy; + }); + + it.skip('Modify Blockchain', async () => { + const __settings = await _settings.modifyBlockchain({hash: '0x1', name: "Ethereum", alchemySupport: false}) + expect(__settings.success).toBeTruthy; + }); + + it.skip('Delete Blockchain', async () => { + const __settings = await _settings.removeBlockchain({hash: '0x1'}) + expect(__settings.success).toBeTruthy; + }); + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/tokens.test.ts b/rair-infra/assets/sdk-tests/test/unit/tokens.test.ts new file mode 100644 index 000000000..ab980c13c --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/tokens.test.ts @@ -0,0 +1,28 @@ +const { serverURL, Contract_Id1 } = require('../test-util'); +//import Api from '../src/common/Api'; +import { TokensAPI } from '../../src/API/tokens'; +//console.log(process.env.SERVER_URL) + +const _tokens = new TokensAPI(serverURL, "tokens"); + +//user.findUserByUserAddress({publicAddress: `0x8B1b77BF1a23951Ae0F1dff8162C5A67632aF224`}).then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('getAllTokens', async () => { + const __tokens = await _tokens.getAllTokens({contract: Contract_Id1}); + expect(__tokens.results).toEqual((10000)); + }); + + it('Get information for a single token', async () => { + const __tokens = await _tokens.getSingleToken({contract: Contract_Id1, token: '0' , offerPool: '0', offers: '0'}); + expect(__tokens.data.doc.ownerAddress).toContain(('0xf3fc93b77a1a39610aa800734dfd017ca293e53d')); + }); + + it.skip('Get data for a specific token', async () => { + const __tokens = await _tokens.getFullTokenInfo({id: '67351be174592808d65dfeda'}); + expect(__tokens.tokenData.ownerAddress).toContain(('0xf3fc93b77a1a39610aa800734dfd017ca293e53d')); + }); + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/upload.test.ts b/rair-infra/assets/sdk-tests/test/unit/upload.test.ts new file mode 100644 index 000000000..348cabe8a --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/upload.test.ts @@ -0,0 +1,18 @@ +const { serverURL } = require('../test-util'); +//import Api from '../src/common/Api'; +import { UploadAPI } from '../../src/API/upload'; +//console.log(process.env.SERVER_URL) + +const _upload = new UploadAPI(serverURL, "upload"); + +//user.findUserByUserAddress({publicAddress: `0x8B1b77BF1a23951Ae0F1dff8162C5A67632aF224`}).then(console.log); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('getUploadToken', async () => { + const __upload = await _upload.getUploadToken(); + expect(__upload.success).toBeFalsy; + }); + +}); diff --git a/rair-infra/assets/sdk-tests/test/unit/users.test.ts b/rair-infra/assets/sdk-tests/test/unit/users.test.ts new file mode 100644 index 000000000..85c275994 --- /dev/null +++ b/rair-infra/assets/sdk-tests/test/unit/users.test.ts @@ -0,0 +1,51 @@ +const { serverURL, User_Address } = require('../test-util'); +//import Api from '../src/common/Api'; +import { UsersAPI } from '../../src/API/users'; +//console.log(process.env.SERVER_URL) + +const _user = new UsersAPI(serverURL, "users"); + +//user.findUserByUserAddress({publicAddress: `0x8B1b77BF1a23951Ae0F1dff8162C5A67632aF224`}).then(console.log); + +//_user.listUsers({itemsPerPage: 8, pageNum: 0, fields: ['publicAddress, nickName']}).then(console.log); + +var crypto = require('crypto'); +const randomString = crypto.randomBytes(20).toString("hex"); + +console.log(randomString); + +jest.setTimeout(50000); +describe('Unit tests', () => { + + it('List users', async () => { + const __user = await _user.listUsers({itemsPerPage: 8, pageNum: 0, fields: ['publicAddress, nickName'] }) + expect(__user.data[0].publicAddress).toEqual(User_Address); + }); + + it.skip('exportUserData', async () => { + const __user = await _user.exportUserData() + expect(__user).toContain("user"); + }); + + it.skip('Verify Age', async () => { + //const __user = await _user.verifyAge({file: test.jpg}) + //expect(__user.success).toBeFalsy; + }); + + it('create new user', async () => { + const __user = await _user.createUser({publicAddress: `0x${randomString}`}) + expect(__user.user.publicAddress).toEqual((`0x${randomString}`).toLowerCase()); + }); + + it('checks if user details matches', async () => { + const __user = await _user.findUserByUserAddress({publicAddress: User_Address}) + expect(__user.user.publicAddress).toEqual((User_Address).toLowerCase()); + }); + + it.skip('Update user details', async () => { + const __user = await _user.updateUserByUserAddress({publicAddress: User_Address, nickName: 'qa-update'}) + expect(__user.user.nickName).toEqual(('qa-update')); + }); + + +}); diff --git a/rair-node/bin/api/auth/auth.Service.js b/rair-node/bin/api/auth/auth.Service.js index d51322d3f..d8c1c4ca5 100644 --- a/rair-node/bin/api/auth/auth.Service.js +++ b/rair-node/bin/api/auth/auth.Service.js @@ -62,7 +62,9 @@ module.exports = { loginFromSignature: async (req, res, next) => { const ethAddress = req?.metaAuth?.recovered; if (ethAddress) { - const userData = await User.findOne({ publicAddress: ethAddress }, '-creationDate -nonce').lean(); + const userData = await User.findOne({ + publicAddress: ethAddress.toLowerCase(), + }, '-creationDate -nonce').lean(); if (userData === null) { return next(new AppError('User not found.', 404)); } @@ -87,7 +89,8 @@ module.exports = { return next(new AppError('Authentication failed.', 403)); } - userData.adminRights = await checkAdminTokenOwns(userData.publicAddress); + // Uncomment to enable NFT check on login + // userData.adminRights = await checkAdminTokenOwns(userData.publicAddress); const { superAdmins, superAdminsOnVault, signupMessage } = await ServerSetting.findOne({}); const socket = req.app.get('socket'); emitEvent(socket)( @@ -100,6 +103,10 @@ module.exports = { ? await superAdminInstance.hasSuperAdminRights(userData.publicAddress) : superAdmins.includes(userData.publicAddress); userData.oreId = req?.metaAuth?.oreId; + + // Delete this line to restore NFT check on login + userData.adminRights = userData.superAdmin; + req.session.userData = { ...userData, loginType: req.web3LoginMethod }; // eslint-disable-next-line no-unused-vars diff --git a/rair-node/bin/api/files/files.Service.js b/rair-node/bin/api/files/files.Service.js index 4e419afcb..298b82d45 100644 --- a/rair-node/bin/api/files/files.Service.js +++ b/rair-node/bin/api/files/files.Service.js @@ -180,6 +180,10 @@ module.exports = { const fileData = await File.findOne({ _id: id }); + if (!fileData) { + return new AppError('No file found'); + } + let deleteResponse; if (!fileData.storage) { log.error(`Can't tell where media ID ${id} is stored, will not unpin/delete from storage, just from DB`); @@ -202,18 +206,17 @@ module.exports = { await File.deleteOne({ _id: id }); await Unlock.deleteMany({ file: id }); log.info(`File with ID: ${id}, was removed from DB.`); - res.json({ + return res.json({ success: true, }); - return; } - res.json({ + return res.json({ success: false, message: deleteResponse.response, }); } catch (err) { - next(err); + return next(err); } }, updateMedia: async (req, res, next) => { diff --git a/rair-node/bin/api/users/users.Service.js b/rair-node/bin/api/users/users.Service.js index 9257c1e03..4da91314f 100644 --- a/rair-node/bin/api/users/users.Service.js +++ b/rair-node/bin/api/users/users.Service.js @@ -250,7 +250,6 @@ exports.updateUserByUserAddress = async (req, res, next) => { ...updatedUser, }; - res.json({ success: true, user: updatedUser }); return next(); } catch (e) { return next(e); @@ -258,19 +257,26 @@ exports.updateUserByUserAddress = async (req, res, next) => { }; exports.queryGithubData = async (req, res, next) => { - const { email, publicAddress, gitHandle } = req.session.userData; - if (gitHandle) { - return; + const { publicAddress, gitHandle } = req.session.userData; + const { gitId } = req.body; + if (gitHandle || !gitId || !Number.isInteger(Number(gitId))) { + return res.json({ success: true, user: req.session.userData }); } - const query = await (await fetch(`https://api.github.com/search/users?q=${email}`)).json(); - if (query.total_count === 1) { - await User.findOneAndUpdate({ publicAddress }, { - gitHandle: query.items[0].login, - gitBio: query.items[0].bio, + const query = await (await fetch(`https://api.github.com/user/${gitId}`)).json(); + if (query.login) { + const updatedUser = await User.findOneAndUpdate({ publicAddress }, { + gitHandle: query.login, + gitBio: query.bio, // available: query.items[0].hireable, - avatar: query.items[0].avatar_url, - }); - return; + avatar: query.avatar_url, + }, { new: true, projection: { nonce: 0 } }).lean(); + + req.session.userData = { + ...req.session.userData, + ...updatedUser, + }; + return res.json({ success: true, user: req.session.userData }); } - log.error("Couldn't fetch Github data, more than one account associated with the email"); + log.error("Couldn't fetch Github data"); + return res.json({ success: true, user: req.session.userData }); }; diff --git a/rair-node/bin/integrations/ethers/web3Signature.js b/rair-node/bin/integrations/ethers/web3Signature.js index bfc32ef38..67025ff51 100644 --- a/rair-node/bin/integrations/ethers/web3Signature.js +++ b/rair-node/bin/integrations/ethers/web3Signature.js @@ -168,11 +168,15 @@ module.exports = { } const recovered = await recoverUserFromSignature(MetaMessage, MetaSignature); const storedOwner = cache.get(`${userAddress}secret`); - if (recovered?.toLowerCase() === storedOwner?.toLowerCase()) { + if ( + recovered !== undefined && + storedOwner !== undefined && + recovered.toLowerCase() === storedOwner.toLowerCase() + ) { cache.del(userAddress.toLowerCase()); cache.del(MetaMessage); cache.del(`${userAddress}secret`); - req.metaAuth = { recovered: userAddress }; + req.metaAuth = { recovered: userAddress.toLowerCase() }; req.web3LoginMethod = 'web3auth'; } else { req.metaAuth = undefined; diff --git a/rair-node/bin/middleware/isAdmin.js b/rair-node/bin/middleware/isAdmin.js index f67e57d53..618066deb 100644 --- a/rair-node/bin/middleware/isAdmin.js +++ b/rair-node/bin/middleware/isAdmin.js @@ -5,7 +5,7 @@ module.exports = (req, res, next) => { const { adminRights, publicAddress } = req.user; if (!adminRights) { - return next(new AppError(`User ${publicAddress} don't have admin rights.`, 401)); + return next(new AppError(`User ${publicAddress} does not have admin rights.`, 401)); } return next(); diff --git a/rair-node/bin/schemas/userRoutes.js b/rair-node/bin/schemas/userRoutes.js index b8a62f7e3..1a7f1205c 100644 --- a/rair-node/bin/schemas/userRoutes.js +++ b/rair-node/bin/schemas/userRoutes.js @@ -19,5 +19,6 @@ module.exports = { firstName: Joi.string(), lastName: Joi.string(), blocked: Joi.boolean(), + gitId: Joi.number(), }), };