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:
Erwin
2026-03-28 03:31:14 +00:00
parent 825dfba2a7
commit 8b0b60db33
4 changed files with 583 additions and 2 deletions

View File

@@ -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';

View File

@@ -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);

View 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;