completed migrate to prisma-rust-client

This commit is contained in:
Ren Amamiya 2023-04-05 09:20:33 +07:00
parent 3f87d510ab
commit fc8dc8fd0d
25 changed files with 342 additions and 213 deletions

View File

@ -27,7 +27,7 @@ CREATE TABLE "Note" (
"content" TEXT NOT NULL,
"parent_id" TEXT NOT NULL,
"parent_comment_id" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" INTEGER NOT NULL,
"accountId" INTEGER NOT NULL,
CONSTRAINT "Note_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
@ -38,7 +38,7 @@ CREATE TABLE "Message" (
"pubkey" TEXT NOT NULL,
"content" TEXT NOT NULL,
"tags" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" INTEGER NOT NULL,
"accountId" INTEGER NOT NULL,
CONSTRAINT "Message_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);

View File

@ -0,0 +1,11 @@
-- DropIndex
DROP INDEX "Message_pubkey_idx";
-- DropIndex
DROP INDEX "Note_eventId_idx";
-- CreateIndex
CREATE INDEX "Message_pubkey_createdAt_idx" ON "Message"("pubkey", "createdAt");
-- CreateIndex
CREATE INDEX "Note_eventId_createdAt_idx" ON "Note"("eventId", "createdAt");

View File

@ -36,33 +36,33 @@ model Follow {
}
model Note {
id Int @id @default(autoincrement())
eventId String @unique
id Int @id @default(autoincrement())
eventId String @unique
pubkey String
kind Int
tags String
content String
parent_id String
parent_comment_id String
createdAt DateTime @default(now())
createdAt Int
Account Account @relation(fields: [accountId], references: [id])
accountId Int
@@index([eventId])
@@index([eventId, createdAt])
}
model Message {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
pubkey String
content String
tags String
createdAt DateTime @default(now())
createdAt Int
Account Account @relation(fields: [accountId], references: [id])
accountId Int
@@index([pubkey])
@@index([pubkey, createdAt])
}
model Relay {

View File

@ -7,7 +7,7 @@
#[macro_use]
extern crate objc;
use prisma_client_rust::raw;
use prisma_client_rust::Direction;
use tauri::{Manager, WindowEvent};
#[cfg(target_os = "macos")]
use window_ext::WindowExt;
@ -54,12 +54,30 @@ struct CreateNoteData {
content: String,
parent_id: String,
parent_comment_id: String,
created_at: i32,
account_id: i32,
}
#[derive(Deserialize, Type)]
struct GetNoteByIdData {
event_id: String,
}
#[derive(Deserialize, Type)]
struct GetNoteData {
date: i32,
limit: i32,
offset: i32,
}
#[derive(Deserialize, Type)]
struct GetLatestNoteData {
date: i32,
}
#[tauri::command]
#[specta::specta]
async fn get_account(db: DbState<'_>) -> Result<Vec<account::Data>, ()> {
async fn get_accounts(db: DbState<'_>) -> Result<Vec<account::Data>, ()> {
db.account()
.find_many(vec![account::active::equals(false)])
.exec()
@ -120,6 +138,7 @@ async fn create_note(db: DbState<'_>, data: CreateNoteData) -> Result<note::Data
data.content,
data.parent_id,
data.parent_comment_id,
data.created_at,
account::id::equals(data.account_id),
vec![],
),
@ -132,8 +151,12 @@ async fn create_note(db: DbState<'_>, data: CreateNoteData) -> Result<note::Data
#[tauri::command]
#[specta::specta]
async fn get_notes(db: DbState<'_>) -> Result<Vec<note::Data>, ()> {
db._query_raw(raw!("SELECT * FROM Note"))
async fn get_notes(db: DbState<'_>, data: GetNoteData) -> Result<Vec<note::Data>, ()> {
db.note()
.find_many(vec![note::created_at::lte(data.date)])
.order_by(note::created_at::order(Direction::Desc))
.take(data.limit.into())
.skip(data.offset.into())
.exec()
.await
.map_err(|_| ())
@ -141,15 +164,30 @@ async fn get_notes(db: DbState<'_>) -> Result<Vec<note::Data>, ()> {
#[tauri::command]
#[specta::specta]
async fn check_note(db: DbState<'_>) -> Result<Vec<note::Data>, ()> {
async fn get_latest_notes(db: DbState<'_>, data: GetLatestNoteData) -> Result<Vec<note::Data>, ()> {
db.note()
.find_many(vec![])
.take(5)
.find_many(vec![note::created_at::gt(data.date)])
.order_by(note::created_at::order(Direction::Desc))
.exec()
.await
.map_err(|_| ())
}
#[tauri::command]
#[specta::specta]
async fn get_note_by_id(db: DbState<'_>, data: GetNoteByIdData) -> Result<Option<note::Data>, ()> {
db.note()
.find_unique(note::event_id::equals(data.event_id))
.exec()
.await
.map_err(|_| ())
}
#[tauri::command]
async fn count_total_notes(db: DbState<'_>) -> Result<i64, ()> {
db.note().count(vec![]).exec().await.map_err(|_| ())
}
#[tokio::main]
async fn main() {
let db = PrismaClient::_builder().build().await.unwrap();
@ -157,13 +195,14 @@ async fn main() {
#[cfg(debug_assertions)]
ts::export(
collect_types![
get_account,
get_accounts,
create_account,
get_follows,
create_follow,
create_note,
get_notes,
check_note
get_latest_notes,
get_note_by_id
],
"../src/utils/bindings.ts",
)
@ -196,13 +235,15 @@ async fn main() {
}
})
.invoke_handler(tauri::generate_handler![
get_account,
get_accounts,
create_account,
get_follows,
create_follow,
create_note,
get_notes,
check_note
get_latest_notes,
get_note_by_id,
count_total_notes
])
.manage(Arc::new(db))
.run(tauri::generate_context!())

View File

@ -2,8 +2,7 @@ import { RelayContext } from '@components/relaysProvider';
import { DEFAULT_AVATAR } from '@stores/constants';
import { createFollows } from '@utils/storage';
import { tagsToArray } from '@utils/transform';
import { fetchMetadata } from '@utils/metadata';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { AvatarIcon, ExitIcon, GearIcon } from '@radix-ui/react-icons';
@ -11,7 +10,7 @@ import { writeText } from '@tauri-apps/api/clipboard';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { nip19 } from 'nostr-tools';
import { memo, useContext, useEffect } from 'react';
import { memo, useCallback, useContext, useEffect } from 'react';
export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }) {
const [pool, relays]: any = useContext(RelayContext);
@ -27,6 +26,21 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }
await writeText(nip19.npubEncode(user.id));
};
const insertFollowsToStorage = useCallback(
async (tags) => {
const { createFollow } = await import('@utils/bindings');
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
for (const tag of tags) {
const metadata: any = await fetchMetadata(tag[1], pool, relays);
createFollow({ pubkey: tag[1], kind: 0, metadata: metadata.content, account_id: activeAccount.id }).catch(
console.error
);
}
},
[pool, relays]
);
useEffect(() => {
const unsubscribe = pool.subscribe(
[
@ -38,7 +52,7 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }
relays,
(event: any) => {
if (event.tags.length > 0) {
createFollows(tagsToArray(event.tags), user.id, 0);
insertFollowsToStorage(event.tags);
}
},
undefined,
@ -51,7 +65,7 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }
return () => {
unsubscribe;
};
}, [pool, relays, user.id]);
}, [insertFollowsToStorage, pool, relays, user.id]);
return (
<DropdownMenu.Root>

View File

@ -1,41 +1,37 @@
import { ActiveAccount } from '@components/multiAccounts/activeAccount';
import { InactiveAccount } from '@components/multiAccounts/inactiveAccount';
import { activeAccountAtom } from '@stores/account';
import { APP_VERSION } from '@stores/constants';
import { getAccounts } from '@utils/storage';
import LumeSymbol from '@assets/icons/Lume';
import { PlusIcon } from '@radix-ui/react-icons';
import { useAtomValue } from 'jotai';
import Link from 'next/link';
import { useCallback, useEffect, useState } from 'react';
export default function MultiAccounts() {
const activeAccount: any = useAtomValue(activeAccountAtom);
const [users, setUsers] = useState([]);
const renderAccount = useCallback(
(user: { id: string }) => {
if (user.id === activeAccount.id) {
return <ActiveAccount key={user.id} user={user} />;
} else {
return <InactiveAccount key={user.id} user={user} />;
}
},
[activeAccount.id]
);
const renderAccount = useCallback((user: { id: string }) => {
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
if (user.id === activeAccount.id) {
return <ActiveAccount key={user.id} user={user} />;
} else {
return <InactiveAccount key={user.id} user={user} />;
}
}, []);
const fetchAccounts = useCallback(async () => {
const { getAccounts } = await import('@utils/bindings');
const accounts = await getAccounts();
// update state
setUsers(accounts);
}, []);
useEffect(() => {
const fetchAccount = async () => {
const result: any = await getAccounts();
setUsers(result);
};
fetchAccount().catch(console.error);
}, []);
fetchAccounts().catch(console.error);
}, [fetchAccounts]);
return (
<div className="flex h-full flex-col items-center justify-between px-2 pb-4 pt-3">

View File

@ -63,13 +63,13 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
const getParent = useMemo(() => {
if (event.parent_id) {
if (event.parent_id !== event.id && !event.content.includes('#[0]')) {
if (event.parent_id !== event.eventId && !event.content.includes('#[0]')) {
return <NoteParent id={event.parent_id} />;
}
}
return;
}, [event.content, event.id, event.parent_id]);
}, [event.content, event.eventId, event.parent_id]);
const openThread = (e) => {
const selection = window.getSelection();
@ -87,7 +87,7 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
>
<>{getParent}</>
<div className="relative z-10 flex flex-col">
<UserExtend pubkey={event.pubkey} time={event.created_at} />
<UserExtend pubkey={event.pubkey} time={event.createdAt || event.created_at} />
<div className="-mt-5 pl-[52px]">
<div className="flex flex-col gap-2">
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">
@ -97,10 +97,10 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
</div>
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
<NoteMetadata
eventID={event.id}
eventID={event.eventId}
eventPubkey={event.pubkey}
eventContent={event.content}
eventTime={event.created_at}
eventTime={event.createdAt || event.created_at}
/>
</div>
</div>

View File

@ -60,7 +60,7 @@ export const NoteComment = memo(function NoteComment({ event }: { event: any })
return (
<div className="relative z-10 flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 px-3 py-5 hover:bg-black/20">
<div className="relative z-10 flex flex-col">
<UserExtend pubkey={event.pubkey} time={event.created_at} />
<UserExtend pubkey={event.pubkey} time={event.createdAt || event.created_at} />
<div className="-mt-5 pl-[52px]">
<div className="flex flex-col gap-2">
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">
@ -70,10 +70,10 @@ export const NoteComment = memo(function NoteComment({ event }: { event: any })
</div>
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
<NoteMetadata
eventID={event.id}
eventID={event.eventId}
eventPubkey={event.pubkey}
eventContent={event.content}
eventTime={event.created_at}
eventTime={event.createdAt || event.created_at}
/>
</div>
</div>

View File

@ -1,15 +1,14 @@
import { RelayContext } from '@components/relaysProvider';
import { activeAccountAtom, lastLoginAtom } from '@stores/account';
import { lastLoginAtom } from '@stores/account';
import { hasNewerNoteAtom } from '@stores/note';
import { dateToUnix } from '@utils/getDate';
import { createCacheNote, getAllFollowsByID } from '@utils/storage';
import { pubkeyArray } from '@utils/transform';
import { getParentID, pubkeyArray } from '@utils/transform';
import { TauriEvent } from '@tauri-apps/api/event';
import { appWindow, getCurrent } from '@tauri-apps/api/window';
import { useAtomValue, useSetAtom } from 'jotai';
import { useSetAtom } from 'jotai';
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
export default function NoteConnector() {
@ -17,30 +16,45 @@ export default function NoteConnector() {
const setLastLoginAtom = useSetAtom(lastLoginAtom);
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
const activeAccount: any = useAtomValue(activeAccountAtom);
const [isOnline] = useState(true);
const now = useRef(new Date());
const subscribe = useCallback(() => {
getAllFollowsByID(activeAccount.id).then((follows) => {
pool.subscribe(
[
{
kinds: [1],
authors: pubkeyArray(follows),
since: dateToUnix(now.current),
},
],
relays,
(event: any) => {
// insert event to local database
createCacheNote(event);
setHasNewerNote(true);
}
);
});
}, [activeAccount.id, pool, relays, setHasNewerNote]);
const now = useRef(new Date());
const unsubscribe = useRef(null);
const subscribe = useCallback(async () => {
const { createNote } = await import('@utils/bindings');
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
const follows = JSON.parse(localStorage.getItem('activeAccountFollows'));
unsubscribe.current = pool.subscribe(
[
{
kinds: [1],
authors: pubkeyArray(follows),
since: dateToUnix(now.current),
},
],
relays,
(event) => {
const parentID = getParentID(event.tags, event.id);
// insert event to local database
createNote({
event_id: event.id,
pubkey: event.pubkey,
kind: event.kind,
tags: JSON.stringify(event.tags),
content: event.content,
parent_id: parentID,
parent_comment_id: '',
created_at: event.created_at,
account_id: activeAccount.id,
}).catch(console.error);
// notify user reload to get newer note
setHasNewerNote(true);
}
);
}, [pool, relays, setHasNewerNote]);
useEffect(() => {
subscribe();
@ -48,23 +62,25 @@ export default function NoteConnector() {
setLastLoginAtom(now.current);
appWindow.close();
});
}, [activeAccount.id, pool, relays, setHasNewerNote, subscribe]);
return () => {
unsubscribe.current;
};
}, [setHasNewerNote, setLastLoginAtom, subscribe]);
return (
<>
<div className="inline-flex items-center gap-1 rounded-md px-1.5 py-1 hover:bg-zinc-900">
<span className="relative flex h-1.5 w-1.5">
<span
className={`absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 ${
isOnline ? 'bg-green-400' : 'bg-red-400'
}`}
></span>
<span
className={`relative inline-flex h-1.5 w-1.5 rounded-full ${isOnline ? 'bg-green-400' : 'bg-amber-400'}`}
></span>
</span>
<p className="text-xs font-medium text-zinc-500">{isOnline ? 'Online' : 'Offline'}</p>
</div>
</>
<div className="inline-flex items-center gap-1 rounded-md px-1.5 py-1 hover:bg-zinc-900">
<span className="relative flex h-1.5 w-1.5">
<span
className={`absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 ${
isOnline ? 'bg-green-400' : 'bg-red-400'
}`}
></span>
<span
className={`relative inline-flex h-1.5 w-1.5 rounded-full ${isOnline ? 'bg-green-400' : 'bg-amber-400'}`}
></span>
</span>
<p className="text-xs font-medium text-zinc-500">{isOnline ? 'Online' : 'Offline'}</p>
</div>
);
}

View File

@ -60,7 +60,7 @@ export const NoteExtend = memo(function NoteExtend({ event }: { event: any }) {
return (
<div className="relative z-10 flex h-min min-h-min w-full select-text flex-col">
<div className="relative z-10 flex flex-col">
<UserLarge pubkey={event.pubkey} time={event.created_at} />
<UserLarge pubkey={event.pubkey} time={event.createdAt || event.created_at} />
<div className="mt-2">
<div className="flex flex-col gap-2">
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">
@ -70,10 +70,10 @@ export const NoteExtend = memo(function NoteExtend({ event }: { event: any }) {
</div>
<div className="mt-5 flex items-center border-b border-t border-zinc-800 py-2">
<NoteMetadata
eventID={event.id}
eventID={event.eventId}
eventPubkey={event.pubkey}
eventContent={event.content}
eventTime={event.created_at}
eventTime={event.createdAt || event.created_at}
/>
</div>
</div>

View File

@ -6,7 +6,7 @@ import { RelayContext } from '@components/relaysProvider';
import { UserExtend } from '@components/user/extend';
import { UserMention } from '@components/user/mention';
import { createCacheNote, getNoteByID } from '@utils/storage';
import { getParentID } from '@utils/transform';
import destr from 'destr';
import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
@ -18,7 +18,10 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
const [event, setEvent] = useState(null);
const unsubscribe = useRef(null);
const fetchEvent = useCallback(() => {
const fetchEvent = useCallback(async () => {
const { createNote } = await import('@utils/bindings');
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
unsubscribe.current = pool.subscribe(
[
{
@ -31,7 +34,19 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
// update state
setEvent(event);
// insert to database
createCacheNote(event);
const parentID = getParentID(event.tags, event.id);
// insert event to local database
createNote({
event_id: event.id,
pubkey: event.pubkey,
kind: event.kind,
tags: JSON.stringify(event.tags),
content: event.content,
parent_id: parentID,
parent_comment_id: '',
created_at: event.created_at,
account_id: activeAccount.id,
}).catch(console.error);
},
undefined,
undefined,
@ -41,19 +56,26 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
);
}, [id, pool, relays]);
const checkNoteExist = useCallback(async () => {
const { getNoteById } = await import('@utils/bindings');
getNoteById({ event_id: id })
.then((res) => {
if (res) {
setEvent(res);
} else {
fetchEvent();
}
})
.catch(console.error);
}, [fetchEvent, id]);
useEffect(() => {
getNoteByID(id).then((res) => {
if (res) {
setEvent(res);
} else {
fetchEvent();
}
});
checkNoteExist();
return () => {
unsubscribe.current;
};
}, [fetchEvent, id]);
}, [checkNoteExist]);
const content = useMemo(() => {
let parsedContent = event ? event.content : null;
@ -110,7 +132,7 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
<div className="relative pb-5">
<div className="absolute left-[21px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
<div className="relative z-10 flex flex-col">
<UserExtend pubkey={event.pubkey} time={event.created_at} />
<UserExtend pubkey={event.pubkey} time={event.createdAt || event.created_at} />
<div className="-mt-5 pl-[52px]">
<div className="flex flex-col gap-2">
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">
@ -120,10 +142,10 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
</div>
<div onClick={(e) => e.stopPropagation()} className="mt-5 pl-[52px]">
<NoteMetadata
eventID={event.id}
eventID={event.eventId}
eventPubkey={event.pubkey}
eventContent={event.content}
eventTime={event.created_at}
eventTime={event.createdAt || event.created_at}
/>
</div>
</div>

View File

@ -2,7 +2,7 @@ import { RelayContext } from '@components/relaysProvider';
import { UserExtend } from '@components/user/extend';
import { UserMention } from '@components/user/mention';
import { createCacheNote, getNoteByID } from '@utils/storage';
import { getParentID } from '@utils/transform';
import destr from 'destr';
import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
@ -14,7 +14,10 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
const [event, setEvent] = useState(null);
const unsubscribe = useRef(null);
const fetchEvent = useCallback(() => {
const fetchEvent = useCallback(async () => {
const { createNote } = await import('@utils/bindings');
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
unsubscribe.current = pool.subscribe(
[
{
@ -27,7 +30,19 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
// update state
setEvent(event);
// insert to database
createCacheNote(event);
const parentID = getParentID(event.tags, event.id);
// insert event to local database
createNote({
event_id: event.id,
pubkey: event.pubkey,
kind: event.kind,
tags: JSON.stringify(event.tags),
content: event.content,
parent_id: parentID,
parent_comment_id: '',
created_at: event.created_at,
account_id: activeAccount.id,
}).catch(console.error);
},
undefined,
undefined,
@ -37,19 +52,26 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
);
}, [id, pool, relays]);
const checkNoteExist = useCallback(async () => {
const { getNoteById } = await import('@utils/bindings');
getNoteById({ event_id: id })
.then((res) => {
if (res) {
setEvent(res);
} else {
fetchEvent();
}
})
.catch(console.error);
}, [fetchEvent, id]);
useEffect(() => {
getNoteByID(id).then((res) => {
if (res) {
setEvent(res);
} else {
fetchEvent();
}
});
checkNoteExist();
return () => {
unsubscribe.current;
};
}, [fetchEvent, id]);
}, [checkNoteExist]);
const content = useMemo(() => {
let parsedContent = event ? event.content : null;
@ -89,7 +111,7 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) {
return (
<div className="relative mb-2 mt-3 rounded-lg border border-zinc-700 bg-zinc-800 p-2 py-3">
<div className="relative z-10 flex flex-col">
<UserExtend pubkey={event.pubkey} time={event.created_at} />
<UserExtend pubkey={event.pubkey} time={event.createdAt || event.created_at} />
<div className="-mt-5 pl-[52px]">
<div className="flex flex-col gap-2">
<div className="prose prose-zinc max-w-none break-words text-[15px] leading-tight dark:prose-invert prose-p:m-0 prose-p:text-[15px] prose-p:leading-tight prose-a:font-normal prose-a:text-fuchsia-500 prose-a:no-underline prose-img:m-0 prose-video:m-0">

View File

@ -6,17 +6,17 @@ import { DEFAULT_AVATAR } from '@stores/constants';
import { truncate } from '@utils/truncate';
import { Author } from 'nostr-relaypool';
import { memo, useContext, useEffect, useMemo, useState } from 'react';
import { memo, useContext, useEffect, useState } from 'react';
export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) {
const [pool, relays]: any = useContext(RelayContext);
const [profile, setProfile] = useState(null);
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
useEffect(() => {
const user = new Author(pool, relays, pubkey);
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
}, [user]);
}, [pool, relays, pubkey]);
return (
<div className="flex items-center gap-2">

View File

@ -10,16 +10,15 @@ import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { useRouter } from 'next/router';
import { Author } from 'nostr-relaypool';
import { memo, useContext, useEffect, useMemo, useState } from 'react';
import { memo, useContext, useEffect, useState } from 'react';
dayjs.extend(relativeTime);
export const UserExtend = memo(function UserExtend({ pubkey, time }: { pubkey: string; time: any }) {
const [pool, relays]: any = useContext(RelayContext);
const router = useRouter();
const [pool, relays]: any = useContext(RelayContext);
const [profile, setProfile] = useState(null);
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
const openUserPage = (e) => {
e.stopPropagation();
@ -27,8 +26,9 @@ export const UserExtend = memo(function UserExtend({ pubkey, time }: { pubkey: s
};
useEffect(() => {
const user = new Author(pool, relays, pubkey);
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
}, [user]);
}, [pool, relays, pubkey]);
return (
<div className="group flex items-start gap-2">

View File

@ -1,33 +1,22 @@
import { ImageWithFallback } from '@components/imageWithFallback';
import { RelayContext } from '@components/relaysProvider';
import { DEFAULT_AVATAR } from '@stores/constants';
import { createCacheProfile } from '@utils/storage';
import { truncate } from '@utils/truncate';
import { fetch } from '@tauri-apps/api/http';
import destr from 'destr';
import { memo, useCallback, useEffect, useState } from 'react';
import { Author } from 'nostr-relaypool';
import { memo, useContext, useEffect, useMemo, useState } from 'react';
export const UserFollow = memo(function UserFollow({ pubkey }: { pubkey: string }) {
const [profile, setProfile] = useState(null);
const [pool, relays]: any = useContext(RelayContext);
const fetchProfile = useCallback(async (id: string) => {
const res = await fetch(`https://rbr.bio/${id}/metadata.json`, {
method: 'GET',
timeout: 30,
});
return res.data;
}, []);
const [profile, setProfile] = useState(null);
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
useEffect(() => {
fetchProfile(pubkey)
.then((res: any) => {
setProfile(destr(res.content));
createCacheProfile(res.pubkey, res.content);
})
.catch(console.error);
}, [fetchProfile, pubkey]);
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
}, [user]);
return (
<div className="flex items-center gap-2">

View File

@ -9,19 +9,18 @@ import { DotsHorizontalIcon } from '@radix-ui/react-icons';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { Author } from 'nostr-relaypool';
import { memo, useContext, useEffect, useMemo, useState } from 'react';
import { memo, useContext, useEffect, useState } from 'react';
dayjs.extend(relativeTime);
export const UserLarge = memo(function UserLarge({ pubkey, time }: { pubkey: string; time: any }) {
const [pool, relays]: any = useContext(RelayContext);
const [profile, setProfile] = useState(null);
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
useEffect(() => {
const user = new Author(pool, relays, pubkey);
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
}, [user]);
}, [pool, relays, pubkey]);
return (
<div className="flex items-center gap-2">

View File

@ -3,17 +3,16 @@ import { RelayContext } from '@components/relaysProvider';
import { truncate } from '@utils/truncate';
import { Author } from 'nostr-relaypool';
import { memo, useContext, useEffect, useMemo, useState } from 'react';
import { memo, useContext, useEffect, useState } from 'react';
export const UserMention = memo(function UserMention({ pubkey }: { pubkey: string }) {
const [pool, relays]: any = useContext(RelayContext);
const [profile, setProfile] = useState(null);
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
useEffect(() => {
const user = new Author(pool, relays, pubkey);
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
}, [user]);
}, [pool, relays, pubkey]);
return <span className="cursor-pointer text-fuchsia-500">@{profile?.name || truncate(pubkey, 16, ' .... ')}</span>;
});

View File

@ -6,17 +6,16 @@ import { DEFAULT_AVATAR } from '@stores/constants';
import { truncate } from '@utils/truncate';
import { Author } from 'nostr-relaypool';
import { useContext, useEffect, useMemo, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
export const UserMini = ({ pubkey }: { pubkey: string }) => {
const [pool, relays]: any = useContext(RelayContext);
const [profile, setProfile] = useState(null);
const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]);
useEffect(() => {
const user = new Author(pool, relays, pubkey);
user.metaData((res) => setProfile(JSON.parse(res.content)), 0);
}, [user]);
}, [pool, relays, pubkey]);
if (profile) {
return (

View File

@ -14,8 +14,8 @@ export default function Page() {
const setActiveAccountFollows = useSetAtom(activeAccountFollowsAtom);
const fetchActiveAccount = useCallback(async () => {
const { getAccount } = await import('@utils/bindings');
return await getAccount();
const { getAccounts } = await import('@utils/bindings');
return await getAccounts();
}, []);
const fetchFollowsByAccount = useCallback(async (id) => {

View File

@ -2,14 +2,12 @@ import BaseLayout from '@layouts/base';
import { RelayContext } from '@components/relaysProvider';
import { activeAccountAtom, activeAccountFollowsAtom, lastLoginAtom } from '@stores/account';
import { dateToUnix, hoursAgo } from '@utils/getDate';
import { getParentID, pubkeyArray } from '@utils/transform';
import LumeSymbol from '@assets/icons/Lume';
import { useAtomValue } from 'jotai';
import { invoke } from '@tauri-apps/api/tauri';
import { useRouter } from 'next/router';
import {
JSXElementConstructor,
@ -27,23 +25,22 @@ export default function Page() {
const router = useRouter();
const [pool, relays]: any = useContext(RelayContext);
const activeAccount: any = useAtomValue(activeAccountAtom);
const activeAccountFollows: any = useAtomValue(activeAccountFollowsAtom);
const lastLogin: any = useAtomValue(lastLoginAtom);
const now = useRef(new Date());
const unsubscribe = useRef(null);
const [eose, setEose] = useState(false);
const fetchData = useCallback(
async (since) => {
async (since: Date) => {
const { createNote } = await import('@utils/bindings');
const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
const follows = JSON.parse(localStorage.getItem('activeAccountFollows'));
unsubscribe.current = pool.subscribe(
[
{
kinds: [1],
authors: pubkeyArray(activeAccountFollows),
authors: pubkeyArray(follows),
since: dateToUnix(since),
until: dateToUnix(now.current),
},
@ -59,7 +56,8 @@ export default function Page() {
tags: JSON.stringify(event.tags),
content: event.content,
parent_id: parentID,
parent_comment_id: 'aaa',
parent_comment_id: '',
created_at: event.created_at,
account_id: activeAccount.id,
}).catch(console.error);
},
@ -69,22 +67,20 @@ export default function Page() {
}
);
},
[activeAccount.id, activeAccountFollows, pool, relays]
[pool, relays]
);
const isNoteExist = useCallback(async () => {
const { checkNote } = await import('@utils/bindings');
checkNote()
.then((res) => {
if (res.length === 5) {
const parseDate = new Date(lastLogin);
fetchData(parseDate);
} else {
fetchData(hoursAgo(24, now.current));
}
})
.catch(console.error);
}, [fetchData, lastLogin]);
invoke('count_total_notes').then((res: number) => {
if (res > 0) {
const lastLogin = JSON.parse(localStorage.getItem('lastLogin'));
const parseDate = new Date(lastLogin);
fetchData(parseDate);
} else {
fetchData(hoursAgo(24, now.current));
}
});
}, [fetchData]);
useEffect(() => {
if (eose === false) {

View File

@ -8,7 +8,7 @@ import { Placeholder } from '@components/note/placeholder';
import { hasNewerNoteAtom } from '@stores/note';
import { dateToUnix } from '@utils/getDate';
import { getLatestNotes, getNotes } from '@utils/storage';
import { filteredData } from '@utils/transform';
import { ArrowUpIcon } from '@radix-ui/react-icons';
import { useAtom } from 'jotai';
@ -48,23 +48,36 @@ export default function Page() {
);
const initialData = useCallback(async () => {
const result: any = await getNotes(dateToUnix(now.current), limit.current, offset.current);
setData((data) => [...data, ...result]);
const { getNotes } = await import('@utils/bindings');
const result: any = await getNotes({
date: dateToUnix(now.current),
limit: limit.current,
offset: offset.current,
});
const filteredResult = filteredData(result);
setData((data) => [...data, ...filteredResult]);
}, []);
const loadMore = useCallback(async () => {
const { getNotes } = await import('@utils/bindings');
offset.current += limit.current;
// next query
const result: any = await getNotes(dateToUnix(now.current), limit.current, offset.current);
setData((data) => [...data, ...result]);
const result: any = await getNotes({
date: dateToUnix(now.current),
limit: limit.current,
offset: offset.current,
});
const filteredResult = filteredData(result);
setData((data) => [...data, ...filteredResult]);
}, []);
const loadLatest = useCallback(async () => {
offset.current += limit.current;
const { getLatestNotes } = await import('@utils/bindings');
// next query
const result: any = await getLatestNotes(dateToUnix(now.current));
const result: any = await getLatestNotes({ date: dateToUnix(now.current) });
// update data
setData((data) => [...result, ...data]);
const filteredResult = filteredData(result);
setData((data) => [...data, ...filteredResult]);
// hide newer trigger
setHasNewerNote(false);
// scroll to top

View File

@ -11,4 +11,4 @@ const createMyJsonStorage = () => {
export const activeAccountAtom = atomWithStorage('activeAccount', {}, createMyJsonStorage());
export const activeAccountFollowsAtom = atomWithStorage('activeAccountFollows', [], createMyJsonStorage());
export const lastLoginAtom = atomWithStorage('lastLoginAtom', [], createMyJsonStorage());
export const lastLoginAtom = atomWithStorage('lastLogin', [], createMyJsonStorage());

View File

@ -1,9 +0,0 @@
import { isSSR } from '@utils/ssr';
import { getAllRelays } from '@utils/storage';
import { atomWithCache } from 'jotai-cache';
export const relaysAtom = atomWithCache(async () => {
const response = isSSR ? [] : await getAllRelays();
return response;
});

View File

@ -8,8 +8,8 @@ declare global {
const invoke = window.__TAURI_INVOKE__;
export function getAccount() {
return invoke<Account[]>('get_account');
export function getAccounts() {
return invoke<Account[]>('get_accounts');
}
export function createAccount(data: CreateAccountData) {
@ -28,15 +28,20 @@ export function createNote(data: CreateNoteData) {
return invoke<Note>('create_note', { data });
}
export function getNotes() {
return invoke<Note[]>('get_notes');
export function getNotes(data: GetNoteData) {
return invoke<Note[]>('get_notes', { data });
}
export function checkNote() {
return invoke<Note[]>('check_note');
export function getLatestNotes(data: GetLatestNoteData) {
return invoke<Note[]>('get_latest_notes', { data });
}
export function getNoteById(data: GetNoteByIdData) {
return invoke<Note | null>('get_note_by_id', { data });
}
export type GetFollowData = { account_id: number };
export type GetNoteByIdData = { event_id: string };
export type Note = {
id: number;
eventId: string;
@ -46,9 +51,10 @@ export type Note = {
content: string;
parent_id: string;
parent_comment_id: string;
createdAt: string;
createdAt: number;
accountId: number;
};
export type GetNoteData = { date: number; limit: number; offset: number };
export type CreateFollowData = { pubkey: string; kind: number; metadata: string; account_id: number };
export type Account = { id: number; pubkey: string; privkey: string; active: boolean; metadata: string };
export type CreateNoteData = {
@ -59,7 +65,9 @@ export type CreateNoteData = {
content: string;
parent_id: string;
parent_comment_id: string;
created_at: number;
account_id: number;
};
export type GetLatestNoteData = { date: number };
export type CreateAccountData = { pubkey: string; privkey: string; metadata: string };
export type Follow = { id: number; pubkey: string; kind: number; metadata: string; accountId: number };

View File

@ -45,3 +45,16 @@ export const getParentID = (arr, fallback) => {
return parentID;
};
export const filteredData = (obj) => {
const filteredArr = obj.reduce((item, current) => {
const x = item.find((item) => item.parent_id === current.parent_id);
if (!x) {
return item.concat([current]);
} else {
return item;
}
}, []);
return filteredArr;
};