Rewrite of ESH-Media v1 with separated main/renderer/shared architecture (vite-plugin-electron, React 18, react-router-dom). Includes NeDB storage, electron-store config, proxy manager with FoxyProxy/uBlock extensions, custom server-checked updater, NSIS installer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
148 lines
3.6 KiB
TypeScript
148 lines
3.6 KiB
TypeScript
import Datastore from 'nedb';
|
|
import * as path from 'path';
|
|
import { Bookmark, AppSettings } from '../shared/types';
|
|
import { DEFAULT_SETTINGS } from '../shared/constants';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
export class DatabaseManager {
|
|
private bookmarksDb: Datastore<any>;
|
|
private settingsDb: Datastore<any>;
|
|
|
|
constructor(userDataPath: string) {
|
|
this.bookmarksDb = new Datastore({
|
|
filename: path.join(userDataPath, 'bookmarks.db'),
|
|
autoload: false,
|
|
});
|
|
|
|
this.settingsDb = new Datastore({
|
|
filename: path.join(userDataPath, 'settings.db'),
|
|
autoload: false,
|
|
});
|
|
}
|
|
|
|
async init(): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
this.bookmarksDb.loadDatabase((err) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
this.settingsDb.loadDatabase((err) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
// Create indexes
|
|
this.bookmarksDb.ensureIndex({ fieldName: 'id', unique: true });
|
|
this.bookmarksDb.ensureIndex({ fieldName: 'siteId' });
|
|
|
|
console.log('Database initialized');
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// Bookmarks methods
|
|
async getBookmarks(): Promise<Bookmark[]> {
|
|
return new Promise((resolve, reject) => {
|
|
this.bookmarksDb
|
|
.find({})
|
|
.sort({ createdAt: -1 })
|
|
.exec((err, docs) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(docs as Bookmark[]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async addBookmark(bookmark: Omit<Bookmark, 'id' | 'createdAt'>): Promise<Bookmark> {
|
|
const newBookmark: Bookmark = {
|
|
...bookmark,
|
|
id: uuidv4(),
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.bookmarksDb.insert(newBookmark, (err, doc) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(doc as Bookmark);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async removeBookmark(id: string): Promise<boolean> {
|
|
return new Promise((resolve, reject) => {
|
|
this.bookmarksDb.remove({ id }, {}, (err, numRemoved) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(numRemoved > 0);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async getBookmarksBySite(siteId: string): Promise<Bookmark[]> {
|
|
return new Promise((resolve, reject) => {
|
|
this.bookmarksDb
|
|
.find({ siteId })
|
|
.sort({ createdAt: -1 })
|
|
.exec((err, docs) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(docs as Bookmark[]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Settings methods
|
|
async getSettings(): Promise<AppSettings> {
|
|
return new Promise((resolve, reject) => {
|
|
this.settingsDb.findOne({ type: 'app-settings' }, (err, doc) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else if (doc) {
|
|
resolve(doc as AppSettings);
|
|
} else {
|
|
// Return default settings if not found
|
|
resolve(DEFAULT_SETTINGS);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async saveSettings(settings: AppSettings): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
this.settingsDb.update(
|
|
{ type: 'app-settings' },
|
|
{ ...settings, type: 'app-settings' },
|
|
{ upsert: true },
|
|
(err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
close(): void {
|
|
// NeDB doesn't require explicit closing, but we can compact databases
|
|
this.bookmarksDb.persistence.compactDatafile();
|
|
this.settingsDb.persistence.compactDatafile();
|
|
}
|
|
}
|