add network to account

This commit is contained in:
Ren Amamiya 2023-08-04 15:38:38 +07:00
parent 4c7826bbb3
commit 373a0f0608
13 changed files with 103 additions and 89 deletions

View File

@ -0,0 +1,2 @@
-- Add migration script here
ALTER TABLE accounts ADD network JSON;

View File

@ -89,6 +89,12 @@ fn main() {
sql: include_str!("../migrations/20230725010250_update_default_relays.sql"),
kind: MigrationKind::Up,
},
Migration {
version: 20230804083544,
description: "add network to accounts",
sql: include_str!("../migrations/20230804083544_add_network_to_account.sql"),
kind: MigrationKind::Up,
},
],
)
.build(),

View File

@ -18,7 +18,7 @@ import { WelcomeScreen } from '@app/auth/welcome';
import { ChannelScreen } from '@app/channel';
import { ChatScreen } from '@app/chats';
import { ErrorScreen } from '@app/error';
import { NoteScreen } from '@app/note';
import { EventScreen } from '@app/events';
import { Root } from '@app/root';
import { AccountSettingsScreen } from '@app/settings/account';
import { GeneralSettingsScreen } from '@app/settings/general';
@ -85,7 +85,7 @@ const router = createBrowserRouter([
children: [
{ path: 'space', element: <SpaceScreen /> },
{ path: 'trending', element: <TrendingScreen /> },
{ path: 'note/:id', element: <NoteScreen /> },
{ path: 'events/:id', element: <EventScreen /> },
{ path: 'users/:pubkey', element: <UserScreen /> },
{ path: 'chats/:pubkey', element: <ChatScreen /> },
{ path: 'channel/:id', element: <ChannelScreen /> },

51
src/app/events/index.tsx Normal file
View File

@ -0,0 +1,51 @@
import { useParams } from 'react-router-dom';
import {
NoteActions,
NoteContent,
NoteReplyForm,
NoteStats,
ThreadUser,
} from '@shared/notes';
import { RepliesList } from '@shared/notes/replies/list';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { useAccount } from '@utils/hooks/useAccount';
import { useEvent } from '@utils/hooks/useEvent';
export function EventScreen() {
const { id } = useParams();
const { account } = useAccount();
const { status, data } = useEvent(id);
return (
<div className="mx-auto w-[600px]">
<div className="scrollbar-hide flex h-full w-full flex-col gap-1.5 overflow-y-auto pt-11">
{status === 'loading' ? (
<div className="px-3 py-1.5">
<div className="rounded-xl bg-white/10 px-3 py-3">
<NoteSkeleton />
</div>
</div>
) : (
<div className="h-min w-full px-3 pt-1.5">
<div className="rounded-xl bg-white/10 px-3 pt-3">
<ThreadUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-2">
<NoteContent content={data.content} />
</div>
<div>
<NoteActions id={data.id} pubkey={data.pubkey} noOpenThread={true} />
<NoteStats id={data.id} />
</div>
</div>
</div>
)}
<div className="px-3">
<NoteReplyForm id={id} pubkey={account.pubkey} />
<RepliesList id={id} />
</div>
</div>
</div>
);
}

View File

@ -1,49 +0,0 @@
import { useParams } from 'react-router-dom';
import { useLiveThread } from '@app/space/hooks/useLiveThread';
import { NoteMetadata } from '@shared/notes/metadata';
import { NoteReplyForm } from '@shared/notes/replies/form';
import { RepliesList } from '@shared/notes/replies/list';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { User } from '@shared/user';
import { useAccount } from '@utils/hooks/useAccount';
import { useEvent } from '@utils/hooks/useEvent';
export function NoteScreen() {
const { id } = useParams();
const { account } = useAccount();
const { status, data } = useEvent(id);
useLiveThread(id);
return (
<div className="mx-auto w-[600px]">
<div className="scrollbar-hide flex h-full w-full flex-col gap-1.5 overflow-y-auto pb-20 pt-16">
{status === 'loading' ? (
<div className="px-3 py-1.5">
<div className="shadow-input rounded-md bg-white/10 px-3 py-3">
<NoteSkeleton />
</div>
</div>
) : (
<div className="h-min w-full px-3 py-1.5">
<div className="rounded-md bg-zinc-900 px-5 pt-5">
<User pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3">
<NoteMetadata id={data.event_id || id} />
</div>
</div>
<div className="mt-3 rounded-md bg-zinc-900">
{account && <NoteReplyForm rootID={id} userPubkey={account.pubkey} />}
</div>
</div>
)}
<div className="px-3">
<RepliesList id={id} />
</div>
</div>
</div>
);
}

View File

@ -27,27 +27,39 @@ export function Root() {
const { ndk, relayUrls, fetcher } = useNDK();
const { status, account } = useAccount();
async function getFollows() {
const authors: string[] = [];
async function fetchNetwork() {
const network = new Set<string>();
// fetch user's follows
const user = ndk.getUser({ hexpubkey: account.pubkey });
const follows = await user.follows();
follows.forEach((follow: NDKUser) => {
authors.push(nip19.decode(follow.npub).data as string);
network.add(nip19.decode(follow.npub).data as string);
});
// update follows in db
await updateAccount('follows', authors, account.pubkey);
// update user's follows in db
await updateAccount('follows', [...network]);
return authors;
// fetch network
for (const item of network) {
const user = ndk.getUser({ hexpubkey: item });
const follows = await user.follows();
follows.forEach((follow: NDKUser) => {
network.add(nip19.decode(follow.npub).data as string);
});
}
// update user's network in db
await updateAccount('network', [...network]);
return [...network];
}
async function fetchNotes() {
try {
const follows = await getFollows();
const network = await fetchNetwork();
if (follows.length > 0) {
if (network.length > 0) {
let since: number;
if (totalNotes === 0 || lastLogin === 0) {
since = nHoursAgo(48);
@ -57,7 +69,7 @@ export function Root() {
const events = fetcher.allEventsIterator(
relayUrls,
{ kinds: [1], authors: follows },
{ kinds: [1], authors: network },
{ since: since },
{ skipVerification: true }
);

View File

@ -16,7 +16,7 @@ import { LumeEvent } from '@utils/types';
const ITEM_PER_PAGE = 10;
export function FollowingBlock() {
export function NetworkBlock() {
// subscribe for live update
useNewsfeed();
@ -130,7 +130,7 @@ export function FollowingBlock() {
ref={parentRef}
className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20"
>
<TitleBar title="Your Circle" />
<TitleBar title="Network" />
<div className="h-full">
{status === 'loading' ? (
<div className="px-3 py-1.5">

View File

@ -2,9 +2,9 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react';
import { FeedBlock } from '@app/space/components/blocks/feed';
import { FollowingBlock } from '@app/space/components/blocks/following';
import { HashtagBlock } from '@app/space/components/blocks/hashtag';
import { ImageBlock } from '@app/space/components/blocks/image';
import { NetworkBlock } from '@app/space/components/blocks/network';
import { ThreadBlock } from '@app/space/components/blocks/thread';
import { UserBlock } from '@app/space/components/blocks/user';
import { FeedModal } from '@app/space/components/modals/feed';
@ -53,7 +53,7 @@ export function SpaceScreen() {
return (
<div className="scrollbar-hide flex h-full w-full flex-nowrap divide-x divide-white/5 overflow-x-auto overflow-y-hidden">
<FollowingBlock />
<NetworkBlock />
{status === 'loading' ? (
<div className="flex w-[350px] shrink-0 flex-col">
<div className="flex w-full flex-1 items-center justify-center p-3">

View File

@ -14,8 +14,6 @@ export default class TauriAdapter implements NDKCacheAdapter {
public async query(subscription: NDKSubscription): Promise<void> {
const { filter } = subscription;
// if this filter uses both authors and kinds, then we need to query for each combination of author and kind
// and then combine the results
if (filter.authors && filter.kinds) {
const promises = [];

View File

@ -63,15 +63,12 @@ export async function createAccount(
}
// update account
export async function updateAccount(
column: string,
value: string | string[],
pubkey: string
) {
export async function updateAccount(column: string, value: string | string[]) {
const db = await connect();
return await db.execute(`UPDATE accounts SET ${column} = ? WHERE pubkey = ?;`, [
const account = await getActiveAccount();
return await db.execute(`UPDATE accounts SET ${column} = ? WHERE id = ?;`, [
value,
pubkey,
account.id,
]);
}

View File

@ -18,14 +18,10 @@ export function ComposerModal() {
const { account } = useAccount();
const [toggle, open] = useComposer((state) => [state.toggleModal, state.open]);
const closeModal = () => {
toggle(false);
};
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
return (
<Dialog.Root open={open}>
<Dialog.Root open={open} onOpenChange={toggle}>
<Dialog.Trigger asChild>
<button
type="button"
@ -51,7 +47,7 @@ export function ComposerModal() {
</div>
</div>
<Dialog.Close
onClick={() => closeModal()}
onClick={() => toggle(false)}
className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-zinc-800"
>
<CancelIcon className="h-5 w-5 text-white/50" />

View File

@ -8,14 +8,15 @@ import { Link } from 'react-router-dom';
import { HorizontalDotsIcon } from '@shared/icons';
export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
const nevent = nip19.neventEncode(id as unknown as EventPointer);
const copyID = async () => {
await writeText(nevent);
await writeText(nip19.neventEncode({ id: id, author: pubkey } as EventPointer));
};
const copyLink = async () => {
await writeText('https://nostr.com/' + nevent);
await writeText(
'https://nostr.com/' +
nip19.neventEncode({ id: id, author: pubkey } as EventPointer)
);
};
return (
@ -41,12 +42,12 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
<Popover.Portal>
<Popover.Content className="w-[200px] overflow-hidden rounded-md bg-white/10 backdrop-blur-xl focus:outline-none">
<div className="flex flex-col p-2">
<button
type="button"
<Link
to={`/app/events/${id}`}
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
>
Open as new screen
</button>
</Link>
<button
type="button"
onClick={() => copyLink()}
@ -62,7 +63,6 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
Copy ID
</button>
<Link
type="button"
to={`/app/users/${pubkey}`}
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
>

View File

@ -20,9 +20,10 @@ export interface Account extends NDKUserProfile {
id: number;
npub: string;
pubkey: string;
privkey: string;
follows: string[] | string;
network: string[] | string;
is_active: number;
privkey?: string; // deprecated
}
export interface Profile extends NDKUserProfile {