From 49445f5f65cda50d33283d2d9d1a86af85ddd342 Mon Sep 17 00:00:00 2001 From: Wolmin Date: Mon, 7 Oct 2024 15:13:59 +0200 Subject: [PATCH 1/5] Initial graph client impl --- deploy/tools/envs-validator/schema.ts | 3 --- docs/ENVS.md | 4 +--- lib/api/buildUniversalProfileUrl.ts | 11 --------- lib/api/graphClient.ts | 33 ++++++++++++++++++++++++++ lib/api/useUniversalProfileApiFetch.ts | 19 +++++++++------ types/api/universalProfile.ts | 7 ++++++ 6 files changed, 53 insertions(+), 24 deletions(-) delete mode 100644 lib/api/buildUniversalProfileUrl.ts create mode 100644 lib/api/graphClient.ts diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 77992c3b41..88efa8f4fc 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -810,9 +810,6 @@ const schema = yup NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: yup.string(), NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: yup.string(), NEXT_PUBLIC_UNIVERSAL_PROFILES_API_URL: yup.string(), - NEXT_PUBLIC_ALGOLIA_APP_ID: yup.string(), - NEXT_PUBLIC_ALGOLIA_API_KEY: yup.string(), - NEXT_PUBLIC_ALGOLIA_INDEX_NAME: yup.string(), NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: yup.string(), // Misc diff --git a/docs/ENVS.md b/docs/ENVS.md index 595ceb6366..856bf6e665 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -236,9 +236,7 @@ Settings for meta tags, OG tags and SEO | NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED | `boolean` | Set to `true` if SolidityScan reports are supported | - | - | `true` | v1.19.0+ | | NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS | `Array<'solidity-hardhat' \| 'solidity-foundry'>` | Pass an array of additional methods from which users can choose while verifying a smart contract. Both methods are available by default, pass `'none'` string to disable them all. | - | - | `['solidity-hardhat']` | v1.33.0+ | | NEXT_PUBLIC_UNIVERSAL_PROFILES_API_URL | `string` | LUKSO UNIVERSAL PROFILE API URL used for getting various universal profile data | - | - | `https://api.universalprofile.com` | - | -| NEXT_PUBLIC_ALGOLIA_APP_ID | `string` | Algolia App's ID | - | - | `ABCD1234` | - | -| NEXT_PUBLIC_ALGOLIA_API_KEY | `string` | Algolia API key used for authenticating requests | - | - | `abcd1234defg5678 `| - | -| NEXT_PUBLIC_ALGOLIA_INDEX_NAME | `string` | Index name from where data should be | - | - | `prod_mainnet_data`| - | +| NEXT_PUBLIC_GRAPH_URL | `string` | URL of LUKSO Graph Indexer | - | - | `https://envio.mainnet.lukso.dev/`| - | ##### Address views list | Id | Description | diff --git a/lib/api/buildUniversalProfileUrl.ts b/lib/api/buildUniversalProfileUrl.ts deleted file mode 100644 index 690da58e06..0000000000 --- a/lib/api/buildUniversalProfileUrl.ts +++ /dev/null @@ -1,11 +0,0 @@ -import algoliasearch from 'algoliasearch'; - -import { getEnvValue } from '../../configs/app/utils'; - -const algolia = { - appId: getEnvValue('NEXT_PUBLIC_ALGOLIA_APP_ID') || '', - apiKey: getEnvValue('NEXT_PUBLIC_ALGOLIA_API_KEY') || '', - index: getEnvValue('NEXT_PUBLIC_ALGOLIA_INDEX_NAME') || '', -}; - -export const algoliaIndex = algoliasearch(algolia.appId, algolia.apiKey).initIndex(algolia.index); diff --git a/lib/api/graphClient.ts b/lib/api/graphClient.ts new file mode 100644 index 0000000000..94f6627136 --- /dev/null +++ b/lib/api/graphClient.ts @@ -0,0 +1,33 @@ +import { getEnvValue } from "configs/app/utils"; +import { UniversalProfileGraphResponse } from "types/api/universalProfile"; + +type GraphClient = { + getProfile: (queryParamas: string) => Promise | null; +}; + +const createGraphClient = (): GraphClient => ({ + async getProfile(queryParams): Promise | null { + const url = getEnvValue('NEXT_PUBLIC_GRAPH_URL') || ''; + try { + const resp = await fetch(url, { + method: 'POST', + headers: { + + }, + body: JSON.stringify("asd"), + }); + const json = await resp.json(); + return json as UniversalProfileGraphResponse; + } catch (err) { + return null; + } + + + return { + result: queryParams, + }; + }, +}); + +export const graphClient: GraphClient = createGraphClient(); + diff --git a/lib/api/useUniversalProfileApiFetch.ts b/lib/api/useUniversalProfileApiFetch.ts index 9eeea0a0b3..62dedd8ea6 100644 --- a/lib/api/useUniversalProfileApiFetch.ts +++ b/lib/api/useUniversalProfileApiFetch.ts @@ -1,11 +1,11 @@ import React from 'react'; import type { SearchResultAddressOrContractOrUniversalProfile } from '../../types/api/search'; -import type { UniversalProfileProxyResponse } from '../../types/api/universalProfile'; +import type { UniversalProfileGraphResponse, UniversalProfileProxyResponse } from '../../types/api/universalProfile'; import type { Params as FetchParams } from 'lib/hooks/useFetch'; -import { algoliaIndex } from './buildUniversalProfileUrl'; +import { graphClient } from './graphClient' import { isUniversalProfileEnabled } from './isUniversalProfileEnabled'; import type { ResourceName, ResourcePathParams } from './resources'; @@ -22,13 +22,18 @@ export default function useUniversalProfileApiFetch() { return [] as Array; } try { - const { hits } = await algoliaIndex.search(queryParams); - return hits.map((hit) => { - const hitAsUp = hit as unknown as UniversalProfileProxyResponse; + const result: UniversalProfileGraphResponse | null = await graphClient.getProfile(queryParams) + if (result == null) { + return [] as Array; + } + + //const { hits } = await algoliaIndex.search(queryParams); + return result.map((hit: UniversalProfileGraphResponse) => { + const hitAsUp = hit as unknown as UniversalProfileGraphResponse; return { type: 'universal_profile', - name: hitAsUp.hasProfileName ? hitAsUp.LSP3Profile.name : null, - address: hit.objectID, + name: hitAsUp.name != '' ? hitAsUp.name : null, + address: hit.id, is_smart_contract_verified: false, }; }); diff --git a/types/api/universalProfile.ts b/types/api/universalProfile.ts index 9e5419567d..4fc9d64a75 100644 --- a/types/api/universalProfile.ts +++ b/types/api/universalProfile.ts @@ -25,3 +25,10 @@ export type UniversalProfileAlgoliaResponse = { }; }; } + +export type UniversalProfileGraphResponse = { + result: string; + id: string; + name: string; + fullName: string +} From 26236b8b5ca597252f350047e31bc0d38897ddf7 Mon Sep 17 00:00:00 2001 From: Wolmin Date: Tue, 8 Oct 2024 16:17:49 +0200 Subject: [PATCH 2/5] Fetching logic --- lib/api/graphClient.ts | 55 +++++++++++++++++++++-------------- lib/api/graphQueries.ts | 12 ++++++++ lib/api/graphTypes.ts | 20 +++++++++++++ types/api/universalProfile.ts | 12 ++++---- 4 files changed, 72 insertions(+), 27 deletions(-) create mode 100644 lib/api/graphQueries.ts create mode 100644 lib/api/graphTypes.ts diff --git a/lib/api/graphClient.ts b/lib/api/graphClient.ts index 94f6627136..dbde86ccae 100644 --- a/lib/api/graphClient.ts +++ b/lib/api/graphClient.ts @@ -1,32 +1,43 @@ import { getEnvValue } from "configs/app/utils"; import { UniversalProfileGraphResponse } from "types/api/universalProfile"; +import { SearchProfileQueryResponse } from "./graphTypes"; +import { universalProfile } from "nextjs/csp/policies"; + +const graphUrl = getEnvValue('NEXT_PUBLIC_GRAPH_URL') || ''; type GraphClient = { - getProfile: (queryParamas: string) => Promise | null; + getProfiles: (queryParams: string) => Promise | null>; }; +type fetchFunc = (queryParams: string) => Promise | null> + +const queryParamsToGraphQuery = (operationName: string, query: string): string => { + return JSON.stringify({ + operationName: operationName, + query: query, + }); +} + +const fetchQuery = (operationName: string): fetchFunc => { + return async (queryParams: string): Promise | null> => { + const query = queryParamsToGraphQuery(operationName, queryParams); + + try { + const resp = await fetch(graphUrl, { + method: 'POST', + headers: {}, + body: query, + }); + const json = await resp.json(); + return json as UniversalProfileGraphResponse; + } catch (err) { + return null; + }; + } +} + const createGraphClient = (): GraphClient => ({ - async getProfile(queryParams): Promise | null { - const url = getEnvValue('NEXT_PUBLIC_GRAPH_URL') || ''; - try { - const resp = await fetch(url, { - method: 'POST', - headers: { - - }, - body: JSON.stringify("asd"), - }); - const json = await resp.json(); - return json as UniversalProfileGraphResponse; - } catch (err) { - return null; - } - - - return { - result: queryParams, - }; - }, + getProfiles: fetchQuery('search_profiles'), }); export const graphClient: GraphClient = createGraphClient(); diff --git a/lib/api/graphQueries.ts b/lib/api/graphQueries.ts new file mode 100644 index 0000000000..1a2b1e3470 --- /dev/null +++ b/lib/api/graphQueries.ts @@ -0,0 +1,12 @@ +export const searchProfileQuery = (query: string): string => ` +search_profiles(args: { search: "${ query }" }) { + profileImages(order_by: { width: asc }) { + src, + width, + }, + id, + name, + fullName, + } +` + diff --git a/lib/api/graphTypes.ts b/lib/api/graphTypes.ts new file mode 100644 index 0000000000..8a69a1faf7 --- /dev/null +++ b/lib/api/graphTypes.ts @@ -0,0 +1,20 @@ +export type ProfileImage = { + src: string; + width: number; +} + +export type SearchProfileQueryResponse = { + profileImages: Array; + id: string; + name: string; + fullName: string; +}; + +export type GraphQuery = { + operationName: string; + query: string; +}; + +export enum Query { + search_profile, +} diff --git a/types/api/universalProfile.ts b/types/api/universalProfile.ts index 4fc9d64a75..3a9a3f51a3 100644 --- a/types/api/universalProfile.ts +++ b/types/api/universalProfile.ts @@ -1,3 +1,5 @@ +import { Query, queryResponse } from "lib/api/graphTypes"; + export type UniversalProfileProxyResponse = { type: string; hasProfileName: boolean; @@ -26,9 +28,9 @@ export type UniversalProfileAlgoliaResponse = { }; } -export type UniversalProfileGraphResponse = { - result: string; - id: string; - name: string; - fullName: string +export type UniversalProfileGraphResponse = { + data: { + [key in Query]: T; + }; } + From 47d7bb7b6b3ecbe4da652f6f23f1affc9f705f7f Mon Sep 17 00:00:00 2001 From: Wolmin Date: Thu, 10 Oct 2024 10:55:53 +0200 Subject: [PATCH 3/5] Generalize types --- lib/api/graphClient.ts | 37 +++++++++++++------------ lib/api/graphQueries.ts | 19 +++++++++++-- lib/api/graphTypes.ts | 4 +-- lib/api/useUniversalProfileApiFetch.ts | 15 +++++----- nextjs/csp/policies/universalprofile.ts | 1 + types/api/universalProfile.ts | 7 ++--- 6 files changed, 49 insertions(+), 34 deletions(-) diff --git a/lib/api/graphClient.ts b/lib/api/graphClient.ts index dbde86ccae..93d1b7b104 100644 --- a/lib/api/graphClient.ts +++ b/lib/api/graphClient.ts @@ -1,25 +1,29 @@ -import { getEnvValue } from "configs/app/utils"; -import { UniversalProfileGraphResponse } from "types/api/universalProfile"; -import { SearchProfileQueryResponse } from "./graphTypes"; -import { universalProfile } from "nextjs/csp/policies"; +import type { GraphResponse } from 'types/api/universalProfile'; + +import { getEnvValue } from 'configs/app/utils'; + +import { constructQuery } from './graphQueries'; +import type { QueryOperation, SearchProfileQueryResponse } from './graphTypes'; const graphUrl = getEnvValue('NEXT_PUBLIC_GRAPH_URL') || ''; +type FetchFunc = (queryParams?: string) => Promise | null> + type GraphClient = { - getProfiles: (queryParams: string) => Promise | null>; + getProfiles: FetchFunc; }; -type fetchFunc = (queryParams: string) => Promise | null> +const queryParamsToGraphQuery = (operationName: QueryOperation, queryParams?: string): string => { + const query = constructQuery(operationName, queryParams); -const queryParamsToGraphQuery = (operationName: string, query: string): string => { return JSON.stringify({ operationName: operationName, query: query, - }); -} + }); +}; -const fetchQuery = (operationName: string): fetchFunc => { - return async (queryParams: string): Promise | null> => { +const fetchQuery = (operationName: QueryOperation): FetchFunc => { + return async(queryParams?: string): Promise | null> => { const query = queryParamsToGraphQuery(operationName, queryParams); try { @@ -29,16 +33,15 @@ const fetchQuery = (operationName: string): fetchFunc => { body: query, }); const json = await resp.json(); - return json as UniversalProfileGraphResponse; + return json as GraphResponse; } catch (err) { return null; - }; - } -} + } + }; +}; const createGraphClient = (): GraphClient => ({ - getProfiles: fetchQuery('search_profiles'), + getProfiles: fetchQuery('search_profiles'), }); export const graphClient: GraphClient = createGraphClient(); - diff --git a/lib/api/graphQueries.ts b/lib/api/graphQueries.ts index 1a2b1e3470..90d36ce51f 100644 --- a/lib/api/graphQueries.ts +++ b/lib/api/graphQueries.ts @@ -1,5 +1,15 @@ -export const searchProfileQuery = (query: string): string => ` -search_profiles(args: { search: "${ query }" }) { +import type { QueryOperation } from './graphTypes'; + +type QueryConstructor = (queryParams?: string) => string; + +export const constructQuery = (operationType: QueryOperation, queryParams?: string) => { + const queryConstruct = queryConstructors[operationType]; + + return queryConstruct(queryParams); +}; + +export const searchProfileQuery = (queryParams?: string): string => `query search_profiles { + search_profiles(args: { search: "${ queryParams }" }) { profileImages(order_by: { width: asc }) { src, width, @@ -8,5 +18,8 @@ search_profiles(args: { search: "${ query }" }) { name, fullName, } -` +}`; +const queryConstructors: Record = { + search_profiles: searchProfileQuery, +}; diff --git a/lib/api/graphTypes.ts b/lib/api/graphTypes.ts index 8a69a1faf7..5c4134b48a 100644 --- a/lib/api/graphTypes.ts +++ b/lib/api/graphTypes.ts @@ -15,6 +15,4 @@ export type GraphQuery = { query: string; }; -export enum Query { - search_profile, -} +export type QueryOperation = 'search_profiles'; diff --git a/lib/api/useUniversalProfileApiFetch.ts b/lib/api/useUniversalProfileApiFetch.ts index 62dedd8ea6..29b5f32c9c 100644 --- a/lib/api/useUniversalProfileApiFetch.ts +++ b/lib/api/useUniversalProfileApiFetch.ts @@ -1,11 +1,11 @@ import React from 'react'; import type { SearchResultAddressOrContractOrUniversalProfile } from '../../types/api/search'; -import type { UniversalProfileGraphResponse, UniversalProfileProxyResponse } from '../../types/api/universalProfile'; import type { Params as FetchParams } from 'lib/hooks/useFetch'; -import { graphClient } from './graphClient' +import { graphClient } from './graphClient'; +import type { SearchProfileQueryResponse } from './graphTypes'; import { isUniversalProfileEnabled } from './isUniversalProfileEnabled'; import type { ResourceName, ResourcePathParams } from './resources'; @@ -22,17 +22,18 @@ export default function useUniversalProfileApiFetch() { return [] as Array; } try { - const result: UniversalProfileGraphResponse | null = await graphClient.getProfile(queryParams) + const result = await graphClient.getProfiles(queryParams); if (result == null) { return [] as Array; } - //const { hits } = await algoliaIndex.search(queryParams); - return result.map((hit: UniversalProfileGraphResponse) => { - const hitAsUp = hit as unknown as UniversalProfileGraphResponse; + const hits = result.data.search_profiles as Array; + + return hits.map((hit: SearchProfileQueryResponse) => { + const hitAsUp = hit as unknown as SearchProfileQueryResponse; return { type: 'universal_profile', - name: hitAsUp.name != '' ? hitAsUp.name : null, + name: hitAsUp.name !== '' ? hitAsUp.name : null, address: hit.id, is_smart_contract_verified: false, }; diff --git a/nextjs/csp/policies/universalprofile.ts b/nextjs/csp/policies/universalprofile.ts index 886889d327..14d516913f 100644 --- a/nextjs/csp/policies/universalprofile.ts +++ b/nextjs/csp/policies/universalprofile.ts @@ -6,6 +6,7 @@ export function universalProfile(): CspDev.DirectiveDescriptor { 'api.universalprofile.cloud', '*.algolianet.com', '*.algolia.net', + 'envio.mainnet.lukso.dev', ], }; } diff --git a/types/api/universalProfile.ts b/types/api/universalProfile.ts index 3a9a3f51a3..0f28b2f277 100644 --- a/types/api/universalProfile.ts +++ b/types/api/universalProfile.ts @@ -1,4 +1,4 @@ -import { Query, queryResponse } from "lib/api/graphTypes"; +import type { QueryOperation } from 'lib/api/graphTypes'; export type UniversalProfileProxyResponse = { type: string; @@ -28,9 +28,8 @@ export type UniversalProfileAlgoliaResponse = { }; } -export type UniversalProfileGraphResponse = { +export type GraphResponse = { data: { - [key in Query]: T; + [key in QueryOperation]: Array; }; } - From bab0cea2bd4f2e57d7a9c2aa0cf95b6940a55194 Mon Sep 17 00:00:00 2001 From: Wolmin Date: Thu, 10 Oct 2024 11:01:58 +0200 Subject: [PATCH 4/5] Validate graph env --- deploy/tools/envs-validator/schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 88efa8f4fc..ce47ad59ec 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -810,6 +810,7 @@ const schema = yup NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: yup.string(), NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: yup.string(), NEXT_PUBLIC_UNIVERSAL_PROFILES_API_URL: yup.string(), + NEXT_PUBLIC_GRAPH_URL: yup.string(), NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: yup.string(), // Misc From e7bb1fc4a0b729ad3d38e57affc01b5d367be781 Mon Sep 17 00:00:00 2001 From: Wolmin Date: Thu, 10 Oct 2024 12:51:14 +0200 Subject: [PATCH 5/5] Trim name --- lib/api/useUniversalProfileApiFetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/useUniversalProfileApiFetch.ts b/lib/api/useUniversalProfileApiFetch.ts index 29b5f32c9c..aaa71d7a12 100644 --- a/lib/api/useUniversalProfileApiFetch.ts +++ b/lib/api/useUniversalProfileApiFetch.ts @@ -33,7 +33,7 @@ export default function useUniversalProfileApiFetch() { const hitAsUp = hit as unknown as SearchProfileQueryResponse; return { type: 'universal_profile', - name: hitAsUp.name !== '' ? hitAsUp.name : null, + name: hitAsUp.name !== '' ? hitAsUp.name.trim() : null, address: hit.id, is_smart_contract_verified: false, };