/**
* NotebookService — Business logic for notebook operations.
*
* Extracts filtering, visibility, and side-effect logic from route handlers.
* Routes call this service; this service calls NoteManager.
*/
export class NotebookService {
constructor ({ noteManager, notebookCrypto, authManager, sitePublisher }) {
this.noteManager = noteManager
this.notebookCrypto = notebookCrypto
this.authManager = authManager
this.sitePublisher = sitePublisher
}
/**
* List notebooks with visibility/ownership filtering.
*
* @param {Object|null} auth - Auth result from authenticate() (null = anonymous)
* @param {Object} filters
* @param {boolean} [filters.mine] - Only current user's notebooks
* @param {string} [filters.owner] - Filter by owner username
* @returns {Promise<Array>} Filtered notebook list
*/
async listFiltered (auth, filters = {}) {
let notebooks = await this.noteManager.listNotebooks()
const isAdmin = auth && auth.permissions && auth.permissions.admin
// Non-admins cannot see private or encrypted notebooks
if (!isAdmin) {
notebooks = notebooks.filter(nb => nb.visibility !== 'private' && !nb.encrypted)
}
// Filter to current user's notebooks
if (filters.mine) {
if (auth && auth.userId) {
notebooks = notebooks.filter(nb => {
return nb.ownerId === auth.userId || (!nb.ownerId && isAdmin)
})
} else {
return []
}
}
// Filter by owner username
if (filters.owner) {
const ownerUser = await this.authManager.identityRegistry.getUserByUsername(filters.owner)
if (!ownerUser) return []
const userPerms = ownerUser.globalPermissions || []
const ownerIsAdmin = userPerms.includes('admin')
notebooks = notebooks.filter(nb => {
if (nb.ownerId === ownerUser.userId) return true
if (ownerIsAdmin && !nb.ownerId) return true
return false
})
}
return notebooks
}
/**
* Create a notebook with optional encryption and site publishing.
*
* @param {string} slug
* @param {Object} options - { name, description, visibility, encrypted, password }
* @param {string} ownerId
* @returns {Promise<Object>} Created notebook
*/
async create (slug, options, ownerId) {
const encrypted = !!(options.encrypted && options.password)
if (encrypted && !this.notebookCrypto) {
throw new Error('Encryption not configured on server')
}
const notebook = await this.noteManager.createNotebook(slug, {
name: options.name,
description: options.description,
encrypted,
visibility: options.visibility || 'public',
ownerId
})
if (encrypted) {
const key = await this.notebookCrypto.enableEncryption(slug, options.password)
this.noteManager.cacheKey(slug, key)
}
return notebook
}
/**
* Get a single notebook by slug.
*/
async get (slug) {
return this.noteManager.getNotebook(slug)
}
/**
* Update notebook metadata.
*/
async update (slug, updates) {
const allowed = {}
if (updates.name !== undefined) allowed.name = updates.name
if (updates.description !== undefined) allowed.description = updates.description
if (updates.encrypted !== undefined) allowed.encrypted = updates.encrypted
if (updates.visibility !== undefined) allowed.visibility = updates.visibility
return this.noteManager.updateNotebook(slug, allowed)
}
/**
* Delete a notebook.
*/
async delete (slug) {
return this.noteManager.deleteNotebook(slug)
}
/**
* Rename a notebook.
*/
async rename (oldSlug, newSlug) {
return this.noteManager.renameNotebook(oldSlug, newSlug)
}
}