commit 4fd126a77ecf41b070031eaf41e61022428978b9 parent 19152008955d59137a3b8703807e37921c70a849 Author: triesap <137732411+triesap@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:04:47 -0700 Edit `indexer` updating `listing` event model handlers with normalised `quantity`, `price`, and `image` vector tags parsing, and structured `lat`, `lng`, `geohash` location tags parsing; added content-addressed shard files for `listing` indexes on `author`, `npub` and `nip-05`. Edit `app` migrating profile views to shared library, adding environment variables export. Diffstat:
40 files changed, 588 insertions(+), 778 deletions(-)
diff --git a/app/.env.example b/app/.env.example @@ -1,5 +1,5 @@ VITE_PUBLIC_RADROOTS_MARKET_RELAY_URL= -PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL= +VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL= VITE_PUBLIC_IDB_NAME= VITE_PUBLIC_NDK_CACHE_NAME= VITE_PUBLIC_NDK_CLIENT_NAME= diff --git a/app/package.json b/app/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@radroots/apps-lib": "*", + "@radroots/apps-lib-market": "*", "@radroots/utils-nostr": "*", "@radroots/radroots-common-bindings": "*", "@nostr-dev-kit/ndk": "2.14.33" diff --git a/app/src/lib/types/lib.ts b/app/src/lib/types/lib.ts diff --git a/app/src/lib/types/page.ts b/app/src/lib/types/page.ts @@ -1,17 +0,0 @@ -import type { RadrootsListingEventData, RadrootsMetadataEventData } from "@radroots/radroots-common-bindings"; - -export type PageLoadProfileData = { - public_key: string; - npub?: string; - events: PageLoadProfileDataEvents; -}; - -export type PageLoadProfileDataEvents = - ( - { - metadata: RadrootsMetadataEventData; - } | { - metadata: RadrootsMetadataEventData; - listings: RadrootsListingEventData[]; - } - ); diff --git a/app/src/lib/types/views/profile.ts b/app/src/lib/types/views/profile.ts @@ -1,23 +0,0 @@ -import type { PageLoadProfileData } from "../page"; - -export type IProfileView = IProfileViewIndexed | IProfileViewUnknown; - -export type IProfileViewIndexed = { - index: PageLoadProfileData; -}; - -export type IProfileViewUnknown = { - unknown?: IProfileViewUnknownPublicKey | IProfileViewUnknownNpub | IProfileViewUnknownNip05; -}; - -export type IProfileViewUnknownPublicKey = { - public_key: string; -}; - -export type IProfileViewUnknownNpub = { - npub: string; -}; - -export type IProfileViewUnknownNip05 = { - nip05: string; -}; -\ No newline at end of file diff --git a/app/src/lib/utils/_env.ts b/app/src/lib/utils/_env.ts @@ -0,0 +1,22 @@ +const RADROOTS_MARKET_RELAY_URL = import.meta.env.VITE_PUBLIC_RADROOTS_MARKET_RELAY_URL; +if (!RADROOTS_MARKET_RELAY_URL || typeof RADROOTS_MARKET_RELAY_URL !== 'string') throw new Error('Missing env var: VITE_PUBLIC_RADROOTS_MARKET_RELAY_URL'); + +const RADROOTS_MARKET_RELAY_INDEXES_URL = import.meta.env.VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL; +if (!RADROOTS_MARKET_RELAY_INDEXES_URL || typeof RADROOTS_MARKET_RELAY_INDEXES_URL !== 'string') throw new Error('Missing env var: VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL'); + +const IDB_NAME = import.meta.env.VITE_PUBLIC_IDB_NAME; +if (!IDB_NAME || typeof IDB_NAME !== 'string') throw new Error('Missing env var: VITE_PUBLIC_IDB_NAME'); + +const NDK_CACHE_NAME = import.meta.env.VITE_PUBLIC_NDK_CACHE_NAME; +if (!NDK_CACHE_NAME || typeof NDK_CACHE_NAME !== 'string') throw new Error('Missing env var: VITE_PUBLIC_NDK_CACHE_NAME'); + +const NDK_CLIENT_NAME = import.meta.env.VITE_PUBLIC_NDK_CLIENT_NAME; +if (!NDK_CLIENT_NAME || typeof NDK_CLIENT_NAME !== 'string') throw new Error('Missing env var: VITE_PUBLIC_NDK_CLIENT_NAME'); + +export const _env = { + IDB_NAME, + NDK_CACHE_NAME, + NDK_CLIENT_NAME, + RADROOTS_MARKET_RELAY_INDEXES_URL, + RADROOTS_MARKET_RELAY_URL, +} as const; +\ No newline at end of file diff --git a/app/src/lib/utils/app/lib.ts b/app/src/lib/utils/app/lib.ts @@ -1 +0,0 @@ -export const head_title_suffix = `• Radroots Market` -\ No newline at end of file diff --git a/app/src/lib/utils/app/storage.ts b/app/src/lib/utils/app/storage.ts @@ -1,21 +0,0 @@ -import { IdbLib, type ThemeMode } from "@radroots/apps-lib"; - -export type GlobalConfig = { - theme_mode: ThemeMode; - theme_key: string; - locale: string; - global_relays: string[]; - npub: string; -}; - -export type GlobalConfigKeys = keyof GlobalConfig; - - -export type PageSession = { - draftId: string; - lastScroll: number; -}; - -export type PageSessionKeys = keyof PageSession; - -export const idb = new IdbLib<GlobalConfigKeys, GlobalConfig, PageSessionKeys, PageSession>(); -\ No newline at end of file diff --git a/app/src/lib/utils/app/theme.ts b/app/src/lib/utils/app/theme.ts @@ -1,35 +0,0 @@ -import { browser } from "$app/environment"; -import { get_system_theme, theme_key, theme_mode, theme_set, theme_toggle } from "@radroots/apps-lib"; -import { idb } from "./storage"; - -export const toggle_theme = async (): Promise<void> => { - await theme_toggle(async (mode) => { - await idb.save_global("theme_mode", mode); - }); -}; - -export const init_theme = async (): Promise<void> => { - let mode = await idb.read_global("theme_mode"); - let key = await idb.read_global("theme_key"); - - if (!mode) { - mode = get_system_theme(); - await idb.save_global("theme_mode", mode); - } - - if (!key) { - key = `deault`; - await idb.save_global("theme_key", key); - } - - theme_mode.set(mode); - theme_key.set(key); - - theme_set(key, mode); -}; - -theme_key.subscribe((key) => { - theme_mode.subscribe((mode) => { - if (browser) theme_set(key, mode); - }); -}); -\ No newline at end of file diff --git a/app/src/lib/views/profile/profile-indexed.svelte b/app/src/lib/views/profile/profile-indexed.svelte @@ -1,95 +0,0 @@ -<script lang="ts"> - import type { IProfileViewIndexed } from "$lib/types/views/profile"; - import { head_title_suffix } from "$lib/utils/app/lib"; - import { NDKKind, type NDKUserProfile } from "@nostr-dev-kit/ndk"; - import { ndk } from "@radroots/apps-lib"; - import { on_ndk_event, type NdkEventPayload } from "@radroots/utils-nostr"; - - let { basis }: { basis: IProfileViewIndexed } = $props(); - - let ndk_profile: NDKUserProfile | null = $state(null); - let ndk_events: NdkEventPayload[] = $state([]); - - $ndk.subscribe( - { - kinds: [NDKKind.Metadata, NDKKind.Classified], - authors: [basis.index.public_key], - }, - undefined, - { - onEvent: async (event) => { - const ev = on_ndk_event(event); - if (ev) ndk_events.push(ev); - }, - }, - ); - - const data_user = $derived( - $ndk.getUser({ pubkey: basis.index.public_key }), - ); - - $effect(() => { - data_user.fetchProfile().then((profile) => { - if (profile) ndk_profile = profile; - }); - }); - - const head_title = $derived( - `${ - basis.index.events.metadata.metadata.display_name || - basis.index.events.metadata.metadata.name - } (@${basis.index.events.metadata.metadata.name}) ${head_title_suffix}`, - ); -</script> - -<svelte:head> - <title> - {head_title} - </title> - <meta name="description" content={``} /> - <meta property="og:title" content={head_title} /> - <meta property="og:description" content={``} /> -</svelte:head> - -<div class={`flex flex-col w-full gap-12 justify-start items-start`}> - <div class={`flex flex-col w-full gap-4 justify-start items-start`}> - {#each Object.entries(basis.index.events.metadata) as [k, v]} - <div class={`flex flex-col w-full gap-2 justify-start items-start`}> - <p class={`font-sans font-[400] text-base text-ly0-gl`}> - {k} - </p> - <p - class={`font-sans font-[400] text-base text-ly0-gl break-all`} - > - {JSON.stringify(v)} - </p> - </div> - {/each} - </div> - {#if `listings` in basis.index.events} - <div class={`flex flex-col w-full gap-4 justify-start items-start`}> - {#each basis.index.events.listings as listing_event} - <div - class={`flex flex-col w-full gap-2 justify-start items-start break-all`} - > - {JSON.stringify(listing_event)} - </div> - {/each} - </div> - {/if} - - <div class={`flex flex-col w-full gap-4 justify-start items-start`}> - {#each ndk_events as ndk_event} - <div - class={`flex flex-col w-full gap-2 justify-start items-start break-all`} - > - {ndk_event.kind} - {#if `metadata` in ndk_event} - {JSON.stringify(ndk_event.metadata)} - {:else if `listing` in ndk_event} - {JSON.stringify(ndk_event.listing)} - {/if} - </div> - {/each} - </div> -</div> diff --git a/app/src/lib/views/profile/profile-unknown-nip05.svelte b/app/src/lib/views/profile/profile-unknown-nip05.svelte @@ -1,20 +0,0 @@ -<script lang="ts"> - import type { IProfileViewUnknownNip05 } from "$lib/types/views/profile"; - import ProfileUnknownPublicKey from "./profile-unknown-public-key.svelte"; - - let { - basis, - }: { - basis: IProfileViewUnknownNip05; - } = $props(); - - let public_key: string | undefined = $state(undefined); -</script> - -{#if public_key} - <ProfileUnknownPublicKey basis={{ public_key }} /> -{:else} - <p class={`font-sans font-[400] text-base text-ly0-gl`}> - {`could not find a public key for ${basis.nip05}`} - </p> -{/if} diff --git a/app/src/lib/views/profile/profile-unknown-npub.svelte b/app/src/lib/views/profile/profile-unknown-npub.svelte @@ -1,28 +0,0 @@ -<script lang="ts"> - import type { IProfileViewUnknownNpub } from "$lib/types/views/profile"; - import { lib_nostr_npub_decode } from "@radroots/utils-nostr"; - import { error } from "@sveltejs/kit"; - import { onMount } from "svelte"; - import ProfileUnknownPublicKey from "./profile-unknown-public-key.svelte"; - - let { - basis, - }: { - basis: IProfileViewUnknownNpub; - } = $props(); - - let public_key: string | undefined = $state(undefined); - - onMount(async () => { - public_key = lib_nostr_npub_decode(basis.npub); - if (!public_key) error(404, `invalid:public_key:${public_key}`); - }); -</script> - -{#if public_key} - <ProfileUnknownPublicKey basis={{ public_key }} /> -{:else} - <p class={`font-sans font-[400] text-base text-ly0-gl`}> - {`not a valid npub ${basis.npub}`} - </p> -{/if} diff --git a/app/src/lib/views/profile/profile-unknown-public-key.svelte b/app/src/lib/views/profile/profile-unknown-public-key.svelte @@ -1,18 +0,0 @@ -<script lang="ts"> - import type { IProfileViewUnknownPublicKey } from "$lib/types/views/profile"; - - let { - basis, - }: { - basis: IProfileViewUnknownPublicKey; - } = $props(); -</script> - -<div class={`flex flex-col w-full px-4 pt-8 gap-4 justify-start items-start`}> - <p class={`font-sans font-[400] text-base text-ly0-gl`}> - {`profile`} - </p> - <p class={`font-sans font-[400] text-base text-ly0-gl break-all`}> - {`public_key `}{basis.public_key} - </p> -</div> diff --git a/app/src/lib/views/profile/profile-unknown.svelte b/app/src/lib/views/profile/profile-unknown.svelte @@ -1,20 +0,0 @@ -<script lang="ts"> - import type { IProfileViewUnknown } from "$lib/types/views/profile"; - import ProfileUnknownNip05 from "./profile-unknown-nip05.svelte"; - import ProfileUnknownNpub from "./profile-unknown-npub.svelte"; - import ProfileUnknownPublicKey from "./profile-unknown-public-key.svelte"; - - let { basis }: { basis: IProfileViewUnknown } = $props(); -</script> - -{#if basis.unknown && `public_key` in basis.unknown} - <ProfileUnknownPublicKey basis={basis.unknown} /> -{:else if basis.unknown && `npub` in basis.unknown} - <ProfileUnknownNpub basis={basis.unknown} /> -{:else if basis.unknown && `nip05` in basis.unknown} - <ProfileUnknownNip05 basis={basis.unknown} /> -{:else} - <p class={`font-sans font-[400] text-base text-ly0-gl`}> - {`profile not found`} - </p> -{/if} diff --git a/app/src/lib/views/profile/profile.svelte b/app/src/lib/views/profile/profile.svelte @@ -1,17 +0,0 @@ -<script lang="ts"> - import type { IProfileView } from "$lib/types/views/profile"; - import ProfileIndexed from "./profile-indexed.svelte"; - import ProfileUnknown from "./profile-unknown.svelte"; - - let { - basis, - }: { - basis: IProfileView; - } = $props(); -</script> - -{#if `index` in basis} - <ProfileIndexed {basis} /> -{:else if `unknown` in basis && basis.unknown} - <ProfileUnknown {basis} /> -{/if} diff --git a/app/src/routes/(market)/(listing)/[0=country]/+page.ts b/app/src/routes/(market)/(listing)/[0=country]/+page.ts @@ -1,5 +1,5 @@ -import { PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL } from "$env/static/public"; -import type { RadrootsIndexManifest, RadrootsListingEventData } from "@radroots/radroots-common-bindings"; +import { VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL } from "$env/static/public"; +import type { RadrootsIndexManifest, RadrootsListingEventMetadata } from "@radroots/radroots-common-bindings"; import { error } from "@sveltejs/kit"; import type { EntryGenerator, PageLoad } from "./$types"; @@ -9,7 +9,7 @@ export const entries: EntryGenerator = async () => { ]: [ string[] ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/indexes.json`).then(r => r.json()) + fetch(`${VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/indexes.json`).then(r => r.json()) ]); return events_0_country_indexes.map(i => ({ 0: i })) }; @@ -17,7 +17,7 @@ export const entries: EntryGenerator = async () => { type PageLoadData = { country: string; manifest: RadrootsIndexManifest; - events: RadrootsListingEventData[]; + events: RadrootsListingEventMetadata[]; }; export const load: PageLoad<PageLoadData> = async ({ fetch, params }) => { @@ -26,17 +26,17 @@ export const load: PageLoad<PageLoadData> = async ({ fetch, params }) => { const [ res_country_manifest, ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/${country}/manifest.json`) + fetch(`${VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/${country}/manifest.json`) ]); if (!res_country_manifest.ok) error(404, { message: `country:${country}` }); const manifest: RadrootsIndexManifest = await res_country_manifest.json(); - let events: RadrootsListingEventData[] = []; + let events: RadrootsListingEventMetadata[] = []; if (manifest.shards.length > 0) { const shard = manifest.shards[0]; - const res_country_shard = await fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/${country}/${shard.file}?v=${shard.sha256}`); + const res_country_shard = await fetch(`${VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/${country}/${shard.file}?v=${shard.sha256}`); if (!res_country_shard.ok) error(500, { message: `load:${country}:${shard.file}` }); events = await res_country_shard.json(); } diff --git a/app/src/routes/(market)/(profile)/[0=nip05]/+page.svelte b/app/src/routes/(market)/(profile)/[0=nip05]/+page.svelte @@ -1,8 +1,8 @@ <script lang="ts"> - import Profile from "$lib/views/profile/profile.svelte"; + import { Profile } from "@radroots/apps-lib-market"; import type { PageProps } from "./$types"; let { data }: PageProps = $props(); </script> -<Profile basis={{ index: data }} /> +<Profile basis={{ indexed: data }} /> diff --git a/app/src/routes/(market)/(profile)/[0=nip05]/+page.ts b/app/src/routes/(market)/(profile)/[0=nip05]/+page.ts @@ -1,17 +1,19 @@ -import { PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL } from "$env/static/public"; -import type { PageLoadProfileData } from "$lib/types/page"; -import type { RadrootsIndexManifest, RadrootsListingEventData, RadrootsMetadataEventData } from "@radroots/radroots-common-bindings"; +import { _env } from "$lib/utils/_env"; +import type { PageLoadProfileData } from "@radroots/apps-lib-market"; +import type { RadrootsIndexManifest, RadrootsListingEventMetadata, RadrootsProfileEventMetadata } from "@radroots/radroots-common-bindings"; import { lib_nostr_npub_encode } from "@radroots/utils-nostr"; import { error } from "@sveltejs/kit"; import type { EntryGenerator, PageLoad } from "./$types"; +const { RADROOTS_MARKET_RELAY_INDEXES_URL: indexes_url } = _env; + export const entries: EntryGenerator = async () => { const [ events_0_author_indexes, ]: [ string[] ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/0/nip05/indexes.json`).then(r => r.json()) + fetch(`${indexes_url}/events/0/nip05/indexes.json`).then(r => r.json()) ]); return events_0_author_indexes.map(i => ({ 0: i })) }; @@ -25,35 +27,36 @@ export const load: PageLoad<PageLoadData> = async ({ fetch, params }) => { res_nip05_metadata, res_nip05_listings_manifest, ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/0/nip05/${nip05}/metadata.json`), - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/nip05/${nip05}/manifest.json`) + fetch(`${indexes_url}/events/0/nip05/${nip05}/metadata.json`), + fetch(`${indexes_url}/events/30402/nip05/${nip05}/manifest.json`) ]); if (!res_nip05_metadata.ok) error(404, { message: `nip05:${nip05}`, }); if (!res_nip05_listings_manifest.ok) error(404, { message: `nip05:listing:manifest:${nip05}`, }); - const metadata_event: RadrootsMetadataEventData = await res_nip05_metadata.json(); + const profile_event: RadrootsProfileEventMetadata = await res_nip05_metadata.json(); const listings_manifest: RadrootsIndexManifest = await res_nip05_listings_manifest.json(); - let listings_events: RadrootsListingEventData[] = []; + let listings_events: RadrootsListingEventMetadata[] = []; if (listings_manifest.shards.length > 0) { const shard = listings_manifest.shards[0]; - const res_country_shard = await fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/nip05/${nip05}/${shard.file}?v=${shard.sha256}`); + const res_country_shard = await fetch(`${indexes_url}/events/30402/nip05/${nip05}/${shard.file}?v=${shard.sha256}`); if (!res_country_shard.ok) error(500, { message: `nip05:listing:shard:${nip05}:${shard.file}` }); listings_events = await res_country_shard.json(); } - const public_key = metadata_event.public_key; + const public_key = profile_event.author; const npub = lib_nostr_npub_encode(public_key); - const data: PageLoadData = { + const data: PageLoadProfileData = { public_key, npub, events: { - metadata: metadata_event, + profile: profile_event, listings: listings_events } } + return data; } diff --git a/app/src/routes/(market)/(profile)/profile/+error.svelte b/app/src/routes/(market)/(profile)/profile/+error.svelte @@ -1,11 +1,11 @@ <script lang="ts"> import { page } from "$app/state"; - import type { - IProfileViewUnknownNip05, - IProfileViewUnknownNpub, - IProfileViewUnknownPublicKey, - } from "$lib/types/views/profile"; - import Profile from "$lib/views/profile/profile.svelte"; + import { + Profile, + type IProfileViewUnknownNip05, + type IProfileViewUnknownNpub, + type IProfileViewUnknownPublicKey, + } from "@radroots/apps-lib-market"; $effect(() => { console.log(`page.error`, page.error); diff --git a/app/src/routes/(market)/(profile)/profile/[0=npub]/+page.svelte b/app/src/routes/(market)/(profile)/profile/[0=npub]/+page.svelte @@ -1,8 +1,8 @@ <script lang="ts"> - import Profile from "$lib/views/profile/profile.svelte"; + import { Profile } from "@radroots/apps-lib-market"; import type { PageProps } from "./$types"; let { data }: PageProps = $props(); </script> -<Profile basis={{ index: data }} /> +<Profile basis={{ indexed: data }} /> diff --git a/app/src/routes/(market)/(profile)/profile/[0=npub]/+page.ts b/app/src/routes/(market)/(profile)/profile/[0=npub]/+page.ts @@ -1,16 +1,18 @@ -import { PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL } from "$env/static/public"; -import type { PageLoadProfileData } from "$lib/types/page"; -import type { RadrootsMetadataEventData } from "@radroots/radroots-common-bindings"; +import { _env } from "$lib/utils/_env"; +import type { PageLoadProfileData } from "@radroots/apps-lib-market"; +import type { RadrootsProfileEventMetadata } from "@radroots/radroots-common-bindings"; import { error } from "@sveltejs/kit"; import type { EntryGenerator, PageLoad } from "./$types"; +const { RADROOTS_MARKET_RELAY_INDEXES_URL: indexes_url } = _env; + export const entries: EntryGenerator = async () => { const [ events_0_author_indexes, ]: [ string[] ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/0/npub/indexes.json`).then(r => r.json()) + fetch(`${indexes_url}/events/0/npub/indexes.json`).then(r => r.json()) ]); return events_0_author_indexes.map(i => ({ 0: i })) }; @@ -23,20 +25,20 @@ export const load: PageLoad<PageLoadData> = async ({ fetch, params }) => { const [ res_npub_metadata, ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/0/npub/${npub}/metadata.json`) + fetch(`${indexes_url}/events/0/npub/${npub}/metadata.json`) ]); if (!res_npub_metadata.ok) error(404, { message: `npub:${npub}` }); - const metadata_event: RadrootsMetadataEventData = await res_npub_metadata.json(); + const profile_event: RadrootsProfileEventMetadata = await res_npub_metadata.json(); - const public_key = metadata_event.public_key; + const public_key = profile_event.author; const data: PageLoadData = { public_key, npub, events: { - metadata: metadata_event + profile: profile_event } } return data; diff --git a/app/src/routes/(market)/(profile)/profile/[0=public_key]/+page.svelte b/app/src/routes/(market)/(profile)/profile/[0=public_key]/+page.svelte @@ -1,8 +1,8 @@ <script lang="ts"> - import Profile from "$lib/views/profile/profile.svelte"; + import { Profile } from "@radroots/apps-lib-market"; import type { PageProps } from "./$types"; let { data }: PageProps = $props(); </script> -<Profile basis={{ index: data }} /> +<Profile basis={{ indexed: data }} /> diff --git a/app/src/routes/(market)/(profile)/profile/[0=public_key]/+page.ts b/app/src/routes/(market)/(profile)/profile/[0=public_key]/+page.ts @@ -1,17 +1,19 @@ -import { PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL } from "$env/static/public"; -import type { PageLoadProfileData } from "$lib/types/page"; -import type { RadrootsMetadataEventData } from "@radroots/radroots-common-bindings"; +import { _env } from "$lib/utils/_env"; +import type { PageLoadProfileData } from "@radroots/apps-lib-market"; +import type { RadrootsProfileEventMetadata } from "@radroots/radroots-common-bindings"; import { lib_nostr_npub_encode } from "@radroots/utils-nostr"; import { error } from "@sveltejs/kit"; import type { EntryGenerator, PageLoad } from "./$types"; +const { RADROOTS_MARKET_RELAY_INDEXES_URL: indexes_url } = _env; + export const entries: EntryGenerator = async () => { const [ events_0_author_indexes, ]: [ string[] ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/0/author/indexes.json`).then(r => r.json()) + fetch(`${indexes_url}/events/0/author/indexes.json`).then(r => r.json()) ]); return events_0_author_indexes.map(i => ({ 0: i })) }; @@ -24,12 +26,12 @@ export const load: PageLoad<PageLoadData> = async ({ fetch, params }) => { const [ res_author_metadata, ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/0/author/${public_key}/metadata.json`), + fetch(`${indexes_url}/events/0/author/${public_key}/metadata.json`), ]); if (!res_author_metadata.ok) error(404, { message: `public_key:${public_key}` }); - const metadata_event: RadrootsMetadataEventData = await res_author_metadata.json(); + const profile_event: RadrootsProfileEventMetadata = await res_author_metadata.json(); const npub = lib_nostr_npub_encode(public_key); @@ -37,7 +39,7 @@ export const load: PageLoad<PageLoadData> = async ({ fetch, params }) => { public_key, npub, events: { - metadata: metadata_event + profile: profile_event } } diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte @@ -1,7 +1,6 @@ <script lang="ts"> - import { idb } from "$lib/utils/app/storage"; - import { init_theme } from "$lib/utils/app/theme"; import { ndk, ndk_global } from "@radroots/apps-lib"; + import { idb, init_theme } from "@radroots/apps-lib-market"; import { onMount, type Snippet } from "svelte"; import "../app.css"; diff --git a/app/src/routes/+page.ts b/app/src/routes/+page.ts @@ -1,4 +1,4 @@ -import { PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL } from "$env/static/public"; +import { VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL } from "$env/static/public"; import { error } from "@sveltejs/kit"; import type { PageLoad } from "./$types"; @@ -13,8 +13,8 @@ export const load: PageLoad<PageLoadData> = async ({ fetch, params }) => { res_nip05_indexes, res_country_indexes, ] = await Promise.all([ - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/nip05/indexes.json`), - fetch(`${PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/indexes.json`), + fetch(`${VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/nip05/indexes.json`), + fetch(`${VITE_PUBLIC_RADROOTS_MARKET_RELAY_INDEXES_URL}/events/30402/country/indexes.json`), ]); if (!res_nip05_indexes.ok) error(404, { message: `nip05:indexes` }); diff --git a/crates/indexer-utils/src/nostr.rs b/crates/indexer-utils/src/nostr.rs @@ -1,14 +1,33 @@ +use std::collections::HashMap; + use nostr::key::{Error as PublicKeyError, PublicKey}; use nostr::prelude::ToBech32; use thiserror::Error; #[derive(Debug, Error)] -pub enum NostrError { +pub enum NostrUtilsError { #[error("Invalid hex for public key: {0}")] InvalidPublicKey(#[from] PublicKeyError), + + #[error("Tag parsing error: {0}")] + TagParseError(String), } -pub fn public_key_to_npub(public_key_hex: &str) -> Result<String, NostrError> { +pub fn public_key_to_npub(public_key_hex: &str) -> Result<String, NostrUtilsError> { let pubkey = PublicKey::from_hex(public_key_hex)?; Ok(pubkey.to_bech32().expect("to_bech32 is infallible")) } + +pub fn get_tag_value<'a>( + tags_map: &'a HashMap<String, Vec<String>>, + key: &str, + idx: usize, +) -> Result<Option<String>, NostrUtilsError> { + match tags_map.get(key) { + Some(values) => Ok(values.get(idx).cloned()), + None => Err(NostrUtilsError::TagParseError(format!( + "Tag '{}' not found", + key + ))), + } +} diff --git a/crates/indexer/src/audit.rs b/crates/indexer/src/audit.rs @@ -10,7 +10,8 @@ use tracing::info; use crate::domain::resolvers::profile::ProfileResolver; use crate::relay::event::RelayIndexerEvent; -use radroots_common::models::events::{RadrootsListingEvent, RadrootsMetadataEvent}; +use radroots_common::events::listing::models::RadrootsListingEventIndex; +use radroots_common::events::profile::models::RadrootsProfileEventIndex; #[derive(Clone, Debug)] pub struct AuditFilter { @@ -272,10 +273,10 @@ pub fn log_indexer_event(idx: &RelayIndexerEvent) { } #[inline] -pub fn log_metadata_event(evt: &RadrootsMetadataEvent) { +pub fn log_profile_event(evt: &RadrootsProfileEventIndex) { let (nip05_full_opt, nip05_local_opt) = evt - .data .metadata + .profile .nip05 .as_ref() .map(|n| { @@ -321,7 +322,7 @@ pub fn log_metadata_event(evt: &RadrootsMetadataEvent) { } #[inline] -pub fn log_listing_event(evt: &RadrootsListingEvent) { +pub fn log_listing_event(evt: &RadrootsListingEventIndex) { let need_npub = STATE .read() .ok() diff --git a/crates/indexer/src/domain/events/listing.rs b/crates/indexer/src/domain/events/listing.rs @@ -1,49 +1,49 @@ -use crate::domain::events::RequiredField; use crate::relay::event::RelayIndexerEvent; -use crate::{opt_default, opt_required}; use anyhow::Result; -use radroots_common::models::events::{ - RadrootsListingEvent, RadrootsListingEventData, RadrootsNostrEvent, +use indexer_utils::nostr::NostrUtilsError; +use radroots_common::events::listing::models::{ + RadrootsListing, RadrootsListingDiscount, RadrootsListingEventIndex, + RadrootsListingEventMetadata, RadrootsListingImage, RadrootsListingLocation, + RadrootsListingPrice, RadrootsListingProduct, RadrootsListingQuantity, }; +use radroots_common::events::RadrootsNostrEvent; use std::collections::HashMap; use thiserror::Error; #[derive(Debug, Error)] -pub enum RadrootsListingEventError { +pub enum RadrootsListingEventIndexError { #[error("Missing or invalid tag structure")] TagParseError, #[error("Missing required field: {0}")] MissingField(String), + + #[error("Nostr Error: {0}")] + NostrUtilsError(#[from] NostrUtilsError), } -pub fn create_radroots_listing_event_data( +pub fn create_radroots_listing_event_metadata( id: String, author: String, + published_at: u32, tags: Vec<Vec<String>>, -) -> Result<RadrootsListingEventData, RadrootsListingEventError> { +) -> Result<RadrootsListingEventMetadata, RadrootsListingEventIndexError> { let mut tags_map: HashMap<String, Vec<String>> = HashMap::new(); - let mut images = Vec::new(); let mut location_lat: Option<String> = None; let mut location_lng: Option<String> = None; for tag in &tags { if let Some(key) = tag.get(0).map(String::as_str) { match key { - "image" => { - if let Some(img) = tag.get(1) { - images.push(img.clone()); - } - } "l" => { if let Some(value) = tag.get(1) { if let Some(hint) = tag.get(2) { match hint.as_str() { "dd.lat" if location_lat.is_none() => { - location_lat = Some(value.clone()) + location_lat = Some(value.clone()); } "dd.lon" if location_lng.is_none() => { - location_lng = Some(value.clone()) + location_lng = Some(value.clone()); } _ => {} } @@ -70,32 +70,19 @@ pub fn create_radroots_listing_event_data( tags_map.get(key).and_then(|v| v.get(idx)).cloned() }; - let published_at_str = opt_required!(get("published_at", 0)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - let published_at = published_at_str - .parse::<u32>() - .map_err(|_| RadrootsListingEventError::MissingField("published_at".into()))?; - let d_tag = - opt_required!(get("d", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let title = - opt_required!(get("title", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let summary = - opt_required!(get("summary", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - - let location_address = opt_required!(get("location", 0)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - let location_city = opt_required!(get("location", 1)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - let location_region = opt_required!(get("location", 2)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - let location_country = opt_required!(get("location", 3)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - - let location_lat = - opt_required!(location_lat).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let location_lng = - opt_required!(location_lng).map_err(|e| RadrootsListingEventError::MissingField(e))?; + get("d", 0).ok_or_else(|| RadrootsListingEventIndexError::MissingField("d".into()))?; + let title = get("title", 0) + .ok_or_else(|| RadrootsListingEventIndexError::MissingField("title".into()))?; + + let location_address = get("location", 0) + .ok_or_else(|| RadrootsListingEventIndexError::MissingField("location".into()))?; + let location_city = get("location", 1) + .ok_or_else(|| RadrootsListingEventIndexError::MissingField("location_city".into()))?; + let location_region = get("location", 2) + .ok_or_else(|| RadrootsListingEventIndexError::MissingField("location_region".into()))?; + let location_country = get("location", 3) + .ok_or_else(|| RadrootsListingEventIndexError::MissingField("location_country".into()))?; let location_geohash = tags .iter() @@ -107,75 +94,128 @@ pub fn create_radroots_listing_event_data( } }) .max_by_key(|g| g.len()) - .ok_or_else(|| RadrootsListingEventError::MissingField("location_geohash".into()))?; - - let product_kind = - opt_required!(get("key", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_category = opt_required!(get("category", 0)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_process = - opt_required!(get("process", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_lot = - opt_required!(get("lot", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_profile = opt_default!(get("profile", 0)); - let product_year = - opt_required!(get("year", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_quantity_amt = opt_required!(get("quantity", 0)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_quantity_unit = opt_required!(get("quantity", 1)) - .map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_price_amt = - opt_required!(get("price", 0)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_price_cur = - opt_required!(get("price", 1)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_price_qty_amt = - opt_required!(get("price", 2)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - let product_price_qty_unit = - opt_required!(get("price", 3)).map_err(|e| RadrootsListingEventError::MissingField(e))?; - - Ok(RadrootsListingEventData { + .ok_or_else(|| RadrootsListingEventIndexError::MissingField("location_geohash".into()))?; + + let quantities = tags + .iter() + .filter_map(|tag| { + if tag.get(0).map(String::as_str) == Some("quantity") { + Some(RadrootsListingQuantity { + amt: tag.get(1)?.clone(), + unit: tag.get(2)?.clone(), + label: tag.get(3).cloned(), + }) + } else { + None + } + }) + .collect::<Vec<RadrootsListingQuantity>>(); + + let prices = tags + .iter() + .filter_map(|tag| { + if tag.get(0).map(String::as_str) == Some("price") { + Some(RadrootsListingPrice { + amt: tag.get(1)?.clone(), + currency: tag.get(2)?.clone(), + qty_amt: tag.get(3)?.clone(), + qty_unit: tag.get(4)?.clone(), + qty_key: tag.get(5)?.clone(), + }) + } else { + None + } + }) + .collect::<Vec<RadrootsListingPrice>>(); + + let discounts = tags + .iter() + .filter_map(|tag| { + if tag.get(0).map(String::as_str) == Some("discount") { + Some(RadrootsListingDiscount::Quantity { + ref_quantity: "sample_ref".into(), + threshold: "100".into(), + value: "5".into(), + currency: "USD".into(), + }) + } else { + None + } + }) + .collect::<Vec<RadrootsListingDiscount>>(); + + let location = Some(RadrootsListingLocation { + primary: location_address, + city: Some(location_city), + region: Some(location_region), + country: Some(location_country), + lat: location_lat.map(|lat| lat.parse().unwrap_or_default()), + lng: location_lng.map(|lng| lng.parse().unwrap_or_default()), + geohash: Some(location_geohash), + }); + + let images = tags + .iter() + .filter_map(|tag| { + if tag.get(0).map(String::as_str) == Some("image") { + tag.get(1).map(|url| RadrootsListingImage { + url: url.clone(), + size: None, + }) + } else { + None + } + }) + .collect::<Vec<RadrootsListingImage>>(); + + let product = RadrootsListingProduct { + key: get("key", 0).unwrap_or_default(), + title, + category: get("category", 0).unwrap_or_default(), + summary: get("summary", 0), + process: get("process", 0), + lot: get("lot", 0), + location: get("location", 0), + profile: get("profile", 0), + year: get("year", 0), + }; + + let listing = RadrootsListing { + d_tag, + product, + quantities, + prices, + discounts: Some(discounts), + location, + images: Some(images), + }; + + Ok(RadrootsListingEventMetadata { id, author, published_at, - d_tag, - title, - summary, - images, - location_address, - location_city, - location_region, - location_country, - location_lat, - location_lng, - location_geohash, - product_kind, - product_category, - product_process, - product_lot, - product_profile, - product_year, - product_quantity_amt, - product_quantity_unit, - product_price_amt, - product_price_cur, - product_price_qty_amt, - product_price_qty_unit, + listing, }) } -pub trait ToRadrootsListingEvent { - fn to_radroots_listing_event(self) -> Result<RadrootsListingEvent, RadrootsListingEventError>; +pub trait ToRadrootsListingEventIndex { + fn to_radroots_listing_event( + self, + ) -> Result<RadrootsListingEventIndex, RadrootsListingEventIndexError>; } -impl ToRadrootsListingEvent for RelayIndexerEvent { - fn to_radroots_listing_event(self) -> Result<RadrootsListingEvent, RadrootsListingEventError> { - let data = create_radroots_listing_event_data( +impl ToRadrootsListingEventIndex for RelayIndexerEvent { + fn to_radroots_listing_event( + self, + ) -> Result<RadrootsListingEventIndex, RadrootsListingEventIndexError> { + let metadata = create_radroots_listing_event_metadata( self.id.clone(), self.author.clone(), + self.created_at.clone(), self.tags.clone(), )?; - Ok(RadrootsListingEvent { + Ok(RadrootsListingEventIndex { event: RadrootsNostrEvent { id: self.id, author: self.author, @@ -185,7 +225,7 @@ impl ToRadrootsListingEvent for RelayIndexerEvent { tags: self.tags, sig: self.sig, }, - data, + metadata, }) } } diff --git a/crates/indexer/src/domain/events/metadata.rs b/crates/indexer/src/domain/events/metadata.rs @@ -1,88 +0,0 @@ -use anyhow::Result; -use radroots_common::models::events::{ - RadrootsMetadataEvent, RadrootsMetadataEventData, RadrootsMetadataEventDataMetadata, - RadrootsNostrEvent, -}; -use std::collections::HashMap; -use thiserror::Error; - -use crate::domain::events::RequiredField; -use crate::{opt_required, relay::event::RelayIndexerEvent}; - -#[derive(Debug, Error)] -pub enum RadrootsMetadataEventError { - #[error("Failed to parse metadata content JSON: {0}")] - ParseError(#[from] serde_json::Error), - - #[error("Missing or empty 'name' field in profile data")] - MissingNameField, - - #[error("Missing or invalid 'published_at' tag")] - MissingPublishedAt, -} - -pub fn create_radroots_metadata_event_data( - id: String, - public_key: String, - content: String, - tags: Vec<Vec<String>>, -) -> Result<RadrootsMetadataEventData, RadrootsMetadataEventError> { - let mut tag_map: HashMap<String, Vec<Vec<String>>> = HashMap::new(); - for tag in tags { - if let Some(key) = tag.get(0).map(String::as_str) { - tag_map.entry(key.to_string()).or_default().push(tag); - } - } - - let get = |key: &str| -> Option<String> { tag_map.get(key)?.get(0)?.get(1).cloned() }; - - let published_at_str = opt_required!(get("published_at")) - .map_err(|_| RadrootsMetadataEventError::MissingPublishedAt)?; - let published_at = published_at_str - .parse::<u32>() - .map_err(|_| RadrootsMetadataEventError::MissingPublishedAt)?; - - let metadata: RadrootsMetadataEventDataMetadata = serde_json::from_str(&content)?; - if metadata.name.trim().is_empty() { - return Err(RadrootsMetadataEventError::MissingNameField); - } - - Ok(RadrootsMetadataEventData { - id, - public_key, - metadata, - published_at, - }) -} - -pub trait ToRadrootsMetadataEvent { - fn to_radroots_metadata_event( - self, - ) -> Result<RadrootsMetadataEvent, RadrootsMetadataEventError>; -} - -impl ToRadrootsMetadataEvent for RelayIndexerEvent { - fn to_radroots_metadata_event( - self, - ) -> Result<RadrootsMetadataEvent, RadrootsMetadataEventError> { - let data = create_radroots_metadata_event_data( - self.id.clone(), - self.author.clone(), - self.content.clone(), - self.tags.clone(), - )?; - - Ok(RadrootsMetadataEvent { - event: RadrootsNostrEvent { - id: self.id, - author: self.author, - created_at: self.created_at, - kind: self.kind.as_u64().try_into().unwrap(), - content: self.content, - tags: self.tags, - sig: self.sig, - }, - data, - }) - } -} diff --git a/crates/indexer/src/domain/events/mod.rs b/crates/indexer/src/domain/events/mod.rs @@ -1,8 +1,8 @@ pub mod listing; -pub mod metadata; +pub mod profile; -pub use listing::ToRadrootsListingEvent; -pub use metadata::ToRadrootsMetadataEvent; +pub use listing::ToRadrootsListingEventIndex; +pub use profile::ToRadrootsProfileEventIndex; #[macro_export] macro_rules! opt_required { diff --git a/crates/indexer/src/domain/events/profile.rs b/crates/indexer/src/domain/events/profile.rs @@ -0,0 +1,81 @@ +use anyhow::Result; +use radroots_common::events::profile::models::{ + RadrootsProfile, RadrootsProfileEventIndex, RadrootsProfileEventMetadata, +}; +use radroots_common::events::RadrootsNostrEvent; +use std::collections::HashMap; +use thiserror::Error; + +use crate::relay::event::RelayIndexerEvent; + +#[derive(Debug, Error)] +pub enum RadrootsProfileEventIndexError { + #[error("Failed to parse metadata content JSON: {0}")] + ParseError(#[from] serde_json::Error), + + #[error("Missing or empty 'name' field in profile data")] + MissingNameField, + + #[error("Missing or invalid 'published_at' tag")] + MissingPublishedAt, +} + +pub fn create_radroots_profile_event_metadata( + id: String, + author: String, + published_at: u32, + content: String, + tags: Vec<Vec<String>>, +) -> Result<RadrootsProfileEventMetadata, RadrootsProfileEventIndexError> { + let mut tag_map: HashMap<String, Vec<Vec<String>>> = HashMap::new(); + for tag in tags { + if let Some(key) = tag.get(0).map(String::as_str) { + tag_map.entry(key.to_string()).or_default().push(tag); + } + } + + let profile: RadrootsProfile = serde_json::from_str(&content)?; + if profile.name.trim().is_empty() { + return Err(RadrootsProfileEventIndexError::MissingNameField); + } + + Ok(RadrootsProfileEventMetadata { + id, + author, + published_at, + profile, + }) +} + +pub trait ToRadrootsProfileEventIndex { + fn to_radroots_profile_event( + self, + ) -> Result<RadrootsProfileEventIndex, RadrootsProfileEventIndexError>; +} + +impl ToRadrootsProfileEventIndex for RelayIndexerEvent { + fn to_radroots_profile_event( + self, + ) -> Result<RadrootsProfileEventIndex, RadrootsProfileEventIndexError> { + let metadata = create_radroots_profile_event_metadata( + self.id.clone(), + self.author.clone(), + self.created_at.clone(), + self.content.clone(), + self.tags.clone(), + )?; + + Ok(RadrootsProfileEventIndex { + event: RadrootsNostrEvent { + id: self.id, + author: self.author, + created_at: self.created_at, + kind: self.kind.as_u64().try_into().unwrap(), + content: self.content, + tags: self.tags, + sig: self.sig, + }, + metadata, + }) + } +} diff --git a/crates/indexer/src/domain/indexer/key.rs b/crates/indexer/src/domain/indexer/key.rs @@ -19,7 +19,7 @@ impl IndexerKey { } } -pub const METADATA_INDEX_DIRECTORY: [IndexerKey; 4] = [ +pub const PROFILE_INDEX_DIRECTORY: [IndexerKey; 4] = [ IndexerKey::Id, IndexerKey::Author, IndexerKey::Nip05, diff --git a/crates/indexer/src/domain/indexer/kind.rs b/crates/indexer/src/domain/indexer/kind.rs @@ -5,27 +5,27 @@ use std::fmt; use std::path::PathBuf; use crate::domain::indexer::key::LISTING_INDEX_DIRECTORY; -use crate::domain::indexer::{IndexerKey, METADATA_INDEX_DIRECTORY}; +use crate::domain::indexer::{IndexerKey, PROFILE_INDEX_DIRECTORY}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum IndexerEventKind { - Metadata, + Profile, Listing, } impl IndexerEventKind { - pub const ALL: [IndexerEventKind; 2] = [IndexerEventKind::Metadata, IndexerEventKind::Listing]; + pub const ALL: [IndexerEventKind; 2] = [IndexerEventKind::Profile, IndexerEventKind::Listing]; pub const fn as_u64(self) -> u64 { match self { - IndexerEventKind::Metadata => 0, + IndexerEventKind::Profile => 0, IndexerEventKind::Listing => 30402, } } pub const fn paths(self) -> &'static [IndexerKey] { match self { - IndexerEventKind::Metadata => &METADATA_INDEX_DIRECTORY, + IndexerEventKind::Profile => &PROFILE_INDEX_DIRECTORY, IndexerEventKind::Listing => &LISTING_INDEX_DIRECTORY, } } @@ -75,7 +75,7 @@ impl TryFrom<u64> for IndexerEventKind { fn try_from(val: u64) -> Result<Self, Self::Error> { match val { - 0 => Ok(IndexerEventKind::Metadata), + 0 => Ok(IndexerEventKind::Profile), 30402 => Ok(IndexerEventKind::Listing), other => Err(IndexerEventKindParseError(other)), } diff --git a/crates/indexer/src/domain/indexer/mod.rs b/crates/indexer/src/domain/indexer/mod.rs @@ -2,4 +2,4 @@ pub mod key; pub mod kind; pub mod models; -pub use key::{IndexerKey, METADATA_INDEX_DIRECTORY}; +pub use key::{IndexerKey, PROFILE_INDEX_DIRECTORY}; diff --git a/crates/indexer/src/domain/indexer/models/listing.rs b/crates/indexer/src/domain/indexer/models/listing.rs @@ -4,9 +4,9 @@ use indexer_utils::{ nostr::public_key_to_npub, write::{compute_hash, write_hash, write_json}, }; -use radroots_common::models::{ - events::{RadrootsListingEvent, RadrootsListingEventData}, - indexer::{RadrootsIndexManifest, RadrootsIndexShardMetadata}, +use radroots_common::{ + events::listing::models::{RadrootsListingEventIndex, RadrootsListingEventMetadata}, + models::indexer::{RadrootsIndexManifest, RadrootsIndexShardMetadata}, }; use std::{collections::BTreeMap, fs, path::PathBuf}; use tracing::{instrument, warn}; @@ -14,7 +14,7 @@ use tracing::{instrument, warn}; use crate::{ audit, domain::{ - events::ToRadrootsListingEvent, + events::ToRadrootsListingEventIndex, indexer::{ key::LISTING_INDEX_DIRECTORY, kind::IndexerEventKind, @@ -51,8 +51,8 @@ macro_rules! write_if_stale { #[derive(Debug)] pub struct EventListingIndexes { - events: Vec<RadrootsListingEvent>, - events_id: BTreeMap<String, RadrootsListingEvent>, + events: Vec<RadrootsListingEventIndex>, + events_id: BTreeMap<String, RadrootsListingEventIndex>, country_ids: BTreeMap<String, Vec<String>>, author_ids: BTreeMap<String, Vec<String>>, npub_ids: BTreeMap<String, Vec<String>>, @@ -64,8 +64,8 @@ impl EventListingIndexes { raw_events: &[RelayIndexerEvent], profiles: &ProfileResolver, ) -> Result<Self, NostrEventsStaticError> { - let mut events: Vec<RadrootsListingEvent> = Vec::with_capacity(raw_events.len()); - let mut events_id: BTreeMap<String, RadrootsListingEvent> = BTreeMap::new(); + let mut events: Vec<RadrootsListingEventIndex> = Vec::with_capacity(raw_events.len()); + let mut events_id: BTreeMap<String, RadrootsListingEventIndex> = BTreeMap::new(); let mut country_ids: BTreeMap<String, Vec<String>> = BTreeMap::new(); let mut author_ids: BTreeMap<String, Vec<String>> = BTreeMap::new(); let mut npub_ids: BTreeMap<String, Vec<String>> = BTreeMap::new(); @@ -76,24 +76,36 @@ impl EventListingIndexes { Ok(evt) => { audit::log_listing_event(&evt); - let id = evt.event.id.clone(); - let author = evt.event.author.to_lowercase(); + let id = evt.metadata.id.clone(); + let author_hex = evt.metadata.author.to_lowercase(); - let npub = public_key_to_npub(&author).map(|s| s.to_lowercase()).ok(); - let author_nip05 = profiles.nip05_for_author(&author).map(str::to_owned); + let npub = public_key_to_npub(&author_hex) + .map(|s| s.to_lowercase()) + .ok(); + let author_nip05 = profiles.nip05_for_author(&author_hex).map(str::to_owned); - let country = evt.data.location_country.to_lowercase(); + let country_opt = evt + .metadata + .listing + .location + .as_ref() + .and_then(|loc| loc.country.as_ref()) + .map(|c| c.to_lowercase()); events_id.insert(id.clone(), evt.clone()); events.push(evt.clone()); - country_ids.entry(country).or_default().push(id.clone()); - author_ids.entry(author).or_default().push(id.clone()); + if let Some(country) = country_opt { + country_ids.entry(country).or_default().push(id.clone()); + } + + author_ids.entry(author_hex).or_default().push(id.clone()); if let Some(n) = npub { npub_ids.entry(n).or_default().push(id.clone()); } if let Some(n05) = author_nip05 { + let n05 = n05.to_lowercase(); nip05_ids.entry(n05).or_default().push(id.clone()); } } @@ -111,10 +123,17 @@ impl EventListingIndexes { } } - let sort_ids = |ids: &mut Vec<String>, map: &BTreeMap<String, RadrootsListingEvent>| { + let sort_ids = |ids: &mut Vec<String>, + map: &BTreeMap<String, RadrootsListingEventIndex>| { ids.sort_unstable_by(|a, b| { - let pa = map.get(a).map(|e| e.data.published_at).unwrap_or_default(); - let pb = map.get(b).map(|e| e.data.published_at).unwrap_or_default(); + let pa = map + .get(a) + .map(|e| e.metadata.published_at) + .unwrap_or_default(); + let pb = map + .get(b) + .map(|e| e.metadata.published_at) + .unwrap_or_default(); pb.cmp(&pa).then(a.cmp(b)) }); }; @@ -132,11 +151,11 @@ impl EventListingIndexes { ids.sort_unstable_by(|a, b| { let pa = events_id .get(a) - .map(|e| e.data.published_at) + .map(|e| e.metadata.published_at) .unwrap_or_default(); let pb = events_id .get(b) - .map(|e| e.data.published_at) + .map(|e| e.metadata.published_at) .unwrap_or_default(); pb.cmp(&pa).then(a.cmp(b)) }); @@ -208,7 +227,7 @@ impl WriteEventIndexes for EventListingIndexes { let dir = sub.join(id.to_lowercase()); fs_mkdir(&[&dir])?; write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); - write_if_stale!(dir.join("data.json"), evt.data.clone(), updated); + write_if_stale!(dir.join("data.json"), evt.metadata.clone(), updated); } } @@ -224,10 +243,11 @@ impl WriteEventIndexes for EventListingIndexes { fs_mkdir(&[&cc_dir])?; fs_mkdir(&[&shards_dir])?; - let mut data_items: Vec<RadrootsListingEventData> = Vec::with_capacity(ids.len()); + let mut data_items: Vec<RadrootsListingEventMetadata> = + Vec::with_capacity(ids.len()); for id in ids { if let Some(evt) = self.events_id.get(id) { - data_items.push(evt.data.clone()); + data_items.push(evt.metadata.clone()); } } @@ -303,10 +323,11 @@ impl WriteEventIndexes for EventListingIndexes { let shards_dir = dir.join("shards"); fs_mkdir(&[&dir, &shards_dir])?; - let mut data_items: Vec<RadrootsListingEventData> = Vec::with_capacity(ids.len()); + let mut data_items: Vec<RadrootsListingEventMetadata> = + Vec::with_capacity(ids.len()); for id in ids { if let Some(evt) = self.events_id.get(id) { - data_items.push(evt.data.clone()); + data_items.push(evt.metadata.clone()); } } @@ -390,10 +411,11 @@ impl WriteEventIndexes for EventListingIndexes { let shards_dir = dir.join("shards"); fs_mkdir(&[&dir, &shards_dir])?; - let mut data_items: Vec<RadrootsListingEventData> = Vec::with_capacity(ids.len()); + let mut data_items: Vec<RadrootsListingEventMetadata> = + Vec::with_capacity(ids.len()); for id in ids { if let Some(evt) = self.events_id.get(id) { - data_items.push(evt.data.clone()); + data_items.push(evt.metadata.clone()); } } @@ -479,7 +501,7 @@ impl WriteEventIndexes for EventListingIndexes { let mut data_items = Vec::with_capacity(ids.len()); for id in ids { if let Some(evt) = self.events_id.get(id) { - data_items.push(evt.data.clone()); + data_items.push(evt.metadata.clone()); } } diff --git a/crates/indexer/src/domain/indexer/models/metadata.rs b/crates/indexer/src/domain/indexer/models/metadata.rs @@ -1,177 +0,0 @@ -use indexer_utils::{ - file::fs_mkdir, - logs::truncate_log, - nostr::public_key_to_npub, - write::{compute_hash, write_hash, write_json}, -}; -use radroots_common::models::events::RadrootsMetadataEvent; -use std::{collections::BTreeMap, fs, path::PathBuf}; -use tracing::{instrument, warn}; - -use crate::{ - audit, - domain::{ - events::ToRadrootsMetadataEvent, - indexer::{ - kind::IndexerEventKind, - models::{EventIndexes, NostrEventsStaticError, WriteEventIndexes}, - IndexerKey, METADATA_INDEX_DIRECTORY, - }, - }, - relay::event::RelayIndexerEvent, - Settings, -}; - -macro_rules! write_if_stale { - ($path:expr, $data:expr, $updated:expr) => {{ - let hash = compute_hash(&$data)?; - let hash_path = $path.with_extension("sha256.txt"); - - let needs_write = if $path.exists() && hash_path.exists() { - let stored = fs::read_to_string(&hash_path)?; - stored.trim() != hash - } else { - true - }; - - if needs_write { - write_json(&$path, &$data)?; - write_hash(&$path, &hash)?; - $updated.push($path.clone()); - } - }}; -} - -#[derive(Debug)] -pub struct EventMetadataIndexes { - events: Vec<RadrootsMetadataEvent>, - events_id: BTreeMap<String, RadrootsMetadataEvent>, - events_author: BTreeMap<String, RadrootsMetadataEvent>, - events_nip05: BTreeMap<String, RadrootsMetadataEvent>, - events_npub: BTreeMap<String, RadrootsMetadataEvent>, -} - -impl EventIndexes for EventMetadataIndexes { - type Event = RelayIndexerEvent; - - fn subdirs() -> &'static [IndexerKey] { - &METADATA_INDEX_DIRECTORY - } - - #[instrument(skip(raw_events), fields(event_count = raw_events.len()))] - fn build(raw_events: &[Self::Event]) -> Result<Self, NostrEventsStaticError> { - let mut events = Vec::with_capacity(raw_events.len()); - let mut events_id = BTreeMap::new(); - let mut events_author = BTreeMap::new(); - let mut events_nip05 = BTreeMap::new(); - let mut events_npub = BTreeMap::new(); - - for raw in raw_events { - match raw.clone().to_radroots_metadata_event() { - Ok(evt) => { - audit::log_metadata_event(&evt); - let id = evt.event.id.clone(); - let author = evt.event.author.clone(); - events.push(evt.clone()); - events_id.insert(id.clone(), evt.clone()); - events_author.insert(author.clone(), evt.clone()); - - if let Ok(npub) = public_key_to_npub(&author) { - events_npub.insert(npub.to_lowercase(), evt.clone()); - } - if let Some(nip05) = &evt.data.metadata.nip05 { - let normalized = nip05.replace("@radroots.market", ""); - events_nip05.insert(normalized, evt.clone()); - } - } - Err(err) => { - warn!( - kind = raw.kind.as_u64(), - id = %raw.id, - author = %raw.author, - content = %truncate_log(&raw.content, 1000), - tags = ?raw.tags, - error = %err, - "Skipping malformed metadata event" - ); - } - } - } - - Ok(EventMetadataIndexes { - events, - events_id, - events_author, - events_nip05, - events_npub, - }) - } -} - -impl WriteEventIndexes for EventMetadataIndexes { - fn write(&self, settings: &Settings, updated: &mut Vec<PathBuf>) -> anyhow::Result<()> { - let base: PathBuf = IndexerEventKind::Metadata.base_path(&settings.indexer.data_dir)?; - fs_mkdir(&[&base])?; - - let idxs_root = base.join("events.json"); - let ids: Vec<&String> = self.events.iter().map(|e| &e.event.id).collect(); - write_if_stale!(idxs_root, ids, updated); - - for &subdir in Self::subdirs().iter() { - let sub_base = base.join(subdir.as_str()); - fs_mkdir(&[sub_base.to_str().unwrap()])?; - - let keys_lower: Vec<String> = match subdir { - IndexerKey::Id => self.events_id.keys().map(|k| k.to_lowercase()).collect(), - IndexerKey::Author => self - .events_author - .keys() - .map(|k| k.to_lowercase()) - .collect(), - IndexerKey::Nip05 => self.events_nip05.keys().map(|k| k.to_lowercase()).collect(), - IndexerKey::Npub => self.events_npub.keys().map(|k| k.to_lowercase()).collect(), - _ => Vec::new(), - }; - let idxs_subdir = sub_base.join("indexes.json"); - write_if_stale!(idxs_subdir, keys_lower, updated); - - match subdir { - IndexerKey::Id => { - for (key, evt) in &self.events_id { - let dir = sub_base.join(key.to_lowercase()); - fs_mkdir(&[dir.to_str().unwrap()])?; - write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); - write_if_stale!(dir.join("metadata.json"), evt.data.clone(), updated); - } - } - IndexerKey::Author => { - for (key, evt) in &self.events_author { - let dir = sub_base.join(key.to_lowercase()); - fs_mkdir(&[dir.to_str().unwrap()])?; - write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); - write_if_stale!(dir.join("metadata.json"), evt.data.clone(), updated); - } - } - IndexerKey::Nip05 => { - for (key, evt) in &self.events_nip05 { - let dir = sub_base.join(key.to_lowercase()); - fs_mkdir(&[dir.to_str().unwrap()])?; - write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); - write_if_stale!(dir.join("metadata.json"), evt.data.clone(), updated); - } - } - IndexerKey::Npub => { - for (key, evt) in &self.events_npub { - let dir = sub_base.join(key.to_lowercase()); - fs_mkdir(&[dir.to_str().unwrap()])?; - write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); - write_if_stale!(dir.join("metadata.json"), evt.data.clone(), updated); - } - } - _ => {} - } - } - - Ok(()) - } -} diff --git a/crates/indexer/src/domain/indexer/models/mod.rs b/crates/indexer/src/domain/indexer/models/mod.rs @@ -1,8 +1,8 @@ pub mod listing; -pub mod metadata; +pub mod profile; pub use listing::EventListingIndexes; -pub use metadata::EventMetadataIndexes; +pub use profile::EventProfileIndexes; use crate::{config::Settings, domain::indexer::IndexerKey}; use anyhow::Result; diff --git a/crates/indexer/src/domain/indexer/models/profile.rs b/crates/indexer/src/domain/indexer/models/profile.rs @@ -0,0 +1,177 @@ +use indexer_utils::{ + file::fs_mkdir, + logs::truncate_log, + nostr::public_key_to_npub, + write::{compute_hash, write_hash, write_json}, +}; +use radroots_common::events::profile::models::RadrootsProfileEventIndex; +use std::{collections::BTreeMap, fs, path::PathBuf}; +use tracing::{instrument, warn}; + +use crate::{ + audit, + domain::{ + events::ToRadrootsProfileEventIndex, + indexer::{ + kind::IndexerEventKind, + models::{EventIndexes, NostrEventsStaticError, WriteEventIndexes}, + IndexerKey, PROFILE_INDEX_DIRECTORY, + }, + }, + relay::event::RelayIndexerEvent, + Settings, +}; + +macro_rules! write_if_stale { + ($path:expr, $data:expr, $updated:expr) => {{ + let hash = compute_hash(&$data)?; + let hash_path = $path.with_extension("sha256.txt"); + + let needs_write = if $path.exists() && hash_path.exists() { + let stored = fs::read_to_string(&hash_path)?; + stored.trim() != hash + } else { + true + }; + + if needs_write { + write_json(&$path, &$data)?; + write_hash(&$path, &hash)?; + $updated.push($path.clone()); + } + }}; +} + +#[derive(Debug)] +pub struct EventProfileIndexes { + events: Vec<RadrootsProfileEventIndex>, + events_id: BTreeMap<String, RadrootsProfileEventIndex>, + events_author: BTreeMap<String, RadrootsProfileEventIndex>, + events_nip05: BTreeMap<String, RadrootsProfileEventIndex>, + events_npub: BTreeMap<String, RadrootsProfileEventIndex>, +} + +impl EventIndexes for EventProfileIndexes { + type Event = RelayIndexerEvent; + + fn subdirs() -> &'static [IndexerKey] { + &PROFILE_INDEX_DIRECTORY + } + + #[instrument(skip(raw_events), fields(event_count = raw_events.len()))] + fn build(raw_events: &[Self::Event]) -> Result<Self, NostrEventsStaticError> { + let mut events = Vec::with_capacity(raw_events.len()); + let mut events_id = BTreeMap::new(); + let mut events_author = BTreeMap::new(); + let mut events_nip05 = BTreeMap::new(); + let mut events_npub = BTreeMap::new(); + + for raw in raw_events { + match raw.clone().to_radroots_profile_event() { + Ok(evt) => { + audit::log_profile_event(&evt); + let id = evt.event.id.clone(); + let author = evt.event.author.clone(); + events.push(evt.clone()); + events_id.insert(id.clone(), evt.clone()); + events_author.insert(author.clone(), evt.clone()); + + if let Ok(npub) = public_key_to_npub(&author) { + events_npub.insert(npub.to_lowercase(), evt.clone()); + } + if let Some(nip05) = &evt.metadata.profile.nip05 { + let normalized = nip05.replace("@radroots.market", ""); + events_nip05.insert(normalized, evt.clone()); + } + } + Err(err) => { + warn!( + kind = raw.kind.as_u64(), + id = %raw.id, + author = %raw.author, + content = %truncate_log(&raw.content, 1000), + tags = ?raw.tags, + error = %err, + "Skipping malformed metadata event" + ); + } + } + } + + Ok(EventProfileIndexes { + events, + events_id, + events_author, + events_nip05, + events_npub, + }) + } +} + +impl WriteEventIndexes for EventProfileIndexes { + fn write(&self, settings: &Settings, updated: &mut Vec<PathBuf>) -> anyhow::Result<()> { + let base: PathBuf = IndexerEventKind::Profile.base_path(&settings.indexer.data_dir)?; + fs_mkdir(&[&base])?; + + let idxs_root = base.join("events.json"); + let ids: Vec<&String> = self.events.iter().map(|e| &e.event.id).collect(); + write_if_stale!(idxs_root, ids, updated); + + for &subdir in Self::subdirs().iter() { + let sub_base = base.join(subdir.as_str()); + fs_mkdir(&[sub_base.to_str().unwrap()])?; + + let keys_lower: Vec<String> = match subdir { + IndexerKey::Id => self.events_id.keys().map(|k| k.to_lowercase()).collect(), + IndexerKey::Author => self + .events_author + .keys() + .map(|k| k.to_lowercase()) + .collect(), + IndexerKey::Nip05 => self.events_nip05.keys().map(|k| k.to_lowercase()).collect(), + IndexerKey::Npub => self.events_npub.keys().map(|k| k.to_lowercase()).collect(), + _ => Vec::new(), + }; + let idxs_subdir = sub_base.join("indexes.json"); + write_if_stale!(idxs_subdir, keys_lower, updated); + + match subdir { + IndexerKey::Id => { + for (key, evt) in &self.events_id { + let dir = sub_base.join(key.to_lowercase()); + fs_mkdir(&[dir.to_str().unwrap()])?; + write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); + write_if_stale!(dir.join("metadata.json"), evt.metadata.clone(), updated); + } + } + IndexerKey::Author => { + for (key, evt) in &self.events_author { + let dir = sub_base.join(key.to_lowercase()); + fs_mkdir(&[dir.to_str().unwrap()])?; + write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); + write_if_stale!(dir.join("metadata.json"), evt.metadata.clone(), updated); + } + } + IndexerKey::Nip05 => { + for (key, evt) in &self.events_nip05 { + let dir = sub_base.join(key.to_lowercase()); + fs_mkdir(&[dir.to_str().unwrap()])?; + write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); + write_if_stale!(dir.join("metadata.json"), evt.metadata.clone(), updated); + } + } + IndexerKey::Npub => { + for (key, evt) in &self.events_npub { + let dir = sub_base.join(key.to_lowercase()); + fs_mkdir(&[dir.to_str().unwrap()])?; + write_if_stale!(dir.join("event.json"), evt.event.clone(), updated); + write_if_stale!(dir.join("metadata.json"), evt.metadata.clone(), updated); + } + } + _ => {} + } + } + + Ok(()) + } +} diff --git a/crates/indexer/src/domain/resolvers/profile.rs b/crates/indexer/src/domain/resolvers/profile.rs @@ -1,4 +1,4 @@ -use crate::domain::events::ToRadrootsMetadataEvent; +use crate::domain::events::ToRadrootsProfileEventIndex; use crate::relay::event::RelayIndexerEvent; use std::collections::BTreeMap; @@ -12,15 +12,15 @@ impl ProfileResolver { let mut latest: BTreeMap<String, (u32, String)> = BTreeMap::new(); for raw in raw_metadata { - if let Ok(evt) = raw.clone().to_radroots_metadata_event() { - if let Some(n) = &evt.data.metadata.nip05 { + if let Ok(evt) = raw.clone().to_radroots_profile_event() { + if let Some(n) = &evt.metadata.profile.nip05 { let normalized = n.replace("@radroots.market", "").to_lowercase(); if normalized.is_empty() { continue; } let author = evt.event.author.to_lowercase(); - let ts = evt.data.published_at; + let ts = evt.metadata.published_at; match latest.get(&author) { Some(&(old_ts, _)) if old_ts >= ts => {} _ => { diff --git a/crates/indexer/src/lib.rs b/crates/indexer/src/lib.rs @@ -27,16 +27,20 @@ pub mod audit; #[cfg(not(feature = "audit"))] pub mod audit { + use radroots_common::events::{ + listing::models::RadrootsListingEventIndex, profile::models::RadrootsProfileEventIndex, + }; + pub fn log_indexer_event(_: &crate::relay::event::RelayIndexerEvent) {} - pub fn log_metadata_event(_: &radroots_common::models::events::RadrootsMetadataEvent) {} - pub fn log_listing_event(_: &radroots_common::models::events::RadrootsListingEvent) {} + pub fn log_profile_event(_: &RadrootsProfileEventIndex) {} + pub fn log_listing_event(_: &RadrootsListingEventIndex) {} } use crate::{ domain::{ indexer::{ kind::IndexerEventKind, - models::{EventIndexes, EventListingIndexes, EventMetadataIndexes, WriteEventIndexes}, + models::{EventIndexes, EventListingIndexes, EventProfileIndexes, WriteEventIndexes}, }, resolvers::profile::ProfileResolver, }, @@ -48,7 +52,7 @@ pub use relay::record::RelayEventRecord; pub async fn run(settings: Settings) -> Result<()> { let db_idx = IndexerDb::open(&format!("{}/indexer_db", settings.indexer.data_dir))?; let tree_raw = "hashes"; - let tree_events_metadata = "metadata_events"; + let tree_events_profile = "profile_events"; let tree_events_listing = "listing_events"; let tree_stats = "stats"; @@ -99,9 +103,9 @@ pub async fn run(settings: Settings) -> Result<()> { let mut need_rebuild_listing = false; - if let Some(metadata_events) = records_kind.remove(&IndexerEventKind::Metadata) { - if !metadata_events.is_empty() { - for ev in &metadata_events { + if let Some(profile_events) = records_kind.remove(&IndexerEventKind::Profile) { + if !profile_events.is_empty() { + for ev in &profile_events { last_created_at = last_created_at.max(ev.created_at); let id = &ev.id; let hash = &ev.hash; @@ -115,7 +119,7 @@ pub async fn run(settings: Settings) -> Result<()> { continue; } - db_idx.insert(tree_events_metadata, id, ev)?; + db_idx.insert(tree_events_profile, id, ev)?; db_idx.insert_raw(tree_raw, id, hash.as_bytes())?; } @@ -126,11 +130,11 @@ pub async fn run(settings: Settings) -> Result<()> { )?; db_idx.flush()?; - let raw_metadata_events: Vec<RelayIndexerEvent> = - db_idx.get_all(tree_events_metadata)?; - let indexed_metadata_events = EventMetadataIndexes::build(&raw_metadata_events)?; + let raw_profile_events: Vec<RelayIndexerEvent> = + db_idx.get_all(tree_events_profile)?; + let indexed_profile_events = EventProfileIndexes::build(&raw_profile_events)?; let mut updated_indexes = Vec::new(); - indexed_metadata_events.write(&settings, &mut updated_indexes)?; + indexed_profile_events.write(&settings, &mut updated_indexes)?; info!( written = updated_indexes.len(), "Written {} index files", @@ -141,8 +145,8 @@ pub async fn run(settings: Settings) -> Result<()> { } } - let raw_metadata_events: Vec<RelayIndexerEvent> = db_idx.get_all(tree_events_metadata)?; - let profiles = ProfileResolver::from_metadata(&raw_metadata_events); + let raw_profile_events: Vec<RelayIndexerEvent> = db_idx.get_all(tree_events_profile)?; + let profiles = ProfileResolver::from_metadata(&raw_profile_events); if let Some(listing_events) = records_kind.remove(&IndexerEventKind::Listing) { if !listing_events.is_empty() {