wip: convert tauri sql to prisma

This commit is contained in:
Ren Amamiya 2023-04-02 16:22:50 +07:00
parent 9b8a96c651
commit 39e7c9bf34
11 changed files with 228 additions and 162 deletions

View File

@ -3,4 +3,5 @@
/target/
# prisma
src/prisma.rs
src/db.rs
lume.db

1
src-tauri/Cargo.lock generated
View File

@ -2193,6 +2193,7 @@ dependencies = [
"tauri",
"tauri-build",
"tauri-specta",
"tokio",
]
[[package]]

View File

@ -21,6 +21,7 @@ prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.7", default-features = false, features = ["sqlite", "migrations", "mocking", "specta"] }
specta = "1.0.0"
tauri-specta = { version = "1.0.0", features = ["typescript"] }
tokio = { version = "1.26.0", features = ["macros"] }
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.7"

View File

@ -1,104 +0,0 @@
-- Add migration script here
-- create relays
CREATE TABLE
relays (
id INTEGER PRIMARY KEY,
relay_url TEXT NOT NULL,
relay_status INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- add default relays
-- relay status:
-- 0: off
-- 1: on
INSERT INTO
relays (relay_url, relay_status)
VALUES
("wss://relay.damus.io", "1"),
("wss://eden.nostr.land", "0"),
("wss://nostr-pub.wellorder.net", "1"),
("wss://nostr.bongbong.com", "1"),
("wss://nostr.zebedee.cloud", "1"),
("wss://nostr.fmt.wiz.biz", "1"),
("wss://nostr.walletofsatoshi.com", "0"),
("wss://relay.snort.social", "1"),
("wss://offchain.pub", "1"),
("wss://brb.io", "0"),
("wss://relay.current.fyi", "1"),
("wss://nostr.relayer.se", "0"),
("wss://nostr.bitcoiner.social", "1"),
("wss://relay.nostr.info", "1"),
("wss://relay.zeh.app", "0"),
("wss://nostr-01.dorafactory.org", "1"),
("wss://nostr.zhongwen.world", "1"),
("wss://nostro.cc", "1"),
("wss://relay.nostr.net.in", "1"),
("wss://nos.lol", "1");
-- create accounts
-- is_active (part of multi-account feature):
-- 0: false
-- 1: true
CREATE TABLE
accounts (
id TEXT PRIMARY KEY,
privkey TEXT NOT NULL,
npub TEXT NOT NULL,
nsec TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 0,
metadata TEXT
);
-- create follows
-- kind (part of multi-newsfeed feature):
-- 0: direct
-- 1: follow of follow
CREATE TABLE
follows (
id INTEGER PRIMARY KEY,
pubkey TEXT NOT NULL,
account TEXT NOT NULL,
kind INTEGER NOT NULL DEFAULT 0,
metadata TEXT
);
-- create index for pubkey in follows
CREATE UNIQUE INDEX index_pubkey_on_follows ON follows (pubkey);
-- create cache profiles
CREATE TABLE
cache_profiles (
id TEXT PRIMARY KEY,
metadata TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- create cache notes
CREATE TABLE
cache_notes (
id TEXT PRIMARY KEY,
pubkey TEXT NOT NULL,
created_at TEXT,
kind INTEGER NOT NULL DEFAULT 1,
tags TEXT NOT NULL,
content TEXT NOT NULL,
parent_id TEXT,
parent_comment_id TEXT
);
-- create settings
CREATE TABLE
settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
setting_key TEXT NOT NULL,
setting_value TEXT NOT NULL
);
-- add default setting
INSERT INTO
settings (setting_key, setting_value)
VALUES
("last_login", "0");

View File

@ -1,11 +1,75 @@
datasource db {
provider = "sqlite"
url = "file:lume.db"
url = "file:../lume.db"
}
generator client {
// Corresponds to the cargo alias created earlier
provider = "cargo prisma"
provider = "cargo prisma"
// The location to generate the client. Is relative to the position of the schema
output = "../src/prisma.rs"
output = "../src/db.rs"
module_path = "db"
}
model Account {
id Int @id @default(autoincrement())
pubkey String
privkey String @unique
active Boolean @default(false)
metadata String
// related
follows Follow[]
messages Message[]
notes Note[]
@@index([pubkey])
}
model Follow {
id Int @id @default(autoincrement())
pubkey String
kind Int
metadata String
Account Account @relation(fields: [accountId], references: [id])
accountId Int
}
model Note {
id Int @id @default(autoincrement())
pubkey String
kind Int
tags String
content String
parent_id String
parent_comment_id String
createdAt DateTime @default(now())
Account Account @relation(fields: [accountId], references: [id])
accountId Int
}
model Message {
id Int @id @default(autoincrement())
pubkey String
content String
tags String
createdAt DateTime @default(now())
Account Account @relation(fields: [accountId], references: [id])
accountId Int
@@index([pubkey])
}
model Relay {
id Int @id @default(autoincrement())
url String
active Boolean @default(true)
}
model Setting {
id Int @id @default(autoincrement())
key String
value String
}

View File

@ -8,13 +8,87 @@
extern crate objc;
use tauri::{Manager, WindowEvent};
#[cfg(target_os = "macos")]
use window_ext::WindowExt;
#[cfg(target_os = "macos")]
mod window_ext;
fn main() {
mod db;
use db::*;
use serde::Deserialize;
use specta::{collect_types, Type};
use std::sync::Arc;
use tauri::State;
use tauri_specta::ts;
type DbState<'a> = State<'a, Arc<PrismaClient>>;
#[derive(Deserialize, Type)]
struct CreateAccountData {
pubkey: String,
privkey: String,
metadata: String,
}
#[derive(Deserialize, Type)]
struct CreateFollowData {
pubkey: String,
kind: i32,
metadata: String,
account_id: i32,
}
#[tauri::command]
#[specta::specta]
async fn get_account(db: DbState<'_>) -> Result<Vec<account::Data>, ()> {
db.account()
.find_many(vec![account::active::equals(true)])
.exec()
.await
.map_err(|_| ())
}
#[tauri::command]
#[specta::specta]
async fn create_account(db: DbState<'_>, data: CreateAccountData) -> Result<account::Data, ()> {
db.account()
.create(data.pubkey, data.privkey, data.metadata, vec![])
.exec()
.await
.map_err(|_| ())
}
#[tauri::command]
#[specta::specta]
async fn create_follow(db: DbState<'_>, data: CreateFollowData) -> Result<follow::Data, ()> {
db.follow()
.create(
data.pubkey,
data.kind,
data.metadata,
account::id::equals(data.account_id),
vec![],
)
.exec()
.await
.map_err(|_| ())
}
#[tokio::main]
async fn main() {
let db = PrismaClient::_builder().build().await.unwrap();
#[cfg(debug_assertions)]
ts::export(
collect_types![get_account, create_account, create_follow],
"../src/utils/bindings.ts",
)
.unwrap();
#[cfg(debug_assertions)]
db._db_push().await.unwrap();
tauri::Builder::default()
.setup(|app| {
let main_window = app.get_window("main").unwrap();
@ -38,6 +112,12 @@ fn main() {
_ => {}
}
})
.invoke_handler(tauri::generate_handler![
get_account,
create_account,
create_follow
])
.manage(Arc::new(db))
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -2,24 +2,27 @@ import BaseLayout from '@layouts/base';
import { activeAccountAtom } from '@stores/account';
import { getActiveAccount } from '@utils/storage';
import LumeSymbol from '@assets/icons/Lume';
import { useSetAtom } from 'jotai';
import { useRouter } from 'next/router';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useEffect } from 'react';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useEffect } from 'react';
export default function Page() {
const router = useRouter();
const setActiveAccount = useSetAtom(activeAccountAtom);
const fetchActiveAccount = useCallback(async () => {
const { getAccount } = await import('@utils/bindings');
return await getAccount();
}, []);
useEffect(() => {
getActiveAccount()
fetchActiveAccount()
.then((res: any) => {
if (res) {
if (res.length > 0) {
// update local storage
setActiveAccount(res);
setActiveAccount(res[0]);
// redirect
router.replace('/init');
} else {
@ -27,7 +30,7 @@ export default function Page() {
}
})
.catch(console.error);
}, [router, setActiveAccount]);
}, [fetchActiveAccount, setActiveAccount, router]);
return (
<div className="relative h-full overflow-hidden">

View File

@ -2,13 +2,20 @@ import BaseLayout from '@layouts/base';
import { RelayContext } from '@components/relaysProvider';
import { createAccount } from '@utils/storage';
import { ArrowLeftIcon, EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useMemo, useState } from 'react';
import {
JSXElementConstructor,
ReactElement,
ReactFragment,
ReactPortal,
useCallback,
useContext,
useMemo,
useState,
} from 'react';
import { Config, names, uniqueNamesGenerator } from 'unique-names-generator';
const config: Config = {
@ -39,23 +46,11 @@ export default function Page() {
display_name: name,
name: name,
username: name.toLowerCase(),
picture: 'https://void.cat/d/KmypFh2fBdYCEvyJrPiN89',
picture: 'https://void.cat/d/KmypFh2fBdYCEvyJrPiN89.webp',
}),
[name]
);
// build profile
const data = useMemo(
() => ({
pubkey: pubKey,
privkey: privKey,
npub: npub,
nsec: nsec,
metadata: metadata,
}),
[metadata, npub, nsec, privKey, pubKey]
);
// toggle privatek key
const showPrivateKey = () => {
if (type === 'password') {
@ -66,7 +61,8 @@ export default function Page() {
};
// create account and broadcast to all relays
const submit = () => {
const submit = useCallback(async () => {
const { createAccount } = await import('@utils/bindings');
setLoading(true);
// build event
@ -81,16 +77,16 @@ export default function Page() {
event.sig = signEvent(event, privKey);
// insert to database then broadcast
createAccount(data)
.then(() => {
createAccount({ pubkey: pubKey, privkey: privKey, metadata: JSON.stringify(metadata) })
.then((res) => {
pool.publish(event, relays);
router.push({
pathname: '/onboarding/create/step-2',
query: { id: pubKey, privkey: privKey },
query: { id: res.id, privkey: res.privkey },
});
})
.catch(console.error);
};
}, [pool, pubKey, privKey, metadata, relays, router]);
return (
<div className="grid h-full w-full grid-rows-5">

View File

@ -3,7 +3,7 @@ import BaseLayout from '@layouts/base';
import { RelayContext } from '@components/relaysProvider';
import { UserBase } from '@components/user/base';
import { createFollows } from '@utils/storage';
import { followsTag } from '@utils/transform';
import { CheckCircledIcon } from '@radix-ui/react-icons';
import { createClient } from '@supabase/supabase-js';
@ -15,6 +15,7 @@ import {
ReactElement,
ReactFragment,
ReactPortal,
useCallback,
useContext,
useEffect,
useState,
@ -76,18 +77,9 @@ export default function Page() {
setFollows(arr);
};
// build event tags
const tags = () => {
const arr = [];
// push item to tags
follows.forEach((item) => {
arr.push(['p', item]);
});
return arr;
};
// save follows to database then broadcast
const submit = () => {
const submit = useCallback(async () => {
const { createFollow } = await import('@utils/bindings');
setLoading(true);
// build event
@ -96,21 +88,18 @@ export default function Page() {
created_at: Math.floor(Date.now() / 1000),
kind: 3,
pubkey: id,
tags: tags(),
tags: followsTag(follows),
};
event.id = getEventHash(event);
event.sig = signEvent(event, privkey);
createFollows(follows, id, 0)
.then((res) => {
if (res === 'ok') {
// publish to relays
pool.publish(event, relays);
router.replace('/');
}
})
.catch(console.error);
};
follows.forEach((item) => {
createFollow({ pubkey: item, kind: 0, metadata: JSON.stringify({}), account_id: id });
});
pool.publish(event, relays);
router.replace('/');
}, [follows, id, pool, privkey, relays, router]);
useEffect(() => {
const fetchData = async () => {

26
src/utils/bindings.ts Normal file
View File

@ -0,0 +1,26 @@
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
declare global {
interface Window {
__TAURI_INVOKE__<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
}
}
const invoke = window.__TAURI_INVOKE__;
export function getAccount() {
return invoke<Account[]>('get_account');
}
export function createAccount(data: CreateAccountData) {
return invoke<Account>('create_account', { data });
}
export function createFollow(data: CreateFollowData) {
return invoke<Follow>('create_follow', { data });
}
export type Account = { id: number; pubkey: string; privkey: string; active: boolean; metadata: string };
export type Follow = { id: number; pubkey: string; kind: number; metadata: string; accountId: number };
export type CreateFollowData = { pubkey: string; kind: number; metadata: string; account_id: number };
export type CreateAccountData = { pubkey: string; privkey: string; metadata: string };

View File

@ -9,6 +9,15 @@ export const tagsToArray = (arr) => {
return newarr;
};
export const followsTag = (arr) => {
const newarr = [];
// push item to tags
arr.forEach((item) => {
arr.push(['p', item]);
});
return newarr;
};
export const pubkeyArray = (arr) => {
const newarr = [];
// push item to newarr