refs #2500 Change hashtags database to sqlite3

This commit is contained in:
AkiraFukushima 2023-01-01 00:26:20 +09:00
parent 5098c4ee67
commit 654f4af1ec
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
7 changed files with 88 additions and 63 deletions

View File

@ -36,6 +36,18 @@ FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)',
}
}
)
db.run(
'CREATE TABLE IF NOT EXISTS hashtags(\
id INTEGER PRIMARY KEY, \
tag TEXT NOT NULL, \
account_id INTEGER UNIQUE NOT NULL, \
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)',
err => {
if (err) {
console.error('failed to create hashtags: ', err)
}
}
)
})
return db

View File

@ -1,44 +1,63 @@
import Datastore from 'nedb'
import sqlite3 from 'sqlite3'
import { LocalTag } from '~/src/types/localTag'
export default class Hashtags {
private db: Datastore
constructor(db: Datastore) {
this.db = db
this.db.ensureIndex({ fieldName: 'tagName', unique: true }, _ => {})
}
listTags(): Promise<Array<LocalTag>> {
return new Promise((resolve, reject) => {
this.db.find<LocalTag>({}, (err, docs) => {
if (err) return reject(err)
resolve(docs)
})
})
}
insertTag(tag: string): Promise<LocalTag> {
return new Promise((resolve, reject) => {
this.db.insert({ tagName: tag }, (err, doc) => {
if (err) return reject(err)
resolve(doc)
})
})
}
removeTag(localTag: LocalTag): Promise<number> {
return new Promise((resolve, reject) => {
this.db.remove(
{
tagName: localTag.tagName
},
{ multi: true },
(err, numRemoved) => {
if (err) return reject(err)
resolve(numRemoved)
}
export const listTags = (db: sqlite3.Database, accountId: number): Promise<Array<LocalTag>> => {
return new Promise((resolve, reject) => {
db.all('SELECT * FROM hashtags WHERE account_id = ?', accountId, (err, rows) => {
if (err) {
reject(err)
}
resolve(
rows.map(r => ({
id: r.id,
tagName: r.tag,
accountId: r.account_id
}))
)
})
}
})
}
export const insertTag = (db: sqlite3.Database, accountId: number, tag: string): Promise<LocalTag> => {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run('BEGIN TRANSACTION')
db.get('SELECT * FROM hashtags WHERE id = ? AND tag = ?', [accountId, tag], (err, row) => {
if (err) {
reject(err)
}
if (row) {
resolve({
id: row.id,
tagName: row.tag,
accountId: row.account_id
})
}
db.run('INSERT INTO hashtags(tag, account_id) VALUES (?, ?)', [accountId, tag], function (err) {
if (err) {
reject(err)
}
db.run('COMMIT')
resolve({
id: this.lastID,
tagName: tag,
accountId: accountId
})
})
})
})
})
}
export const removeTag = (db: sqlite3.Database, tag: LocalTag): Promise<null> => {
return new Promise((resolve, reject) => {
db.run('DELETE FROM hashtags WHERE id = ?', tag.id, err => {
if (err) {
reject(err)
}
resolve(null)
})
})
}

View File

