web


git clone https://radroots.dev/git/web.git
Log | Files | Refs | Submodules | README | LICENSE

commit 42f720316db17adbd4a973f063fb7f3b423ffe87
parent 59117e9a992dc37f2d066d910c89270787697003
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Sat,  8 Mar 2025 00:11:15 +0000

Edit `model` crate adding update method result pass return types. Add app client nostr sync util. Edit profile views, layouts.

Diffstat:
Aapp/src/lib/util/nostr/sync.ts | 22++++++++++++++++++++++
Mapp/src/routes/(app)/+layout.svelte | 22+++++++++++++++-------
Mapp/src/routes/(app)/profile/+page.svelte | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mapp/src/routes/(app)/profile/edit/+page.svelte | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mapp/src/routes/(cfg)/init/+page.svelte | 34++++++++++++++++++++++++++++++----
Mapp/src/routes/+layout.svelte | 16+---------------
Mcrates/model/src/tables/location_gcs.rs | 4++--
Mcrates/model/src/tables/log_error.rs | 4++--
Mcrates/model/src/tables/media_image.rs | 4++--
Mcrates/model/src/tables/nostr_profile.rs | 4++--
Mcrates/model/src/tables/nostr_relay.rs | 4++--
Mcrates/model/src/tables/trade_product.rs | 4++--
12 files changed, 347 insertions(+), 132 deletions(-)

