added event collector

This commit is contained in:
Ren Amamiya 2023-04-10 15:50:38 +07:00
parent 213d7514d2
commit fabc0e6cc2
9 changed files with 232 additions and 57 deletions

View File

@ -0,0 +1,12 @@
-- CreateTable
CREATE TABLE "Chat" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pubkey" TEXT NOT NULL,
"createdAt" INTEGER NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Chat_pubkey_key" ON "Chat"("pubkey");
-- CreateIndex
CREATE INDEX "Chat_pubkey_idx" ON "Chat"("pubkey");

View File

@ -0,0 +1,35 @@
/*
Warnings:
- Added the required column `accountId` to the `Chat` table without a default value. This is not possible if the table is not empty.
- Added the required column `accountId` to the `Channel` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Chat" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pubkey" TEXT NOT NULL,
"createdAt" INTEGER NOT NULL,
"accountId" INTEGER NOT NULL,
CONSTRAINT "Chat_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Chat" ("createdAt", "id", "pubkey") SELECT "createdAt", "id", "pubkey" FROM "Chat";
DROP TABLE "Chat";
ALTER TABLE "new_Chat" RENAME TO "Chat";
CREATE UNIQUE INDEX "Chat_pubkey_key" ON "Chat"("pubkey");
CREATE INDEX "Chat_pubkey_idx" ON "Chat"("pubkey");
CREATE TABLE "new_Channel" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"eventId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"accountId" INTEGER NOT NULL,
CONSTRAINT "Channel_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Channel" ("content", "eventId", "id") SELECT "content", "eventId", "id" FROM "Channel";
DROP TABLE "Channel";
ALTER TABLE "new_Channel" RENAME TO "Channel";
CREATE UNIQUE INDEX "Channel_eventId_key" ON "Channel"("eventId");
CREATE INDEX "Channel_eventId_idx" ON "Channel"("eventId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -21,6 +21,8 @@ model Account {
plebs Pleb[] plebs Pleb[]
messages Message[] messages Message[]
notes Note[] notes Note[]
chats Chat[]
channels Channel[]
@@index([pubkey]) @@index([pubkey])
} }
@ -66,11 +68,25 @@ model Message {
@@index([pubkey, createdAt]) @@index([pubkey, createdAt])
} }
model Chat {
id Int @id @default(autoincrement())
pubkey String @unique
createdAt Int
Account Account @relation(fields: [accountId], references: [id])
accountId Int
@@index([pubkey])
}
model Channel { model Channel {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
eventId String @unique eventId String @unique
content String content String
Account Account @relation(fields: [accountId], references: [id])
accountId Int
@@index([eventId]) @@index([eventId])
} }

View File

@ -81,10 +81,23 @@ struct GetLatestNoteData {
date: i32, date: i32,
} }
#[derive(Deserialize, Type)]
struct CreateChatData {
pubkey: String,
created_at: i32,
account_id: i32,
}
#[derive(Deserialize, Type)]
struct GetChatData {
account_id: i32,
}
#[derive(Deserialize, Type)] #[derive(Deserialize, Type)]
struct CreateChannelData { struct CreateChannelData {
event_id: String, event_id: String,
content: String, content: String,
account_id: i32,
} }
#[tauri::command] #[tauri::command]
@ -225,7 +238,45 @@ async fn count_total_notes(db: DbState<'_>) -> Result<i64, ()> {
#[specta::specta] #[specta::specta]
async fn create_channel(db: DbState<'_>, data: CreateChannelData) -> Result<channel::Data, ()> { async fn create_channel(db: DbState<'_>, data: CreateChannelData) -> Result<channel::Data, ()> {
db.channel() db.channel()
.create(data.event_id, data.content, vec![]) .upsert(
channel::event_id::equals(data.event_id.clone()),
channel::create(
data.event_id,
data.content,
account::id::equals(data.account_id),
vec![],
),
vec![],
)
.exec()
.await
.map_err(|_| ())
}
#[tauri::command]
#[specta::specta]
async fn create_chat(db: DbState<'_>, data: CreateChatData) -> Result<chat::Data, ()> {
db.chat()
.upsert(
chat::pubkey::equals(data.pubkey.clone()),
chat::create(
data.pubkey,
data.created_at,
account::id::equals(data.account_id),
vec![],
),
vec![],
)
.exec()
.await
.map_err(|_| ())
}
#[tauri::command]
#[specta::specta]
async fn get_chats(db: DbState<'_>, data: GetChatData) -> Result<Vec<chat::Data>, ()> {
db.chat()
.find_many(vec![chat::account_id::equals(data.account_id)])
.exec() .exec()
.await .await
.map_err(|_| ()) .map_err(|_| ())
@ -247,7 +298,9 @@ async fn main() {
get_notes, get_notes,
get_latest_notes, get_latest_notes,
get_note_by_id, get_note_by_id,
create_channel create_channel,
create_chat,
get_chats
], ],
"../src/utils/bindings.ts", "../src/utils/bindings.ts",
) )
@ -290,7 +343,9 @@ async fn main() {
get_latest_notes, get_latest_notes,
get_note_by_id, get_note_by_id,
count_total_notes, count_total_notes,
create_channel create_channel,
create_chat,
get_chats
]) ])
.manage(Arc::new(db)) .manage(Arc::new(db))
.run(tauri::generate_context!()) .run(tauri::generate_context!())

View File

@ -4,7 +4,7 @@ const AppActions = dynamic(() => import('@components/appHeader/actions'), {
ssr: false, ssr: false,
}); });
const NoteConnector = dynamic(() => import('@components/note/connector'), { const EventCollector = dynamic(() => import('@components/eventCollector'), {
ssr: false, ssr: false,
}); });
@ -15,7 +15,7 @@ export default function AppHeader() {
<div data-tauri-drag-region className="flex h-full w-full items-center justify-between"> <div data-tauri-drag-region className="flex h-full w-full items-center justify-between">
<div className="flex h-full items-center divide-x divide-zinc-900 px-4 pt-px"></div> <div className="flex h-full items-center divide-x divide-zinc-900 px-4 pt-px"></div>
<div> <div>
<NoteConnector /> <EventCollector />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,23 +1,21 @@
import { ChatListItem } from '@components/chats/chatListItem'; import { ChatListItem } from '@components/chats/chatListItem';
import { ChatModal } from '@components/chats/chatModal'; import { ChatModal } from '@components/chats/chatModal';
import { ImageWithFallback } from '@components/imageWithFallback'; import { ImageWithFallback } from '@components/imageWithFallback';
import { RelayContext } from '@components/relaysProvider';
import { activeAccountAtom } from '@stores/account'; import { activeAccountAtom } from '@stores/account';
import { DEFAULT_AVATAR } from '@stores/constants'; import { DEFAULT_AVATAR } from '@stores/constants';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useContext, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export default function ChatList() { export default function ChatList() {
const [pool, relays]: any = useContext(RelayContext);
const router = useRouter(); const router = useRouter();
const activeAccount: any = useAtomValue(activeAccountAtom); const activeAccount: any = useAtomValue(activeAccountAtom);
const accountProfile = JSON.parse(activeAccount.metadata); const accountProfile = JSON.parse(activeAccount.metadata);
const [list, setList] = useState(new Set()); const [list, setList] = useState([]);
const openSelfChat = () => { const openSelfChat = () => {
router.push({ router.push({
@ -27,26 +25,15 @@ export default function ChatList() {
}; };
useEffect(() => { useEffect(() => {
const unsubscribe = pool.subscribe( const fetchChats = async () => {
[ const { getChats } = await import('@utils/bindings');
{ return await getChats({ account_id: activeAccount.id });
kinds: [4],
'#p': [activeAccount.pubkey],
since: 0,
},
],
relays,
(event: any) => {
if (event.pubkey !== activeAccount.pubkey) {
setList((list) => new Set(list).add(event.pubkey));
}
}
);
return () => {
unsubscribe;
}; };
}, [pool, relays, activeAccount.pubkey]);
fetchChats()
.then((res) => setList(res))
.catch(console.error);
}, [activeAccount.id]);
return ( return (
<div className="flex flex-col gap-px"> <div className="flex flex-col gap-px">
@ -68,8 +55,8 @@ export default function ChatList() {
</h5> </h5>
</div> </div>
</div> </div>
{[...list].map((item: string, index) => ( {list.map((item) => (
<ChatListItem key={index} pubkey={item} /> <ChatListItem key={item.id} pubkey={item.pubkey} />
))} ))}
<ChatModal /> <ChatModal />
</div> </div>

View File

@ -11,7 +11,7 @@ import { appWindow, getCurrent } from '@tauri-apps/api/window';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { useCallback, useContext, useEffect, useRef, useState } from 'react';
export default function NoteConnector() { export default function EventCollector() {
const [pool, relays]: any = useContext(RelayContext); const [pool, relays]: any = useContext(RelayContext);
const setLastLoginAtom = useSetAtom(lastLoginAtom); const setLastLoginAtom = useSetAtom(lastLoginAtom);
@ -24,6 +24,9 @@ export default function NoteConnector() {
const subscribe = useCallback(async () => { const subscribe = useCallback(async () => {
const { createNote } = await import('@utils/bindings'); const { createNote } = await import('@utils/bindings');
const { createChat } = await import('@utils/bindings');
const { createChannel } = await import('@utils/bindings');
const activeAccount = JSON.parse(localStorage.getItem('activeAccount')); const activeAccount = JSON.parse(localStorage.getItem('activeAccount'));
const follows = JSON.parse(localStorage.getItem('activeAccountFollows')); const follows = JSON.parse(localStorage.getItem('activeAccountFollows'));
@ -34,29 +37,47 @@ export default function NoteConnector() {
authors: pubkeyArray(follows), authors: pubkeyArray(follows),
since: dateToUnix(now.current), since: dateToUnix(now.current),
}, },
{
kinds: [4],
'#p': [activeAccount.pubkey],
since: 0,
},
{
kinds: [40],
since: 0,
},
], ],
relays, relays,
(event) => { (event) => {
const parentID = getParentID(event.tags, event.id); if (event.kind === 1) {
// insert event to local database const parentID = getParentID(event.tags, event.id);
createNote({ // insert event to local database
event_id: event.id, createNote({
pubkey: event.pubkey, event_id: event.id,
kind: event.kind, pubkey: event.pubkey,
tags: JSON.stringify(event.tags), kind: event.kind,
content: event.content, tags: JSON.stringify(event.tags),
parent_id: parentID, content: event.content,
parent_comment_id: '', parent_id: parentID,
created_at: event.created_at, parent_comment_id: '',
account_id: activeAccount.id, created_at: event.created_at,
}) account_id: activeAccount.id,
.then(() => })
// notify user reload to get newer note .then(() =>
setHasNewerNote(true) // notify user reload to get newer note
) setHasNewerNote(true)
.catch(console.error); )
}, .catch(console.error);
10000 } else if (event.kind === 4) {
if (event.pubkey !== activeAccount.pubkey) {
createChat({ pubkey: event.pubkey, created_at: event.created_at, account_id: activeAccount.id });
}
} else if (event.kind === 40) {
createChannel({ event_id: event.id, content: event.content, account_id: activeAccount.id });
} else {
console.error;
}
}
); );
}, [pool, relays, setHasNewerNote]); }, [pool, relays, setHasNewerNote]);

View File

@ -1,10 +1,48 @@
import BaseLayout from '@layouts/base'; import BaseLayout from '@layouts/base';
import WithSidebarLayout from '@layouts/withSidebar'; import WithSidebarLayout from '@layouts/withSidebar';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react'; import { RelayContext } from '@components/relaysProvider';
import {
JSXElementConstructor,
ReactElement,
ReactFragment,
ReactPortal,
useContext,
useEffect,
useState,
} from 'react';
export default function Page() { export default function Page() {
return <></>; const [pool, relays]: any = useContext(RelayContext);
const [list, setList] = useState([]);
useEffect(() => {
const unsubscribe = pool.subscribe(
[
{
kinds: [40],
since: 0,
},
],
relays,
(event: any) => {
setList((list) => [event, ...list]);
}
);
return () => {
unsubscribe;
};
}, [pool, relays]);
return (
<div className="h-full w-full overflow-y-auto">
{list.map((channel) => (
<div key={channel.id}>{channel.content}</div>
))}
</div>
);
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(

View File

@ -48,6 +48,14 @@ export function createChannel(data: CreateChannelData) {
return invoke<Channel>('create_channel', { data }); return invoke<Channel>('create_channel', { data });
} }
export function createChat(data: CreateChatData) {
return invoke<Chat>('create_chat', { data });
}
export function getChats(data: GetChatData) {
return invoke<Chat[]>('get_chats', { data });
}
export type CreateNoteData = { export type CreateNoteData = {
event_id: string; event_id: string;
pubkey: string; pubkey: string;
@ -59,10 +67,7 @@ export type CreateNoteData = {
created_at: number; created_at: number;
account_id: number; account_id: number;
}; };
export type CreateChannelData = { event_id: string; content: string };
export type CreatePlebData = { pleb_id: string; pubkey: string; kind: number; metadata: string; account_id: number }; export type CreatePlebData = { pleb_id: string; pubkey: string; kind: number; metadata: string; account_id: number };
export type GetNoteByIdData = { event_id: string };
export type Pleb = { id: number; plebId: string; pubkey: string; kind: number; metadata: string; accountId: number };
export type Note = { export type Note = {
id: number; id: number;
eventId: string; eventId: string;
@ -75,10 +80,16 @@ export type Note = {
createdAt: number; createdAt: number;
accountId: number; accountId: number;
}; };
export type CreateChatData = { pubkey: string; created_at: number; account_id: number };
export type GetNoteByIdData = { event_id: string };
export type Chat = { id: number; pubkey: string; createdAt: number; accountId: number };
export type Account = { id: number; pubkey: string; privkey: string; active: boolean; metadata: string }; export type Account = { id: number; pubkey: string; privkey: string; active: boolean; metadata: string };
export type Channel = { id: number; eventId: string; content: string }; export type GetChatData = { account_id: number };
export type CreateChannelData = { event_id: string; content: string; account_id: number };
export type GetPlebPubkeyData = { pubkey: string }; export type GetPlebPubkeyData = { pubkey: string };
export type Channel = { id: number; eventId: string; content: string; accountId: number };
export type GetPlebData = { account_id: number }; export type GetPlebData = { account_id: number };
export type CreateAccountData = { pubkey: string; privkey: string; metadata: string }; export type CreateAccountData = { pubkey: string; privkey: string; metadata: string };
export type GetLatestNoteData = { date: number }; export type GetLatestNoteData = { date: number };
export type Pleb = { id: number; plebId: string; pubkey: string; kind: number; metadata: string; accountId: number };
export type GetNoteData = { date: number; limit: number; offset: number }; export type GetNoteData = { date: number; limit: number; offset: number };