@ -35,7 +35,6 @@ import { getAccount, insertAccount, listAccounts } from './account'
// import { StreamingURL, UserStreaming, DirectStreaming, LocalStreaming, PublicStreaming, ListStreaming, TagStreaming } from './websocket'
import Preferences from './preferences'
import Fonts from './fonts'
import Hashtags from './hashtags'
import i18next from '~/src/config/i18n'
import { i18n as I18n } from 'i18next'
import Language, { LanguageType } from '../constants/language'
@ -55,6 +54,7 @@ import Settings from './settings'
import { BaseSettings, Setting } from '~/src/types/setting'
import { insertServer } from './server'
import { LocalServer } from '~src/types/localServer'
import { insertTag, listTags, removeTag } from './hashtags'
/**
* Context menu
@ -126,12 +126,6 @@ const db = newDB(databasePath)
const preferencesDBPath = process.env.NODE_ENV === 'production' ? userData + './db/preferences.json' : 'preferences.json'
const hashtagsDBPath = process.env.NODE_ENV === 'production' ? userData + '/db/hashtags.db' : 'hashtags.db'
const hashtagsDB = new Datastore({
filename: hashtagsDBPath,
autoload: true
})
const settingsDBPath = process.env.NODE_ENV === 'production' ? userData + './db/settings.json' : 'settings.json'
/**
@ -1136,20 +1130,17 @@ ipcMain.handle('update-spellchecker-languages', async (_: IpcMainInvokeEvent, la
})
// hashtag
ipcMain.handle('save-hashtag', async (_: IpcMainInvokeEvent, tag: string) => {
const hashtags = new Hashtags(hashtagsDB)
await hashtags.insertTag(tag)
ipcMain.handle('save-hashtag', async (_: IpcMainInvokeEvent, req: { accountId: number; tag: string }) => {
await insertTag(db, req.accountId, req.tag)
})
ipcMain.handle('list-hashtags', async (_: IpcMainInvokeEvent) => {
const hashtags = new Hashtags(hashtagsDB)
const tags = await hashtags.listTags()
ipcMain.handle('list-hashtags', async (_: IpcMainInvokeEvent, accountId: number) => {
const tags = await listTags(db, accountId)
return tags
})
ipcMain.handle('remove-hashtag', async (_: IpcMainInvokeEvent, tag: LocalTag) => {
const hashtags = new Hashtags(hashtagsDB)
await hashtags.removeTag(tag)
await removeTag(db, tag)
})
// Fonts

View File

@ -18,8 +18,8 @@ export type HashtagModuleState = HashtagModule & HashtagState
const state = (): HashtagState => ({})
const actions: ActionTree<HashtagState, RootState> = {
saveTag: async ({ dispatch }, tag: string) => {
await win.ipcRenderer.invoke('save-hashtag', tag)
saveTag: async ({ dispatch, rootState }, tag: string) => {
await win.ipcRenderer.invoke('save-hashtag', { accountId: rootState.TimelineSpace.account!.id, tag })
dispatch('TimelineSpace/SideMenu/listTags', {}, { root: true })
}
}

View File

@ -4,7 +4,7 @@ import { RootState } from '@/store'
import { MyWindow } from '~/src/types/global'
import { toRaw } from 'vue'
const win = window as any as MyWindow
const win = (window as any) as MyWindow
export type ListState = {
tags: Array<LocalTag>
@ -30,14 +30,14 @@ export const ACTION_TYPES = {
}
const actions: ActionTree<ListState, RootState> = {
[ACTION_TYPES.LIST_TAGS]: async ({ commit }) => {
const tags: Array<LocalTag> = await win.ipcRenderer.invoke('list-hashtags')
[ACTION_TYPES.LIST_TAGS]: async ({ rootState, commit }) => {
const tags: Array<LocalTag> = await win.ipcRenderer.invoke('list-hashtags', rootState.TimelineSpace.account!.id)
commit(MUTATION_TYPES.UPDATE_TAGS, tags)
return tags
},
[ACTION_TYPES.REMOVE_TAG]: async ({ dispatch }, tag: LocalTag) => {
await win.ipcRenderer.invoke('remove-hashtag', toRaw(tag))
dispatch('listTags')
dispatch(ACTION_TYPES.LIST_TAGS)
dispatch('TimelineSpace/SideMenu/listTags', {}, { root: true })
return 'deleted'
}

View File

@ -223,8 +223,10 @@ const actions: ActionTree<SideMenuState, RootState> = {
commit(MUTATION_TYPES.CHANGE_COLLAPSE, value)
return value
},
[ACTION_TYPES.LIST_TAGS]: async ({ commit }) => {
const tags: Array<LocalTag> = await win.ipcRenderer.invoke('list-hashtags')
[ACTION_TYPES.LIST_TAGS]: async ({ rootState, commit }) => {
// TODO: Can not get account because too early.
// It should be executed after TimelineSpace obtain local account.
const tags: Array<LocalTag> = await win.ipcRenderer.invoke('list-hashtags', rootState.TimelineSpace.account!.id)
commit(MUTATION_TYPES.UPDATE_TAGS, tags)
return tags
}

View File

@ -1,4 +1,5 @@
export type LocalTag = {
id: number
tagName: string
_id?: string
accountId: number
}