diff --git a/app/src/lib/util/nostr/sync.ts b/app/src/lib/util/nostr/sync.ts @@ -0,0 +1,21 @@ +import { get_store, handle_err, ndk_user } from "@radroots/lib-app"; +import { throw_err } from "@radroots/util"; +import { db, nostrsync } from ".."; + +export const nostr_sync_metadata = async (): Promise<void> => { + try { + const $ndk_user = get_store(ndk_user); + const tb_nostr_profile = await db.nostr_profile_read({ + public_key: $ndk_user?.pubkey, + }); + if (`err` in tb_nostr_profile) throw_err(tb_nostr_profile.err); + const ev_metadata = await nostrsync.metadata({ + metadata: tb_nostr_profile.result, + }); + if (`err` in ev_metadata) throw_err(ev_metadata.err); + await ev_metadata.publish(); + } catch (e) { + await handle_err(e, `nostr_sync_metadata`); + //location.reload(); @todo + } +}; +\ No newline at end of file diff --git a/app/src/routes/(app)/+layout.svelte b/app/src/routes/(app)/+layout.svelte @@ -1,6 +1,7 @@ <script lang="ts"> - import { datastore, db, http, keys } from "$lib/util"; + import { datastore, db, gui, http, keys } from "$lib/util"; import { + app_notify, app_splash, handle_err, idb_init, @@ -11,6 +12,7 @@ import { ndk_init } from "@radroots/nostr-util"; import { throw_err } from "@radroots/util"; + import { nostr_sync_metadata } from "$lib/util/nostr/sync"; import { onMount } from "svelte"; import type { LayoutProps } from "./$types"; @@ -41,15 +43,13 @@ const nostr_init = async (): Promise<void> => { try { - if (!data.public_key) return void throw_err(`*-key_nostr`); + if (!data.public_key) throw_err(`*-key_nostr`); const keys_nostr_read = await keys.nostr_read(data.public_key); - if (`err` in keys_nostr_read) - return void throw_err(keys_nostr_read.err); + if (`err` in keys_nostr_read) throw_err(keys_nostr_read.err); const tb_nostr_relays = await db.nostr_relay_read_list({ table: [`on_profile`, { public_key: data.public_key }], }); - if (`err` in tb_nostr_relays) - return void throw_err(tb_nostr_relays.err); + if (`err` in tb_nostr_relays) throw_err(tb_nostr_relays.err); for (const { url } of tb_nostr_relays.results) $ndk.addExplicitRelay(url); await $ndk.connect(); @@ -58,13 +58,21 @@ secret_key: keys_nostr_read.secret_key, }); nostr_ndk_configured.set(!!ndk_user_init); - if (!ndk_user_init) return; //@todo + if (!ndk_user_init) throw_err(`error.nostr.ndk_user_undefined`); $ndk_user = ndk_user_init; $ndk_user.ndk = $ndk; + await nostr_sync_metadata(); } catch (e) { await handle_err(e, `nostr_init`); } }; + + app_notify.subscribe(async (_app_notify) => { + console.log(`_app_notify `, _app_notify); + if (!_app_notify) return; + await gui.notify_send($app_notify); + app_notify.set(``); + }); </script> {@render children()} diff --git a/app/src/routes/(app)/profile/+page.svelte b/app/src/routes/(app)/profile/+page.svelte @@ -1,94 +1,178 @@ <script lang="ts"> import { ls } from "$lib/locale/i18n"; - import { db, fs, gui, nostrsync, route } from "$lib/util"; - import { handle_err, ndk_user, Profile } from "@radroots/lib-app"; - import { throw_err } from "@radroots/util"; + import { db, fs, gui, keys, radroots, route } from "$lib/util"; + import { nostr_sync_metadata } from "$lib/util/nostr/sync"; + import { + handle_err, + ndk_user, + Profile, + type IViewProfileData, + } from "@radroots/lib-app"; + import { parse_file_path, throw_err, type FilePath } from "@radroots/util"; + import { onMount } from "svelte"; + let data: IViewProfileData | undefined = $state(undefined); let loading_photo_upload = $state(false); - let photo_path_opt = $state(``); -</script> + let loading_photo_upload_open = $state(false); + let photo_path = $state(``); -<Profile - bind:photo_path_opt - {ls} - basis={{ - loading_photo_upload, - lc_on_destroy: async () => { - try { - const tb_nostrprofile = await db.nostr_profile_read({ - public_key: $ndk_user.pubkey, - }); - if (`err` in tb_nostrprofile) throw_err(tb_nostrprofile.err); //@todo - const ev_metadata = await nostrsync.metadata({ - metadata: tb_nostrprofile.result, - }); - if (`err` in ev_metadata) - throw_err(`error.nostr.sync.missing_metadata_event`); - await ev_metadata.publish(); - } catch (e) { - await handle_err(e, `lc_on_destroy`); + onMount(async () => { + try { + const tb_nostr_profile = await db.nostr_profile_read({ + public_key: $ndk_user?.pubkey, + }); + console.log( + JSON.stringify(tb_nostr_profile, null, 4), + `tb_nostr_profile`, + ); + if (`result` in tb_nostr_profile) { + data = { ...tb_nostr_profile.result }; + nostr_sync_metadata(); // no await + return; } - }, - lc_handle_back: async () => { - try { - if (photo_path_opt) { - const confirm = await gui.confirm({ - message: `${$ls(`notification.profile.update_name_confirmation_on_route_previous`)}`, - ok: `${$ls(`common.upload_photo`)}`, - cancel: `${$ls(`common.keep_previous`)}`, + return void (await route(`/`)); + } catch (e) { + handle_err(e, `on_mount`); + } + }); +</script> + +{#if data} + <Profile + bind:photo_path + {ls} + basis={{ + data, + loading_photo_upload, + loading_photo_upload_open, + lc_on_destroy: async ({ public_key }) => { + try { + const tb_nostrprofile = await db.nostr_profile_read({ + public_key, }); - if (!confirm) return void (await route(`/`)); - // @todo upload photo + if (`err` in tb_nostrprofile) + throw_err(tb_nostrprofile.err); //@todo + await nostr_sync_metadata(); + } catch (e) { + await handle_err(e, `lc_on_destroy`); } - await route(`/`); - } catch (e) { - await handle_err(e, `lc_handle_back`); - } - }, - lc_handle_edit_profile_field: async ({ field }) => { - try { - //@todo - if (field === `name`) { - const confirm = await gui.confirm({ - message: `${$ls(`notification.profile.update_name_confirmation`)}. ${$ls(`common.do_you_want_to_continue_q`)}`, - ok: `${$ls(`common.continue`)}`, - cancel: `${$ls(`common.cancel`)}`, + }, + lc_handle_back: async ({ is_photo_existing }) => { + try { + const public_key = $ndk_user?.pubkey; + if (!photo_path || !public_key) + return void (await route(`/`)); + const keys_nostr_read = await keys.nostr_read(public_key); + if (`err` in keys_nostr_read) + throw_err(keys_nostr_read.err); + if (photo_path) { + const confirm = await gui.confirm({ + message: is_photo_existing + ? `${$ls(`notification.profile.handle_back_with_selected_photo`)}` + : `${$ls(`notification.profile.handle_back_with_selected_photo_no_existing`)}`, + ok: `${$ls(`common.upload_photo`)}`, + cancel: is_photo_existing + ? `${$ls(`common.keep_previous`)}` + : `${$ls(`common.continue`)}`, + }); + if (!confirm) return void (await route(`/`)); + } + loading_photo_upload = true; + let upload_file_path: FilePath | undefined = undefined; + parse_file_path(photo_path); + const tb_media_upload_existing = await db.media_image_read({ + file_path: photo_path, }); - if (!confirm) return; + if (`result` in tb_media_upload_existing) + upload_file_path = parse_file_path( + tb_media_upload_existing.result.file_path, + ); + else upload_file_path = parse_file_path(photo_path); + if (!upload_file_path) + throw_err(`error.util.parse_file_path`); + const file_data = await fs.read_bin( + upload_file_path.file_path, + ); + const res_fetch_media_image_upload = + await radroots.fetch_media_image_upload({ + file_path: upload_file_path, + file_data, + secret_key: keys_nostr_read.secret_key, + }); + if (`err` in res_fetch_media_image_upload) + throw_err(res_fetch_media_image_upload.err); + const { + res_base: upload_res_base, + res_path: upload_res_path, + } = res_fetch_media_image_upload; + const tb_media_image_create = await db.media_image_create({ + file_path: upload_file_path.file_path, + res_base: upload_res_base, + res_path: upload_res_path, + mime_type: upload_file_path.mime_type, + }); + if (`err` in tb_media_image_create) + throw_err(tb_media_image_create.err); + const tb_nostr_profile_update = + await db.nostr_profile_update({ + filter: { public_key }, + fields: { + picture: `${upload_res_base}/${upload_res_path}.${upload_file_path.mime_type}`, + }, + }); + if (`err` in tb_nostr_profile_update) + throw_err(tb_nostr_profile_update.err); + await route(`/`); + } catch (e) { + await handle_err(e, `lc_handle_back`); + } finally { + loading_photo_upload = false; } - await route(`/profile/edit`, [ - [`key_nostr`, $ndk_user.pubkey], - [`field`, field], - ]); - } catch (e) { - await handle_err(e, `lc_handle_edit_profile_field`); - } - }, - lc_handle_photo_add: async () => { - try { - loading_photo_upload = true; - const photo_paths_open = await gui.open_photos(); - if (!photo_paths_open) return; - const photo_path_add = photo_paths_open.results[0]; - if (photo_path_add) return photo_path_add; - } catch (e) { - await handle_err(e, `lc_handle_photo_add`); - } finally { - loading_photo_upload = false; - } - }, - lc_handle_photo_options: async () => { - try { - } catch (e) { - await handle_err(e, `lc_handle_photo_options`); - } - }, - lc_fs_read_bin: async (file_path) => { - try { - return await fs.read_bin(file_path); - } catch (e) { - await handle_err(e, `lc_fs_read_bin`); - } - }, - }} -/> + }, + lc_handle_edit_profile_field: async ({ field }) => { + try { + if (field === `name`) { + const confirm = await gui.confirm({ + message: `${$ls(`notification.profile.update_name_confirmation`)}. ${$ls(`common.do_you_want_to_continue_q`)}`, + ok: `${$ls(`common.continue`)}`, + cancel: `${$ls(`common.cancel`)}`, + }); + if (!confirm) return; + } + await route(`/profile/edit`, [ + [`key_nostr`, $ndk_user?.pubkey], + [`field`, field], + ]); + } catch (e) { + await handle_err(e, `lc_handle_edit_profile_field`); + } + }, + lc_handle_photo_add: async () => { + try { + loading_photo_upload_open = true; + const photo_paths_open = await gui.open_photos(); + if (!photo_paths_open) return; + const photo_path_add = photo_paths_open.results[0]; + if (photo_path_add) return photo_path_add; + } catch (e) { + await handle_err(e, `lc_handle_photo_add`); + } finally { + loading_photo_upload_open = false; + } + }, + lc_handle_photo_options: async () => { + try { + } catch (e) { + await handle_err(e, `lc_handle_photo_options`); + } + }, + lc_fs_read_bin: async (file_path) => { + try { + return await fs.read_bin(file_path); + } catch (e) { + await handle_err(e, `lc_fs_read_bin`); + } + }, + }} + /> +{/if} diff --git a/app/src/routes/(app)/profile/edit/+page.svelte b/app/src/routes/(app)/profile/edit/+page.svelte @@ -1,14 +1,103 @@ <script lang="ts"> - import { goto } from "$app/navigation"; + import { ls } from "$lib/locale/i18n"; + import { db, gui, route } from "$lib/util"; + import { nostr_sync_metadata } from "$lib/util/nostr/sync"; + import { + handle_err, + type IViewProfileEditData, + parse_view_profile_field_key, + ProfileEdit, + qp_field, + qp_keynostr, + } from "@radroots/lib-app"; + import { throw_err } from "@radroots/util"; + import { onMount } from "svelte"; + + type LoadData = IViewProfileEditData | undefined; + let data: LoadData = $state(undefined); + + let val_field_init = $state(``); + let val_field = $state(``); + + onMount(async () => { + try { + data = await load(); + if (!data) return void (await route(`/`)); //@todo + } catch (e) { + handle_err(e, `on_mount`); + } + }); + + const load = async (): Promise<LoadData> => { + try { + const field = parse_view_profile_field_key($qp_field); + if (!field || !$qp_keynostr) return void (await route(`/`)); + const tb_nostr_profile = await db.nostr_profile_read({ + public_key: $qp_keynostr, + }); + console.log( + JSON.stringify(tb_nostr_profile, null, 4), + `tb_nostr_profile`, + ); + if (`err` in tb_nostr_profile) return void (await route(`/`)); + if (field in tb_nostr_profile.result) + val_field = tb_nostr_profile.result[field] || ``; + val_field_init = val_field; + return { + public_key: tb_nostr_profile.result.public_key, + field, + } satisfies IViewProfileEditData; + } catch (e) { + await handle_err(e, `load`); + } + }; </script> -<div class={`flex flex-col w-full pt-32 justify-center items-center`}> - <button - class={`flex flex-row justify-center items-center`} - onclick={async () => { - await goto(`/`); +{#if data} + <ProfileEdit + bind:val_field + {ls} + basis={{ + data, + lc_handle_back: async ({ field, public_key }) => { + try { + if (val_field_init === val_field) + return void (await route(`/profile`)); + const confirm = await gui.confirm({ + message: `${$ls(`notify.profile.name_update`)}. ${$ls(`common.do_you_want_to_continue_q`)}`, + }); + if (!confirm) return; + const nostr_profile_update = await db.nostr_profile_update({ + filter: { + public_key, + }, + fields: { + [field]: val_field, + }, + }); + if (`err` in nostr_profile_update) + throw_err(nostr_profile_update.err); + const tb_nostr_profile = await db.nostr_profile_read({ + public_key, + }); + console.log( + JSON.stringify(tb_nostr_profile, null, 4), + `tb_nostr_profile`, + ); + if (`err` in tb_nostr_profile) + throw_err(tb_nostr_profile.err); + nostr_sync_metadata(); // no await + await route(`/profile`); + } catch (e) { + await handle_err(e, `lc_handle_back`); + } + }, + lc_handle_input: async () => { + try { + } catch (e) { + await handle_err(e, `lc_handle_input`); + } + }, }} - > - profile edit - </button> -</div> + /> +{/if} diff --git a/app/src/routes/(cfg)/init/+page.svelte b/app/src/routes/(cfg)/init/+page.svelte @@ -2,7 +2,15 @@ import { goto } from "$app/navigation"; import { PUBLIC_NOSTR_RELAY_DEFAULTS } from "$env/static/public"; import { ls } from "$lib/locale/i18n"; - import { datastore, db, gui, keys, nostrkey, radroots } from "$lib/util"; + import { + datastore, + db, + gui, + keys, + nostrkey, + radroots, + route, + } from "$lib/util"; import { cfg_delay } from "$lib/util/conf"; import { app_lo, @@ -24,7 +32,13 @@ LogoCircle, view_effect, } from "@radroots/lib-app"; - import { el_id, form_fields, sleep, type ResultPass } from "@radroots/util"; + import { + el_id, + form_fields, + sleep, + str_capitalize_words, + type ResultPass, + } from "@radroots/util"; import { onMount } from "svelte"; type IdbKey = `nostr:key:add` | `nostr:profile` | `#key_nostrp`; @@ -346,8 +360,20 @@ kv_keynostrp, await kv.read(`nostr:profile`), ); - if (configuration_result && `pass` in configuration_result) - app_notify.set(`${$ls(`notification.init.on_complete`)}`); + if (configuration_result && `pass` in configuration_result) { + const confirm = await gui.confirm({ + message: `${$ls(`notification.init.on_complete`)}`, + ok: `${$ls(`common.continue`)}`, + cancel: str_capitalize_words( + `${$ls(`common.hide_alerts`)}`, + ), + }); + if (confirm) { + await gui.notify_init(); + app_notify.set(`${$ls(`notification.init.on_first_load`)}`); + } + await route(`/`); + } } catch (e) { await handle_err(e, `submit`); } finally { diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte @@ -1,10 +1,6 @@ <script lang="ts"> - import { gui, route } from "$lib/util"; - import { cfg_delay } from "$lib/util/conf"; import { app_lo, - app_loading, - app_notify, app_th, app_thc, LayoutWindow, @@ -12,7 +8,7 @@ win_h, } from "@radroots/lib-app"; import { parse_color_mode, parse_theme_key } from "@radroots/theme"; - import { cfg_app, sleep } from "@radroots/util"; + import { cfg_app } from "@radroots/util"; import "css-paint-polyfill"; import "../app.css"; import type { LayoutProps } from "./$types"; @@ -31,16 +27,6 @@ if (_win_h > cfg_app.layout.ios1.h) app_lo.set(`ios1`); else app_lo.set(`ios0`); }); - - app_notify.subscribe(async (_app_notify) => { - if (!_app_notify) return; - app_loading.set(true); - await sleep(cfg_delay.notify); - route(`/`); - app_loading.set(false); - await gui.alert(_app_notify); - app_notify.set(``); - }); </script> <LayoutWindow> diff --git a/crates/model/src/tables/location_gcs.rs b/crates/model/src/tables/location_gcs.rs @@ -230,7 +230,7 @@ pub struct ILocationGcsQueryUpdate { } pub type ILocationGcsUpdate = ILocationGcsQueryUpdate; -pub type ILocationGcsUpdateResolve = (); +pub type ILocationGcsUpdateResolve = IResultPass; pub async fn lib_model_location_gcs_update( db: &sqlx::Pool<sqlx::Sqlite>, @@ -244,7 +244,7 @@ pub async fn lib_model_location_gcs_update( .execute(db) .await .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; - Ok(()) + Ok(IResultPass { pass: true }) } pub type ILocationGcsDelete = LocationGcsQueryBindValues; diff --git a/crates/model/src/tables/log_error.rs b/crates/model/src/tables/log_error.rs @@ -202,7 +202,7 @@ pub struct ILogErrorQueryUpdate { } pub type ILogErrorUpdate = ILogErrorQueryUpdate; -pub type ILogErrorUpdateResolve = (); +pub type ILogErrorUpdateResolve = IResultPass; pub async fn lib_model_log_error_update( db: &sqlx::Pool<sqlx::Sqlite>, @@ -216,7 +216,7 @@ pub async fn lib_model_log_error_update( .execute(db) .await .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; - Ok(()) + Ok(IResultPass { pass: true }) } pub type ILogErrorDelete = LogErrorQueryBindValues; diff --git a/crates/model/src/tables/media_image.rs b/crates/model/src/tables/media_image.rs @@ -194,7 +194,7 @@ pub struct IMediaImageQueryUpdate { } pub type IMediaImageUpdate = IMediaImageQueryUpdate; -pub type IMediaImageUpdateResolve = (); +pub type IMediaImageUpdateResolve = IResultPass; pub async fn lib_model_media_image_update( db: &sqlx::Pool<sqlx::Sqlite>, @@ -208,7 +208,7 @@ pub async fn lib_model_media_image_update( .execute(db) .await .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; - Ok(()) + Ok(IResultPass { pass: true }) } pub type IMediaImageDelete = MediaImageQueryBindValues; diff --git a/crates/model/src/tables/nostr_profile.rs b/crates/model/src/tables/nostr_profile.rs @@ -210,7 +210,7 @@ pub struct INostrProfileQueryUpdate { } pub type INostrProfileUpdate = INostrProfileQueryUpdate; -pub type INostrProfileUpdateResolve = (); +pub type INostrProfileUpdateResolve = IResultPass; pub async fn lib_model_nostr_profile_update( db: &sqlx::Pool<sqlx::Sqlite>, @@ -224,7 +224,7 @@ pub async fn lib_model_nostr_profile_update( .execute(db) .await .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; - Ok(()) + Ok(IResultPass { pass: true }) } pub type INostrProfileDelete = NostrProfileQueryBindValues; diff --git a/crates/model/src/tables/nostr_relay.rs b/crates/model/src/tables/nostr_relay.rs @@ -210,7 +210,7 @@ pub struct INostrRelayQueryUpdate { } pub type INostrRelayUpdate = INostrRelayQueryUpdate; -pub type INostrRelayUpdateResolve = (); +pub type INostrRelayUpdateResolve = IResultPass; pub async fn lib_model_nostr_relay_update( db: &sqlx::Pool<sqlx::Sqlite>, @@ -224,7 +224,7 @@ pub async fn lib_model_nostr_relay_update( .execute(db) .await .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; - Ok(()) + Ok(IResultPass { pass: true }) } pub type INostrRelayDelete = NostrRelayQueryBindValues; diff --git a/crates/model/src/tables/trade_product.rs b/crates/model/src/tables/trade_product.rs @@ -236,7 +236,7 @@ pub struct ITradeProductQueryUpdate { } pub type ITradeProductUpdate = ITradeProductQueryUpdate; -pub type ITradeProductUpdateResolve = (); +pub type ITradeProductUpdateResolve = IResultPass; pub async fn lib_model_trade_product_update( db: &sqlx::Pool<sqlx::Sqlite>, @@ -250,7 +250,7 @@ pub async fn lib_model_trade_product_update( .execute(db) .await .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; - Ok(()) + Ok(IResultPass { pass: true }) } pub type ITradeProductDelete = TradeProductQueryBindValues;