import { Account, IColumn, NDKCacheEvent, NDKCacheEventTag, NDKCacheUser, NDKCacheUserProfile, } from "@lume/types"; import { appConfigDir, resolveResource } from "@tauri-apps/api/path"; import { invoke } from "@tauri-apps/api/primitives"; import { Platform } from "@tauri-apps/plugin-os"; import { Child, Command } from "@tauri-apps/plugin-shell"; import Database from "@tauri-apps/plugin-sql"; export class LumeStorage { #db: Database; #depot: Child; readonly platform: Platform; public account: Account; public settings: { autoupdate: boolean; bunker: boolean; outbox: boolean; media: boolean; hashtag: boolean; depot: boolean; tunnelUrl: string; lowPowerMode: boolean; }; constructor(db: Database, platform: Platform) { this.#db = db; this.platform = platform; this.settings = { autoupdate: false, bunker: false, outbox: false, media: true, hashtag: true, depot: false, tunnelUrl: "", lowPowerMode: false, }; } public async init() { const settings = await this.getAllSettings(); for (const item of settings) { if (item.key === "nsecbunker") this.settings.bunker = !!parseInt(item.value); if (item.key === "outbox") this.settings.outbox = !!parseInt(item.value); if (item.key === "media") this.settings.media = !!parseInt(item.value); if (item.key === "hashtag") this.settings.hashtag = !!parseInt(item.value); if (item.key === "autoupdate") this.settings.autoupdate = !!parseInt(item.value); if (item.key === "depot") this.settings.depot = !!parseInt(item.value); if (item.key === "tunnel_url") this.settings.tunnelUrl = item.value; } const account = await this.getActiveAccount(); if (account) this.account = account; } async #keyring_save(key: string, value: string) { return await invoke("secure_save", { key, value }); } async #keyring_load(key: string) { try { const value: string = await invoke("secure_load", { key }); if (!value) return null; return value; } catch { return null; } } async #keyring_remove(key: string) { return await invoke("secure_remove", { key }); } public async launchDepot() { const configPath = await resolveResource("resources/config.toml"); const dataPath = await appConfigDir(); const command = Command.sidecar("bin/depot", [ "-c", configPath, "-d", dataPath, ]); this.#depot = await command.spawn(); } public checkDepot() { if (this.#depot) return true; return false; } public async stopDepot() { if (this.#depot) return this.#depot.kill(); } public async getCacheUser(pubkey: string) { const results: Array = await this.#db.select( "SELECT * FROM ndk_users WHERE pubkey = $1 ORDER BY pubkey DESC LIMIT 1;", [pubkey], ); if (!results.length) return null; if (typeof results[0].profile === "string") results[0].profile = JSON.parse(results[0].profile); return results[0]; } public async getCacheEvent(id: string) { const results: Array = await this.#db.select( "SELECT * FROM ndk_events WHERE id = $1 ORDER BY id DESC LIMIT 1;", [id], ); if (!results.length) return null; return results[0]; } public async getCacheEvents(ids: string[]) { const idsArr = `'${ids.join("','")}'`; const results: Array = await this.#db.select( `SELECT * FROM ndk_events WHERE id IN (${idsArr}) ORDER BY id;`, ); if (!results.length) return []; return results; } public async getCacheEventsByPubkey(pubkey: string) { const results: Array = await this.#db.select( "SELECT * FROM ndk_events WHERE pubkey = $1 ORDER BY id;", [pubkey], ); if (!results.length) return []; return results; } public async getCacheEventsByKind(kind: number) { const results: Array = await this.#db.select( "SELECT * FROM ndk_events WHERE kind = $1 ORDER BY id;", [kind], ); if (!results.length) return []; return results; } public async getCacheEventsByKindAndAuthor(kind: number, pubkey: string) { const results: Array = await this.#db.select( "SELECT * FROM ndk_events WHERE kind = $1 AND pubkey = $2 ORDER BY id;", [kind, pubkey], ); if (!results.length) return []; return results; } public async getCacheEventTagsByTagValue(tagValue: string) { const results: Array = await this.#db.select( "SELECT * FROM ndk_eventtags WHERE tagValue = $1 ORDER BY id;", [tagValue], ); if (!results.length) return []; return results; } public async setCacheEvent({ id, pubkey, content, kind, createdAt, relay, event, }: NDKCacheEvent) { return await this.#db.execute( "INSERT OR IGNORE INTO ndk_events (id, pubkey, content, kind, createdAt, relay, event) VALUES ($1, $2, $3, $4, $5, $6, $7);", [id, pubkey, content, kind, createdAt, relay, event], ); } public async setCacheEventTag({ id, eventId, tag, value, tagValue, }: NDKCacheEventTag) { return await this.#db.execute( "INSERT OR IGNORE INTO ndk_eventtags (id, eventId, tag, value, tagValue) VALUES ($1, $2, $3, $4, $5);", [id, eventId, tag, value, tagValue], ); } public async setCacheProfiles(profiles: Array) { return await Promise.all( profiles.map( async (profile) => await this.#db.execute( "INSERT OR IGNORE INTO ndk_users (pubkey, profile, createdAt) VALUES ($1, $2, $3);", [profile.pubkey, profile.profile, profile.createdAt], ), ), ); } public async getAllCacheUsers() { const results: Array = await this.#db.select( "SELECT * FROM ndk_users ORDER BY createdAt DESC;", ); if (!results.length) return []; const users: NDKCacheUserProfile[] = results.map((item) => ({ pubkey: item.pubkey, ...JSON.parse(item.profile as string), })); return users; } public async checkAccount() { const result: Array<{ total: string }> = await this.#db.select( 'SELECT COUNT(*) AS "total" FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;', ); return parseInt(result[0].total); } public async getActiveAccount() { const results: Array = await this.#db.select( 'SELECT * FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;', ); if (results.length) { this.account = results[0]; return results[0]; } return null; } public async createAccount({ id, pubkey, privkey, }: { id: string; pubkey: string; privkey?: string; }) { const existAccounts: Array = await this.#db.select( "SELECT * FROM accounts WHERE pubkey = $1 ORDER BY id DESC LIMIT 1;", [pubkey], ); if (existAccounts.length) { await this.#db.execute( "UPDATE accounts SET is_active = '1' WHERE pubkey = $1;", [pubkey], ); } else { await this.#db.execute( "INSERT OR IGNORE INTO accounts (id, pubkey, is_active) VALUES ($1, $2, $3);", [id, pubkey, 1], ); if (privkey) await this.#keyring_save(pubkey, privkey); } const account = await this.getActiveAccount(); this.account = account; this.account.contacts = []; return account; } /** * Save private key to OS secure storage * @deprecated this method will be remove in the next update */ public async createPrivkey(name: string, privkey: string) { return await this.#keyring_save(name, privkey); } /** * Load private key from OS secure storage * @deprecated this method will be remove in the next update */ public async loadPrivkey(name: string) { return await this.#keyring_load(name); } /** * Remove private key from OS secure storage * @deprecated this method will be remove in the next update */ public async removePrivkey(name: string) { return await this.#keyring_remove(name); } public async updateAccount(column: string, value: string) { const insert = await this.#db.execute( `UPDATE accounts SET ${column} = $1 WHERE id = $2;`, [value, this.account.id], ); if (insert) { const account = await this.getActiveAccount(); return account; } } public async getWidgets() { const widgets: Array = await this.#db.select( "SELECT * FROM widgets WHERE account_id = $1 ORDER BY created_at DESC;", [this.account.id], ); return widgets; } public async createWidget( kind: number, title: string, content: string | string[], ) { const insert = await this.#db.execute( "INSERT INTO widgets (account_id, kind, title, content) VALUES ($1, $2, $3, $4);", [this.account.id, kind, title, content], ); if (insert) { const widgets: Array = await this.#db.select( "SELECT * FROM widgets ORDER BY id DESC LIMIT 1;", ); if (widgets.length < 1) console.error("get created widget failed"); return widgets[0]; } console.error("create widget failed"); } public async removeWidget(id: number) { const res = await this.#db.execute("DELETE FROM widgets WHERE id = $1;", [ id, ]); if (res) return id; } public async createSetting(key: string, value: string | undefined) { const currentSetting = await this.checkSettingValue(key); if (!currentSetting) { return await this.#db.execute( "INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);", [key, value], ); } return await this.#db.execute( "UPDATE settings SET value = $1 WHERE key = $2;", [value, key], ); } public async getAllSettings() { const results: { key: string; value: string }[] = await this.#db.select( "SELECT * FROM settings ORDER BY id DESC;", ); if (results.length < 1) return []; return results; } public async checkSettingValue(key: string) { const results: { key: string; value: string }[] = await this.#db.select( "SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;", [key], ); if (!results.length) return false; return results[0].value; } public async getSettingValue(key: string) { const results: { key: string; value: string }[] = await this.#db.select( "SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;", [key], ); if (!results.length) return "0"; return results[0].value; } public async clearCache() { await this.#db.execute("DELETE FROM ndk_events;"); await this.#db.execute("DELETE FROM ndk_eventtags;"); await this.#db.execute("DELETE FROM ndk_users;"); } public async logout() { this.account = null; return await this.#db.execute( "UPDATE accounts SET is_active = '0' WHERE id = $1;", [this.account.id], ); } }