206 lines
6.2 KiB
TypeScript
206 lines
6.2 KiB
TypeScript
|
|
import Database, { Database as DatabaseType } from 'better-sqlite3';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
|
|
// TypeScript interfaces for cluster feature
|
|
export interface Cluster {
|
|
id: number;
|
|
name: string;
|
|
description?: string;
|
|
color: string;
|
|
icon: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface ClusterWithStats extends Cluster {
|
|
library_count: number;
|
|
media_count: number;
|
|
video_count: number;
|
|
photo_count: number;
|
|
text_count: number;
|
|
total_size: number;
|
|
}
|
|
|
|
export interface LibraryClusterMapping {
|
|
id: number;
|
|
library_id: number;
|
|
cluster_id: number;
|
|
created_at: string;
|
|
}
|
|
|
|
let db: DatabaseType | null = null;
|
|
|
|
function initializeDatabase() {
|
|
if (db) return db;
|
|
|
|
// const dbPath = process.env.DB_FILE || path.join(process.cwd(), 'media.db');
|
|
const dbPath = path.join(process.cwd(), 'data', 'media.db');
|
|
|
|
// Ensure the data directory exists
|
|
const dataDir = path.dirname(dbPath);
|
|
if (!fs.existsSync(dataDir)) {
|
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
}
|
|
|
|
db = new Database(dbPath);
|
|
|
|
// Create tables
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS libraries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
path TEXT NOT NULL UNIQUE
|
|
);
|
|
`);
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS media (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
library_id INTEGER,
|
|
path TEXT NOT NULL UNIQUE,
|
|
type TEXT NOT NULL,
|
|
title TEXT,
|
|
size INTEGER,
|
|
thumbnail TEXT,
|
|
codec_info TEXT DEFAULT '{}',
|
|
bookmark_count INTEGER DEFAULT 0,
|
|
star_count INTEGER DEFAULT 0,
|
|
avg_rating REAL DEFAULT 0.0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (library_id) REFERENCES libraries (id)
|
|
);
|
|
`);
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS bookmarks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
media_id INTEGER NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS stars (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
media_id INTEGER NOT NULL,
|
|
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Create folder bookmarks table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS folder_bookmarks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
folder_path TEXT NOT NULL UNIQUE,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`);
|
|
|
|
// Create clusters table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS clusters (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL UNIQUE,
|
|
description TEXT,
|
|
color TEXT DEFAULT '#6366f1',
|
|
icon TEXT DEFAULT 'folder',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`);
|
|
|
|
// Create library-cluster mapping table
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS library_cluster_mapping (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
library_id INTEGER NOT NULL,
|
|
cluster_id INTEGER NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (library_id) REFERENCES libraries(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE,
|
|
UNIQUE(library_id, cluster_id)
|
|
);
|
|
`);
|
|
|
|
// Create indexes for performance
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_media_id ON bookmarks(media_id);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_stars_media_id ON stars(media_id);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_folder_bookmarks_path ON folder_bookmarks(folder_path);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_bookmark_count ON media(bookmark_count);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_star_count ON media(star_count);`);
|
|
|
|
// Pagination and filtering indexes
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_type_created_at ON media(type, created_at);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_path ON media(path);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_library_id ON media(library_id);`);
|
|
|
|
// Full-text search indexes
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_title ON media(title);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_type_path ON media(type, path);`);
|
|
|
|
// Cluster indexes
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_library_cluster_mapping_library ON library_cluster_mapping(library_id);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_library_cluster_mapping_cluster ON library_cluster_mapping(cluster_id);`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_clusters_name ON clusters(name);`);
|
|
|
|
return db;
|
|
}
|
|
|
|
export function getDatabase(): DatabaseType {
|
|
return initializeDatabase();
|
|
}
|
|
|
|
// Helper functions for folder bookmarks
|
|
export function addFolderBookmark(folderPath: string): number {
|
|
const db = getDatabase();
|
|
const result = db.prepare(`
|
|
INSERT OR REPLACE INTO folder_bookmarks (folder_path, updated_at)
|
|
VALUES (?, CURRENT_TIMESTAMP)
|
|
`).run(folderPath);
|
|
return result.lastInsertRowid as number;
|
|
}
|
|
|
|
export function removeFolderBookmark(folderPath: string): boolean {
|
|
const db = getDatabase();
|
|
const result = db.prepare(`
|
|
DELETE FROM folder_bookmarks WHERE folder_path = ?
|
|
`).run(folderPath);
|
|
return result.changes > 0;
|
|
}
|
|
|
|
export function isFolderBookmarked(folderPath: string): boolean {
|
|
const db = getDatabase();
|
|
const result = db.prepare(`
|
|
SELECT id FROM folder_bookmarks WHERE folder_path = ?
|
|
`).get(folderPath) as { id: number } | undefined;
|
|
return !!result;
|
|
}
|
|
|
|
export function getFolderBookmarks(limit: number = 50, offset: number = 0) {
|
|
const db = getDatabase();
|
|
const bookmarks = db.prepare(`
|
|
SELECT * FROM folder_bookmarks
|
|
ORDER BY updated_at DESC
|
|
LIMIT ? OFFSET ?
|
|
`).all(limit, offset);
|
|
|
|
const totalResult = db.prepare(`
|
|
SELECT COUNT(*) as total FROM folder_bookmarks
|
|
`).get() as { total: number };
|
|
|
|
return {
|
|
bookmarks,
|
|
total: totalResult.total
|
|
};
|
|
}
|
|
|
|
// For backward compatibility, export the database instance getter
|
|
export default getDatabase;
|