fix: add missing libraryService.js and fix two bugs
- Create missing src/services/libraryService.js (was imported but never created) - Fix readJSONFile import in tagIndexer.js (unused import) - Fix found.found?.libId typo in deleteDocument (double .found lookup) These were blocking the application from starting.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Rebuild and query the global tag index
|
||||
*/
|
||||
|
||||
import { readJSON, writeJSON, pathExists, listDir, readJSONFile } from '../utils/fsHelper.js';
|
||||
import { readJSON, writeJSON, pathExists, listDir } from '../utils/fsHelper.js';
|
||||
import { join } from 'path';
|
||||
import config from '../config/index.js';
|
||||
|
||||
|
||||
@@ -276,7 +276,6 @@ export class DocumentService {
|
||||
}
|
||||
|
||||
const meta = readJSON(found.metaPath);
|
||||
deletePath(found.found?.libId ? join(this.librariesPath, found.libId, 'documents', docId) : null);
|
||||
deletePath(this._docPath(found.libId, docId));
|
||||
|
||||
this.tagIndexer.removeDocument(docId);
|
||||
|
||||
273
src/services/libraryService.js
Normal file
273
src/services/libraryService.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* SimpleNote Web - Library Service
|
||||
* Library CRUD with filesystem storage
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
import config from '../config/index.js';
|
||||
import { ensureDir, readJSON, writeJSON, pathExists, deletePath, listDir, isDirectory } from '../utils/fsHelper.js';
|
||||
import { generateId } from '../utils/uuid.js';
|
||||
import { NotFoundError, ValidationError } from '../utils/errors.js';
|
||||
|
||||
const LIBRARIES_DIR = 'libraries';
|
||||
const LIBRARY_META_FILE = '.library.json';
|
||||
|
||||
export class LibraryService {
|
||||
constructor(dataRoot = config.dataRoot) {
|
||||
this.dataRoot = dataRoot;
|
||||
this.librariesPath = join(dataRoot, LIBRARIES_DIR);
|
||||
}
|
||||
|
||||
_libPath(libId) {
|
||||
return join(this.librariesPath, libId);
|
||||
}
|
||||
|
||||
_libMetaPath(libId) {
|
||||
return join(this._libPath(libId), LIBRARY_META_FILE);
|
||||
}
|
||||
|
||||
_libDocumentsPath(libId) {
|
||||
return join(this._libPath(libId), 'documents');
|
||||
}
|
||||
|
||||
_libSubLibrariesPath(libId) {
|
||||
return join(this._libPath(libId), 'sub-libraries');
|
||||
}
|
||||
|
||||
_readLibMeta(libId) {
|
||||
const meta = readJSON(this._libMetaPath(libId));
|
||||
if (!meta) throw new NotFoundError('Library');
|
||||
return meta;
|
||||
}
|
||||
|
||||
_findLibraryById(libId) {
|
||||
const metaPath = this._libMetaPath(libId);
|
||||
if (pathExists(metaPath)) {
|
||||
return { id: libId, metaPath };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async createLibrary({ name, parentId = null }) {
|
||||
if (!name || !name.trim()) {
|
||||
throw new ValidationError('Library name is required');
|
||||
}
|
||||
|
||||
const libId = generateId();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
if (parentId) {
|
||||
// Verify parent exists
|
||||
const parentMeta = this._readLibMeta(parentId);
|
||||
const parentSubLibsPath = this._libSubLibrariesPath(parentId);
|
||||
ensureDir(parentSubLibsPath);
|
||||
|
||||
const libMeta = {
|
||||
id: libId,
|
||||
name: name.trim(),
|
||||
parentId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
const libPath = join(parentSubLibsPath, libId);
|
||||
ensureDir(libPath);
|
||||
ensureDir(join(libPath, 'documents'));
|
||||
writeJSON(this._libMetaPath(libId), libMeta);
|
||||
return libMeta;
|
||||
} else {
|
||||
ensureDir(this.librariesPath);
|
||||
|
||||
const libMeta = {
|
||||
id: libId,
|
||||
name: name.trim(),
|
||||
parentId: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
const libPath = this._libPath(libId);
|
||||
ensureDir(libPath);
|
||||
ensureDir(join(libPath, 'documents'));
|
||||
writeJSON(this._libMetaPath(libId), libMeta);
|
||||
return libMeta;
|
||||
}
|
||||
}
|
||||
|
||||
async listRootLibraries() {
|
||||
ensureDir(this.librariesPath);
|
||||
|
||||
const entries = listDir(this.librariesPath);
|
||||
const libraries = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const libPath = join(this.librariesPath, entry);
|
||||
if (!isDirectory(libPath)) continue;
|
||||
|
||||
const meta = readJSON(this._libMetaPath(entry));
|
||||
if (!meta) continue;
|
||||
|
||||
const docCount = this._countDocuments(this._libDocumentsPath(entry));
|
||||
libraries.push({
|
||||
...meta,
|
||||
documentCount: docCount,
|
||||
});
|
||||
}
|
||||
|
||||
return libraries;
|
||||
}
|
||||
|
||||
_countDocuments(docsPath) {
|
||||
if (!pathExists(docsPath)) return 0;
|
||||
const entries = listDir(docsPath);
|
||||
return entries.filter(e => {
|
||||
const metaPath = join(docsPath, e, '.meta.json');
|
||||
return pathExists(metaPath);
|
||||
}).length;
|
||||
}
|
||||
|
||||
async getLibrary(libId) {
|
||||
const found = this._findLibraryById(libId);
|
||||
if (!found) {
|
||||
throw new NotFoundError('Library');
|
||||
}
|
||||
|
||||
const meta = readJSON(this._libMetaPath(libId));
|
||||
const docsPath = this._libDocumentsPath(libId);
|
||||
const subLibsPath = this._libSubLibrariesPath(libId);
|
||||
|
||||
// List documents
|
||||
const documents = [];
|
||||
if (pathExists(docsPath)) {
|
||||
const docEntries = listDir(docsPath);
|
||||
for (const docEntry of docEntries) {
|
||||
const docMetaPath = join(docsPath, docEntry, '.meta.json');
|
||||
if (pathExists(docMetaPath)) {
|
||||
const docMeta = readJSON(docMetaPath);
|
||||
if (docMeta?.id) {
|
||||
documents.push({
|
||||
id: docMeta.id,
|
||||
title: docMeta.title,
|
||||
type: docMeta.type,
|
||||
status: docMeta.status,
|
||||
tags: docMeta.tags || [],
|
||||
updatedAt: docMeta.updatedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List sub-libraries
|
||||
const subLibraries = [];
|
||||
if (pathExists(subLibsPath)) {
|
||||
const subEntries = listDir(subLibsPath);
|
||||
for (const subEntry of subEntries) {
|
||||
const subMetaPath = join(subLibsPath, subEntry, LIBRARY_META_FILE);
|
||||
if (pathExists(subMetaPath)) {
|
||||
const subMeta = readJSON(subMetaPath);
|
||||
if (subMeta?.id) {
|
||||
const subDocCount = this._countDocuments(join(subLibsPath, subEntry, 'documents'));
|
||||
subLibraries.push({
|
||||
id: subMeta.id,
|
||||
name: subMeta.name,
|
||||
documentCount: subDocCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
library: meta,
|
||||
documents,
|
||||
subLibraries,
|
||||
};
|
||||
}
|
||||
|
||||
async getLibraryTree(libId) {
|
||||
const found = this._findLibraryById(libId);
|
||||
if (!found) {
|
||||
throw new NotFoundError('Library');
|
||||
}
|
||||
|
||||
const meta = readJSON(this._libMetaPath(libId));
|
||||
|
||||
const buildTree = (id) => {
|
||||
const libMeta = readJSON(this._libMetaPath(id));
|
||||
const docsPath = this._libDocumentsPath(id);
|
||||
const subLibsPath = this._libSubLibrariesPath(id);
|
||||
|
||||
const documents = [];
|
||||
if (pathExists(docsPath)) {
|
||||
const docEntries = listDir(docsPath);
|
||||
for (const docEntry of docEntries) {
|
||||
const docMetaPath = join(docsPath, docEntry, '.meta.json');
|
||||
if (pathExists(docMetaPath)) {
|
||||
const docMeta = readJSON(docMetaPath);
|
||||
if (docMeta?.id) {
|
||||
documents.push({
|
||||
id: docMeta.id,
|
||||
title: docMeta.title,
|
||||
type: docMeta.type,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const subLibraries = [];
|
||||
if (pathExists(subLibsPath)) {
|
||||
const subEntries = listDir(subLibsPath);
|
||||
for (const subEntry of subEntries) {
|
||||
const subMetaPath = join(subLibsPath, subEntry, LIBRARY_META_FILE);
|
||||
if (pathExists(subMetaPath)) {
|
||||
subLibraries.push(buildTree(subEntry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: libMeta.id,
|
||||
name: libMeta.name,
|
||||
documents,
|
||||
subLibraries,
|
||||
};
|
||||
};
|
||||
|
||||
return buildTree(libId);
|
||||
}
|
||||
|
||||
async listDocumentsInLibrary(libId) {
|
||||
const found = this._findLibraryById(libId);
|
||||
if (!found) {
|
||||
throw new NotFoundError('Library');
|
||||
}
|
||||
|
||||
const result = await this.getLibrary(libId);
|
||||
return { documents: result.documents, total: result.documents.length };
|
||||
}
|
||||
|
||||
async deleteLibrary(libId) {
|
||||
const found = this._findLibraryById(libId);
|
||||
if (!found) {
|
||||
throw new NotFoundError('Library');
|
||||
}
|
||||
|
||||
const libPath = this._libPath(libId);
|
||||
deletePath(libPath);
|
||||
|
||||
return { deleted: true, id: libId };
|
||||
}
|
||||
}
|
||||
|
||||
let globalLibraryService = null;
|
||||
|
||||
export function getLibraryService(dataRoot = config.dataRoot) {
|
||||
if (!globalLibraryService) {
|
||||
globalLibraryService = new LibraryService(dataRoot);
|
||||
}
|
||||
return globalLibraryService;
|
||||
}
|
||||
|
||||
export default LibraryService;
|
||||
Reference in New Issue
Block a user