Merge branch 'master' into shawn/rss-feeds

This commit is contained in:
Shawn Hoffman 2023-08-22 10:30:16 -07:00 committed by GitHub
commit 24989e73ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1575 additions and 1193 deletions

View file

@ -21,7 +21,7 @@ class AuthorController {
// Used on author landing page to include library items and items grouped in series
if (include.includes('items')) {
authorJson.libraryItems = await Database.models.libraryItem.getForAuthor(req.author, req.user)
authorJson.libraryItems = await Database.libraryItemModel.getForAuthor(req.author, req.user)
if (include.includes('series')) {
const seriesMap = {}
@ -96,7 +96,7 @@ class AuthorController {
const existingAuthor = authorNameUpdate ? Database.authors.find(au => au.id !== req.author.id && payload.name === au.name) : false
if (existingAuthor) {
const bookAuthorsToCreate = []
const itemsWithAuthor = await Database.models.libraryItem.getForAuthor(req.author)
const itemsWithAuthor = await Database.libraryItemModel.getForAuthor(req.author)
itemsWithAuthor.forEach(libraryItem => { // Replace old author with merging author for each book
libraryItem.media.metadata.replaceAuthor(req.author, existingAuthor)
bookAuthorsToCreate.push({
@ -113,9 +113,11 @@ class AuthorController {
// Remove old author
await Database.removeAuthor(req.author.id)
SocketAuthority.emitter('author_removed', req.author.toJSON())
// Update filter data
Database.removeAuthorFromFilterData(req.author.libraryId, req.author.id)
// Send updated num books for merged author
const numBooks = await Database.models.libraryItem.getForAuthor(existingAuthor).length
const numBooks = await Database.libraryItemModel.getForAuthor(existingAuthor).length
SocketAuthority.emitter('author_updated', existingAuthor.toJSONExpanded(numBooks))
res.json({
@ -130,7 +132,7 @@ class AuthorController {
if (hasUpdated) {
req.author.updatedAt = Date.now()
const itemsWithAuthor = await Database.models.libraryItem.getForAuthor(req.author)
const itemsWithAuthor = await Database.libraryItemModel.getForAuthor(req.author)
if (authorNameUpdate) { // Update author name on all books
itemsWithAuthor.forEach(libraryItem => {
libraryItem.media.metadata.updateAuthor(req.author)
@ -202,7 +204,7 @@ class AuthorController {
await Database.updateAuthor(req.author)
const numBooks = await Database.models.libraryItem.getForAuthor(req.author).length
const numBooks = await Database.libraryItemModel.getForAuthor(req.author).length
SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
}

View file

@ -22,10 +22,10 @@ class CollectionController {
}
// Create collection record
await Database.models.collection.createFromOld(newCollection)
await Database.collectionModel.createFromOld(newCollection)
// Get library items in collection
const libraryItemsInCollection = await Database.models.libraryItem.getForCollection(newCollection)
const libraryItemsInCollection = await Database.libraryItemModel.getForCollection(newCollection)
// Create collectionBook records
let order = 1
@ -50,7 +50,7 @@ class CollectionController {
}
async findAll(req, res) {
const collectionsExpanded = await Database.models.collection.getOldCollectionsJsonExpanded(req.user)
const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user)
res.json({
collections: collectionsExpanded
})
@ -96,8 +96,8 @@ class CollectionController {
if (req.body.books?.length) {
const collectionBooks = await req.collection.getCollectionBooks({
include: {
model: Database.models.book,
include: Database.models.libraryItem
model: Database.bookModel,
include: Database.libraryItemModel
},
order: [['order', 'ASC']]
})
@ -143,7 +143,7 @@ class CollectionController {
* @param {*} res
*/
async addBook(req, res) {
const libraryItem = await Database.models.libraryItem.getOldById(req.body.id)
const libraryItem = await Database.libraryItemModel.getOldById(req.body.id)
if (!libraryItem) {
return res.status(404).send('Book not found')
}
@ -158,7 +158,7 @@ class CollectionController {
}
// Create collectionBook record
await Database.models.collectionBook.create({
await Database.collectionBookModel.create({
collectionId: req.collection.id,
bookId: libraryItem.media.id,
order: collectionBooks.length + 1
@ -176,7 +176,7 @@ class CollectionController {
* @param {*} res
*/
async removeBook(req, res) {
const libraryItem = await Database.models.libraryItem.getOldById(req.params.bookId)
const libraryItem = await Database.libraryItemModel.getOldById(req.params.bookId)
if (!libraryItem) {
return res.sendStatus(404)
}
@ -227,14 +227,14 @@ class CollectionController {
}
// Get library items associated with ids
const libraryItems = await Database.models.libraryItem.findAll({
const libraryItems = await Database.libraryItemModel.findAll({
where: {
id: {
[Sequelize.Op.in]: bookIdsToAdd
}
},
include: {
model: Database.models.book
model: Database.bookModel
}
})
@ -285,14 +285,14 @@ class CollectionController {
}
// Get library items associated with ids
const libraryItems = await Database.models.libraryItem.findAll({
const libraryItems = await Database.libraryItemModel.findAll({
where: {
id: {
[Sequelize.Op.in]: bookIdsToRemove
}
},
include: {
model: Database.models.book
model: Database.bookModel
}
})
@ -327,7 +327,7 @@ class CollectionController {
async middleware(req, res, next) {
if (req.params.id) {
const collection = await Database.models.collection.findByPk(req.params.id)
const collection = await Database.collectionModel.findByPk(req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}

View file

@ -17,7 +17,7 @@ class FileSystemController {
})
// Do not include existing mapped library paths in response
const libraryFoldersPaths = await Database.models.libraryFolder.getAllLibraryFolderPaths()
const libraryFoldersPaths = await Database.libraryModelFolder.getAllLibraryFolderPaths()
libraryFoldersPaths.forEach((path) => {
let dir = path || ''
if (dir.includes(global.appRoot)) dir = dir.replace(global.appRoot, '')

View file

@ -8,6 +8,7 @@ const Library = require('../objects/Library')
const libraryHelpers = require('../utils/libraryHelpers')
const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters')
const libraryItemFilters = require('../utils/queries/libraryItemFilters')
const seriesFilters = require('../utils/queries/seriesFilters')
const { sort, createNewSortInstance } = require('../libs/fastSort')
const naturalSort = createNewSortInstance({
comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
@ -15,6 +16,8 @@ const naturalSort = createNewSortInstance({
const Database = require('../Database')
const libraryFilters = require('../utils/queries/libraryFilters')
const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcastFilters')
const authorFilters = require('../utils/queries/authorFilters')
class LibraryController {
constructor() { }
@ -48,7 +51,7 @@ class LibraryController {
const library = new Library()
let currentLargestDisplayOrder = await Database.models.library.getMaxDisplayOrder()
let currentLargestDisplayOrder = await Database.libraryModel.getMaxDisplayOrder()
if (isNaN(currentLargestDisplayOrder)) currentLargestDisplayOrder = 0
newLibraryPayload.displayOrder = currentLargestDisplayOrder + 1
library.setData(newLibraryPayload)
@ -67,7 +70,7 @@ class LibraryController {
}
async findAll(req, res) {
const libraries = await Database.models.library.getAllOldLibraries()
const libraries = await Database.libraryModel.getAllOldLibraries()
const librariesAccessible = req.user.librariesAccessible || []
if (librariesAccessible.length) {
@ -89,7 +92,7 @@ class LibraryController {
return res.json({
filterdata,
issues: filterdata.numIssues,
numUserPlaylists: await Database.models.playlist.getNumPlaylistsForUserAndLibrary(req.user.id, req.library.id),
numUserPlaylists: await Database.playlistModel.getNumPlaylistsForUserAndLibrary(req.user.id, req.library.id),
library: req.library
})
}
@ -141,17 +144,17 @@ class LibraryController {
for (const folder of library.folders) {
if (!req.body.folders.some(f => f.id === folder.id)) {
// Remove library items in folder
const libraryItemsInFolder = await Database.models.libraryItem.findAll({
const libraryItemsInFolder = await Database.libraryItemModel.findAll({
where: {
libraryFolderId: folder.id
},
attributes: ['id', 'mediaId', 'mediaType'],
include: [
{
model: Database.models.podcast,
model: Database.podcastModel,
attributes: ['id'],
include: {
model: Database.models.podcastEpisode,
model: Database.podcastEpisodeModel,
attributes: ['id']
}
}
@ -188,6 +191,8 @@ class LibraryController {
return user.checkCanAccessLibrary && user.checkCanAccessLibrary(library.id)
}
SocketAuthority.emitter('library_updated', library.toJSON(), userFilter)
await Database.resetLibraryIssuesFilterData(library.id)
}
return res.json(library.toJSON())
}
@ -205,23 +210,23 @@ class LibraryController {
this.watcher.removeLibrary(library)
// Remove collections for library
const numCollectionsRemoved = await Database.models.collection.removeAllForLibrary(library.id)
const numCollectionsRemoved = await Database.collectionModel.removeAllForLibrary(library.id)
if (numCollectionsRemoved) {
Logger.info(`[Server] Removed ${numCollectionsRemoved} collections for library "${library.name}"`)
}
// Remove items in this library
const libraryItemsInLibrary = await Database.models.libraryItem.findAll({
const libraryItemsInLibrary = await Database.libraryItemModel.findAll({
where: {
libraryId: library.id
},
attributes: ['id', 'mediaId', 'mediaType'],
include: [
{
model: Database.models.podcast,
model: Database.podcastModel,
attributes: ['id'],
include: {
model: Database.models.podcastEpisode,
model: Database.podcastEpisodeModel,
attributes: ['id']
}
}
@ -243,9 +248,15 @@ class LibraryController {
await Database.removeLibrary(library.id)
// Re-order libraries
await Database.models.library.resetDisplayOrder()
await Database.libraryModel.resetDisplayOrder()
SocketAuthority.emitter('library_removed', libraryJson)
// Remove library filter data
if (Database.libraryFilterData[library.id]) {
delete Database.libraryFilterData[library.id]
}
return res.json(libraryJson)
}
@ -267,7 +278,7 @@ class LibraryController {
}
payload.offset = payload.page * payload.limit
const { libraryItems, count } = await Database.models.libraryItem.getByFilterAndSort(req.library, req.user, payload)
const { libraryItems, count } = await Database.libraryItemModel.getByFilterAndSort(req.library, req.user, payload)
payload.results = libraryItems
payload.total = count
@ -471,12 +482,13 @@ class LibraryController {
/**
* DELETE: /libraries/:id/issues
* Remove all library items missing or invalid
* @param {*} req
* @param {*} res
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async removeLibraryItemsWithIssues(req, res) {
const libraryItemsWithIssues = await Database.models.libraryItem.findAll({
const libraryItemsWithIssues = await Database.libraryItemModel.findAll({
where: {
libraryId: req.library.id,
[Sequelize.Op.or]: [
{
isMissing: true
@ -489,10 +501,10 @@ class LibraryController {
attributes: ['id', 'mediaId', 'mediaType'],
include: [
{
model: Database.models.podcast,
model: Database.podcastModel,
attributes: ['id'],
include: {
model: Database.models.podcastEpisode,
model: Database.podcastEpisodeModel,
attributes: ['id']
}
}
@ -507,7 +519,7 @@ class LibraryController {
Logger.info(`[LibraryController] Removing ${libraryItemsWithIssues.length} items with issues`)
for (const libraryItem of libraryItemsWithIssues) {
let mediaItemIds = []
if (library.isPodcast) {
if (req.library.isPodcast) {
mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id)
} else {
mediaItemIds.push(libraryItem.mediaId)
@ -516,19 +528,22 @@ class LibraryController {
await this.handleDeleteLibraryItem(libraryItem.mediaType, libraryItem.id, mediaItemIds)
}
// Set numIssues to 0 for library filter data
if (Database.libraryFilterData[req.library.id]) {
Database.libraryFilterData[req.library.id].numIssues = 0
}
res.sendStatus(200)
}
/**
* GET: /api/libraries/:id/series
* Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open
*
* @param {*} req
* @param {*} res
*/
* GET: /api/libraries/:id/series
* Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getAllSeriesForLibrary(req, res) {
const libraryItems = req.libraryItems
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const payload = {
@ -543,45 +558,10 @@ class LibraryController {
include: include.join(',')
}
let series = libraryHelpers.getSeriesFromBooks(libraryItems, Database.series, null, payload.filterBy, req.user, payload.minified, req.library.settings.hideSingleBookSeries)
const direction = payload.sortDesc ? 'desc' : 'asc'
series = naturalSort(series).by([
{
[direction]: (se) => {
if (payload.sortBy === 'numBooks') {
return se.books.length
} else if (payload.sortBy === 'totalDuration') {
return se.totalDuration
} else if (payload.sortBy === 'addedAt') {
return se.addedAt
} else if (payload.sortBy === 'lastBookUpdated') {
return Math.max(...(se.books).map(x => x.updatedAt), 0)
} else if (payload.sortBy === 'lastBookAdded') {
return Math.max(...(se.books).map(x => x.addedAt), 0)
} else { // sort by name
return Database.serverSettings.sortingIgnorePrefix ? se.nameIgnorePrefixSort : se.name
}
}
}
])
payload.total = series.length
if (payload.limit) {
const startIndex = payload.page * payload.limit
series = series.slice(startIndex, startIndex + payload.limit)
}
// add rssFeed when "include=rssfeed" is in query string
if (include.includes('rssfeed')) {
series = await Promise.all(series.map(async (se) => {
const feedData = await this.rssFeedManager.findFeedForEntityId(se.id)
se.rssFeed = feedData?.toJSONMinified() || null
return se
}))
}
const offset = payload.page * payload.limit
const { series, count } = await seriesFilters.getFilteredSeries(req.library, req.user, payload.filterBy, payload.sortBy, payload.sortDesc, include, payload.limit, offset)
payload.total = count
payload.results = series
res.json(payload)
}
@ -644,7 +624,7 @@ class LibraryController {
}
// TODO: Create paginated queries
let collections = await Database.models.collection.getOldCollectionsJsonExpanded(req.user, req.library.id, include)
let collections = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user, req.library.id, include)
payload.total = collections.length
@ -664,7 +644,7 @@ class LibraryController {
* @param {*} res
*/
async getUserPlaylistsForLibrary(req, res) {
let playlistsForUser = await Database.models.playlist.getPlaylistsForUserAndLibrary(req.user.id, req.library.id)
let playlistsForUser = await Database.playlistModel.getPlaylistsForUserAndLibrary(req.user.id, req.library.id)
playlistsForUser = await Promise.all(playlistsForUser.map(async p => p.getOldJsonExpanded()))
const payload = {
@ -685,8 +665,8 @@ class LibraryController {
/**
* GET: /api/libraries/:id/filterdata
* @param {*} req
* @param {*} res
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getLibraryFilterData(req, res) {
const filterData = await libraryFilters.getFilterData(req.library)
@ -694,44 +674,30 @@ class LibraryController {
}
/**
* GET: /api/libraries/:id/personalized2
* TODO: new endpoint
* @param {*} req
* @param {*} res
* GET: /api/libraries/:id/personalized
* Home page shelves
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getUserPersonalizedShelves(req, res) {
const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const shelves = await Database.models.libraryItem.getPersonalizedShelves(req.library, req.user, include, limitPerShelf)
const shelves = await Database.libraryItemModel.getPersonalizedShelves(req.library, req.user, include, limitPerShelf)
res.json(shelves)
}
/**
* GET: /api/libraries/:id/personalized
* TODO: remove after personalized2 is ready
* @param {*} req
* @param {*} res
*/
async getLibraryUserPersonalizedOptimal(req, res) {
const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const categories = await libraryHelpers.buildPersonalizedShelves(this, req.user, req.libraryItems, req.library, limitPerShelf, include)
res.json(categories)
}
/**
* POST: /api/libraries/order
* Change the display order of libraries
* @param {*} req
* @param {*} res
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async reorder(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error('[LibraryController] ReorderLibraries invalid user', req.user)
return res.sendStatus(403)
}
const libraries = await Database.models.library.getAllOldLibraries()
const libraries = await Database.libraryModel.getAllOldLibraries()
const orderdata = req.body
let hasUpdates = false
@ -759,99 +725,62 @@ class LibraryController {
})
}
// GET: Global library search
search(req, res) {
/**
* GET: /api/libraries/:id/search
* Search library items with query
* ?q=search
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async search(req, res) {
if (!req.query.q) {
return res.status(400).send('No query string')
}
const libraryItems = req.libraryItems
const maxResults = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
const limit = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
const query = req.query.q.trim().toLowerCase()
const itemMatches = []
const authorMatches = {}
const narratorMatches = {}
const seriesMatches = {}
const tagMatches = {}
libraryItems.forEach((li) => {
const queryResult = li.searchQuery(req.query.q)
if (queryResult.matchKey) {
itemMatches.push({
libraryItem: li.toJSONExpanded(),
matchKey: queryResult.matchKey,
matchText: queryResult.matchText
})
}
if (queryResult.series?.length) {
queryResult.series.forEach((se) => {
if (!seriesMatches[se.id]) {
const _series = Database.series.find(_se => _se.id === se.id)
if (_series) seriesMatches[se.id] = { series: _series.toJSON(), books: [li.toJSON()] }
} else {
seriesMatches[se.id].books.push(li.toJSON())
}
})
}
if (queryResult.authors?.length) {
queryResult.authors.forEach((au) => {
if (!authorMatches[au.id]) {
const _author = Database.authors.find(_au => _au.id === au.id)
if (_author) {
authorMatches[au.id] = _author.toJSON()
authorMatches[au.id].numBooks = 1
}
} else {
authorMatches[au.id].numBooks++
}
})
}
if (queryResult.tags?.length) {
queryResult.tags.forEach((tag) => {
if (!tagMatches[tag]) {
tagMatches[tag] = { name: tag, books: [li.toJSON()] }
} else {
tagMatches[tag].books.push(li.toJSON())
}
})
}
if (queryResult.narrators?.length) {
queryResult.narrators.forEach((narrator) => {
if (!narratorMatches[narrator]) {
narratorMatches[narrator] = { name: narrator, books: [li.toJSON()] }
} else {
narratorMatches[narrator].books.push(li.toJSON())
}
})
}
})
const itemKey = req.library.mediaType
const results = {
[itemKey]: itemMatches.slice(0, maxResults),
tags: Object.values(tagMatches).slice(0, maxResults),
authors: Object.values(authorMatches).slice(0, maxResults),
series: Object.values(seriesMatches).slice(0, maxResults),
narrators: Object.values(narratorMatches).slice(0, maxResults)
}
res.json(results)
const matches = await libraryItemFilters.search(req.user, req.library, query, limit)
res.json(matches)
}
/**
* GET: /api/libraries/:id/stats
* Get stats for library
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async stats(req, res) {
var libraryItems = req.libraryItems
var authorsWithCount = libraryHelpers.getAuthorsWithCount(libraryItems)
var genresWithCount = libraryHelpers.getGenresWithCount(libraryItems)
var durationStats = libraryHelpers.getItemDurationStats(libraryItems)
var sizeStats = libraryHelpers.getItemSizeStats(libraryItems)
var stats = {
totalItems: libraryItems.length,
totalAuthors: Object.keys(authorsWithCount).length,
totalGenres: Object.keys(genresWithCount).length,
totalDuration: durationStats.totalDuration,
longestItems: durationStats.longestItems,
numAudioTracks: durationStats.numAudioTracks,
totalSize: libraryHelpers.getLibraryItemsTotalSize(libraryItems),
largestItems: sizeStats.largestItems,
authorsWithCount,
genresWithCount
const stats = {
largestItems: await libraryItemFilters.getLargestItems(req.library.id, 10)
}
if (req.library.isBook) {
const authors = await authorFilters.getAuthorsWithCount(req.library.id)
const genres = await libraryItemsBookFilters.getGenresWithCount(req.library.id)
const bookStats = await libraryItemsBookFilters.getBookLibraryStats(req.library.id)
const longestBooks = await libraryItemsBookFilters.getLongestBooks(req.library.id, 10)
stats.totalAuthors = authors.length
stats.authorsWithCount = authors
stats.totalGenres = genres.length
stats.genresWithCount = genres
stats.totalItems = bookStats.totalItems
stats.longestItems = longestBooks
stats.totalSize = bookStats.totalSize
stats.totalDuration = bookStats.totalDuration
stats.numAudioTracks = bookStats.numAudioFiles
} else {
const genres = await libraryItemsPodcastFilters.getGenresWithCount(req.library.id)
const podcastStats = await libraryItemsPodcastFilters.getPodcastLibraryStats(req.library.id)
const longestPodcasts = await libraryItemsPodcastFilters.getLongestPodcasts(req.library.id, 10)
stats.totalGenres = genres.length
stats.genresWithCount = genres
stats.totalItems = podcastStats.totalItems
stats.longestItems = longestPodcasts
stats.totalSize = podcastStats.totalSize
stats.totalDuration = podcastStats.totalDuration
stats.numAudioTracks = podcastStats.numAudioFiles
}
res.json(stats)
}
@ -859,18 +788,18 @@ class LibraryController {
/**
* GET: /api/libraries/:id/authors
* Get authors for library
* @param {*} req
* @param {*} res
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getAuthors(req, res) {
const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user)
const authors = await Database.models.author.findAll({
const authors = await Database.authorModel.findAll({
where: {
libraryId: req.library.id
},
replacements,
include: {
model: Database.models.book,
model: Database.bookModel,
attributes: ['id', 'tags', 'explicit'],
where: bookWhere,
required: true,
@ -903,12 +832,12 @@ class LibraryController {
*/
async getNarrators(req, res) {
// Get all books with narrators
const booksWithNarrators = await Database.models.book.findAll({
const booksWithNarrators = await Database.bookModel.findAll({
where: Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('narrators')), {
[Sequelize.Op.gt]: 0
}),
include: {
model: Database.models.libraryItem,
model: Database.libraryItemModel,
attributes: ['id', 'libraryId'],
where: {
libraryId: req.library.id
@ -975,7 +904,7 @@ class LibraryController {
await libraryItem.media.update({
narrators: libraryItem.media.narrators
})
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(libraryItem)
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem)
itemsUpdated.push(oldLibraryItem)
}
@ -1015,7 +944,7 @@ class LibraryController {
await libraryItem.media.update({
narrators: libraryItem.media.narrators
})
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(libraryItem)
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem)
itemsUpdated.push(oldLibraryItem)
}
@ -1048,10 +977,16 @@ class LibraryController {
}
res.sendStatus(200)
await this.scanner.scan(req.library, options)
await Database.resetLibraryIssuesFilterData(req.library.id)
Logger.info('[LibraryController] Scan complete')
}
// GET: api/libraries/:id/recent-episode
/**
* GET: /api/libraries/:id/recent-episodes
* Used for latest page
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getRecentEpisodes(req, res) {
if (!req.library.isPodcast) {
return res.sendStatus(404)
@ -1059,40 +994,37 @@ class LibraryController {
const payload = {
episodes: [],
total: 0,
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
}
var allUnfinishedEpisodes = []
for (const libraryItem of req.libraryItems) {
const unfinishedEpisodes = libraryItem.media.episodes.filter(ep => {
const userProgress = req.user.getMediaProgress(libraryItem.id, ep.id)
return !userProgress || !userProgress.isFinished
}).map(_ep => {
const ep = _ep.toJSONExpanded()
ep.podcast = libraryItem.media.toJSONMinified()
ep.libraryItemId = libraryItem.id
ep.libraryId = libraryItem.libraryId
return ep
})
allUnfinishedEpisodes.push(...unfinishedEpisodes)
}
payload.total = allUnfinishedEpisodes.length
allUnfinishedEpisodes = sort(allUnfinishedEpisodes).desc(ep => ep.publishedAt)
if (payload.limit) {
var startIndex = payload.page * payload.limit
allUnfinishedEpisodes = allUnfinishedEpisodes.slice(startIndex, startIndex + payload.limit)
}
payload.episodes = allUnfinishedEpisodes
const offset = payload.page * payload.limit
payload.episodes = await libraryItemsPodcastFilters.getRecentEpisodes(req.user, req.library, payload.limit, offset)
res.json(payload)
}
getOPMLFile(req, res) {
const opmlText = this.podcastManager.generateOPMLFileText(req.libraryItems)
/**
* GET: /api/libraries/:id/opml
* Get OPML file for a podcast library
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getOPMLFile(req, res) {
const userPermissionPodcastWhere = libraryItemsPodcastFilters.getUserPermissionPodcastWhereQuery(req.user)
const podcasts = await Database.podcastModel.findAll({
attributes: ['id', 'feedURL', 'title', 'description', 'itunesPageURL', 'language'],
where: userPermissionPodcastWhere.podcastWhere,
replacements: userPermissionPodcastWhere.replacements,
include: {
model: Database.libraryItemModel,
attributes: ['id', 'libraryId'],
where: {
libraryId: req.library.id
}
}
})
const opmlText = this.podcastManager.generateOPMLFileText(podcasts)
res.type('application/xml')
res.send(opmlText)
}
@ -1109,7 +1041,7 @@ class LibraryController {
return res.sendStatus(403)
}
const library = await Database.models.library.getOldById(req.params.id)
const library = await Database.libraryModel.getOldById(req.params.id)
if (!library) {
return res.status(404).send('Library not found')
}
@ -1122,9 +1054,9 @@ class LibraryController {
/**
* Middleware that is not using libraryItems from memory
* @param {*} req
* @param {*} res
* @param {*} next
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
*/
async middlewareNew(req, res, next) {
if (!req.user.checkCanAccessLibrary(req.params.id)) {
@ -1132,7 +1064,7 @@ class LibraryController {
return res.sendStatus(403)
}
const library = await Database.models.library.getOldById(req.params.id)
const library = await Database.libraryModel.getOldById(req.params.id)
if (!library) {
return res.status(404).send('Library not found')
}

View file

@ -78,6 +78,7 @@ class LibraryItemController {
Logger.error(`[LibraryItemController] Failed to delete library item from file system at "${libraryItemPath}"`, error)
})
}
await Database.resetLibraryIssuesFilterData(req.libraryItem.libraryId)
res.sendStatus(200)
}
@ -124,7 +125,7 @@ class LibraryItemController {
// Book specific - Get all series being removed from this item
let seriesRemoved = []
if (libraryItem.isBook && mediaPayload.metadata?.series) {
const seriesIdsInUpdate = (mediaPayload.metadata?.series || []).map(se => se.id)
const seriesIdsInUpdate = mediaPayload.metadata.series?.map(se => se.id) || []
seriesRemoved = libraryItem.media.metadata.series.filter(se => !seriesIdsInUpdate.includes(se.id))
}
@ -313,7 +314,7 @@ class LibraryItemController {
return res.status(400).send('Invalid request body')
}
const itemsToDelete = await Database.models.libraryItem.getAllOldLibraryItems({
const itemsToDelete = await Database.libraryItemModel.getAllOldLibraryItems({
id: libraryItemIds
})
@ -332,6 +333,8 @@ class LibraryItemController {
})
}
}
await Database.resetLibraryIssuesFilterData(req.libraryItem.libraryId)
res.sendStatus(200)
}
@ -346,7 +349,7 @@ class LibraryItemController {
for (const updatePayload of updatePayloads) {
const mediaPayload = updatePayload.mediaPayload
const libraryItem = await Database.models.libraryItem.getOldById(updatePayload.id)
const libraryItem = await Database.libraryItemModel.getOldById(updatePayload.id)
if (!libraryItem) return null
await this.createAuthorsAndSeriesForItemUpdate(mediaPayload, libraryItem.libraryId)
@ -384,7 +387,7 @@ class LibraryItemController {
if (!libraryItemIds.length) {
return res.status(403).send('Invalid payload')
}
const libraryItems = await Database.models.libraryItem.getAllOldLibraryItems({
const libraryItems = await Database.libraryItemModel.getAllOldLibraryItems({
id: libraryItemIds
})
res.json({
@ -456,9 +459,11 @@ class LibraryItemController {
await this.scanner.scanLibraryItemByRequest(libraryItem)
}
}
await Database.resetLibraryIssuesFilterData(req.libraryItem.libraryId)
}
// POST: api/items/:id/scan (admin)
// POST: api/items/:id/scan
async scan(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryItemController] Non-admin user attempted to scan library item`, req.user)
@ -471,6 +476,7 @@ class LibraryItemController {
}
const result = await this.scanner.scanLibraryItemByRequest(req.libraryItem)
await Database.resetLibraryIssuesFilterData(req.libraryItem.libraryId)
res.json({
result: Object.keys(ScanResult).find(key => ScanResult[key] == result)
})
@ -694,7 +700,7 @@ class LibraryItemController {
}
async middleware(req, res, next) {
req.libraryItem = await Database.models.libraryItem.getOldById(req.params.id)
req.libraryItem = await Database.libraryItemModel.getOldById(req.params.id)
if (!req.libraryItem?.media) return res.sendStatus(404)
// Check user can access this library item

View file

@ -59,7 +59,7 @@ class MeController {
// PATCH: api/me/progress/:id
async createUpdateMediaProgress(req, res) {
const libraryItem = await Database.models.libraryItem.getOldById(req.params.id)
const libraryItem = await Database.libraryItemModel.getOldById(req.params.id)
if (!libraryItem) {
return res.status(404).send('Item not found')
}
@ -75,7 +75,7 @@ class MeController {
// PATCH: api/me/progress/:id/:episodeId
async createUpdateEpisodeMediaProgress(req, res) {
const episodeId = req.params.episodeId
const libraryItem = await Database.models.libraryItem.getOldById(req.params.id)
const libraryItem = await Database.libraryItemModel.getOldById(req.params.id)
if (!libraryItem) {
return res.status(404).send('Item not found')
}
@ -101,7 +101,7 @@ class MeController {
let shouldUpdate = false
for (const itemProgress of itemProgressPayloads) {
const libraryItem = await Database.models.libraryItem.getOldById(itemProgress.libraryItemId)
const libraryItem = await Database.libraryItemModel.getOldById(itemProgress.libraryItemId)
if (libraryItem) {
if (req.user.createUpdateMediaProgress(libraryItem, itemProgress, itemProgress.episodeId)) {
const mediaProgress = req.user.getMediaProgress(libraryItem.id, itemProgress.episodeId)
@ -122,7 +122,7 @@ class MeController {
// POST: api/me/item/:id/bookmark
async createBookmark(req, res) {
if (!await Database.models.libraryItem.checkExistsById(req.params.id)) return res.sendStatus(404)
if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404)
const { time, title } = req.body
const bookmark = req.user.createBookmark(req.params.id, time, title)
@ -133,7 +133,7 @@ class MeController {
// PATCH: api/me/item/:id/bookmark
async updateBookmark(req, res) {
if (!await Database.models.libraryItem.checkExistsById(req.params.id)) return res.sendStatus(404)
if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404)
const { time, title } = req.body
if (!req.user.findBookmark(req.params.id, time)) {
@ -151,7 +151,7 @@ class MeController {
// DELETE: api/me/item/:id/bookmark/:time
async removeBookmark(req, res) {
if (!await Database.models.libraryItem.checkExistsById(req.params.id)) return res.sendStatus(404)
if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404)
const time = Number(req.params.time)
if (isNaN(time)) return res.sendStatus(500)

View file

@ -38,7 +38,7 @@ class MiscController {
const libraryId = req.body.library
const folderId = req.body.folder
const library = await Database.models.library.getOldById(libraryId)
const library = await Database.libraryModel.getOldById(libraryId)
if (!library) {
return res.status(404).send(`Library not found with id ${libraryId}`)
}
@ -177,7 +177,7 @@ class MiscController {
}
const tags = []
const books = await Database.models.book.findAll({
const books = await Database.bookModel.findAll({
attributes: ['tags'],
where: Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('tags')), {
[Sequelize.Op.gt]: 0
@ -189,7 +189,7 @@ class MiscController {
}
}
const podcasts = await Database.models.podcast.findAll({
const podcasts = await Database.podcastModel.findAll({
attributes: ['tags'],
where: Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('tags')), {
[Sequelize.Op.gt]: 0
@ -248,7 +248,7 @@ class MiscController {
await libraryItem.media.update({
tags: libraryItem.media.tags
})
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(libraryItem)
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem)
SocketAuthority.emitter('item_updated', oldLibraryItem.toJSONExpanded())
numItemsUpdated++
}
@ -289,7 +289,7 @@ class MiscController {
await libraryItem.media.update({
tags: libraryItem.media.tags
})
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(libraryItem)
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem)
SocketAuthority.emitter('item_updated', oldLibraryItem.toJSONExpanded())
numItemsUpdated++
}
@ -311,7 +311,7 @@ class MiscController {
return res.sendStatus(404)
}
const genres = []
const books = await Database.models.book.findAll({
const books = await Database.bookModel.findAll({
attributes: ['genres'],
where: Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('genres')), {
[Sequelize.Op.gt]: 0
@ -323,7 +323,7 @@ class MiscController {
}
}
const podcasts = await Database.models.podcast.findAll({
const podcasts = await Database.podcastModel.findAll({
attributes: ['genres'],
where: Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('genres')), {
[Sequelize.Op.gt]: 0
@ -382,7 +382,7 @@ class MiscController {
await libraryItem.media.update({
genres: libraryItem.media.genres
})
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(libraryItem)
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem)
SocketAuthority.emitter('item_updated', oldLibraryItem.toJSONExpanded())
numItemsUpdated++
}
@ -423,7 +423,7 @@ class MiscController {
await libraryItem.media.update({
genres: libraryItem.media.genres
})
const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(libraryItem)
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(libraryItem)
SocketAuthority.emitter('item_updated', oldLibraryItem.toJSONExpanded())
numItemsUpdated++
}

View file

@ -22,11 +22,11 @@ class PlaylistController {
}
// Create Playlist record
const newPlaylist = await Database.models.playlist.createFromOld(oldPlaylist)
const newPlaylist = await Database.playlistModel.createFromOld(oldPlaylist)
// Lookup all library items in playlist
const libraryItemIds = oldPlaylist.items.map(i => i.libraryItemId).filter(i => i)
const libraryItemsInPlaylist = await Database.models.libraryItem.findAll({
const libraryItemsInPlaylist = await Database.libraryItemModel.findAll({
where: {
id: libraryItemIds
}
@ -62,7 +62,7 @@ class PlaylistController {
* @param {*} res
*/
async findAllForUser(req, res) {
const playlistsForUser = await Database.models.playlist.findAll({
const playlistsForUser = await Database.playlistModel.findAll({
where: {
userId: req.user.id
}
@ -106,7 +106,7 @@ class PlaylistController {
// If array of items is passed in then update order of playlist media items
const libraryItemIds = req.body.items?.map(i => i.libraryItemId).filter(i => i) || []
if (libraryItemIds.length) {
const libraryItems = await Database.models.libraryItem.findAll({
const libraryItems = await Database.libraryItemModel.findAll({
where: {
id: libraryItemIds
}
@ -173,14 +173,14 @@ class PlaylistController {
* @param {*} res
*/
async addItem(req, res) {
const oldPlaylist = await Database.models.playlist.getById(req.playlist.id)
const oldPlaylist = await Database.playlistModel.getById(req.playlist.id)
const itemToAdd = req.body
if (!itemToAdd.libraryItemId) {
return res.status(400).send('Request body has no libraryItemId')
}
const libraryItem = await Database.models.libraryItem.getOldById(itemToAdd.libraryItemId)
const libraryItem = await Database.libraryItemModel.getOldById(itemToAdd.libraryItemId)
if (!libraryItem) {
return res.status(400).send('Library item not found')
}
@ -217,7 +217,7 @@ class PlaylistController {
* @param {*} res
*/
async removeItem(req, res) {
const oldLibraryItem = await Database.models.libraryItem.getOldById(req.params.libraryItemId)
const oldLibraryItem = await Database.libraryItemModel.getOldById(req.params.libraryItemId)
if (!oldLibraryItem) {
return res.status(404).send('Library item not found')
}
@ -281,7 +281,7 @@ class PlaylistController {
}
// Find all library items
const libraryItems = await Database.models.libraryItem.findAll({
const libraryItems = await Database.libraryItemModel.findAll({
where: {
id: libraryItemIds
}
@ -345,7 +345,7 @@ class PlaylistController {
}
// Find all library items
const libraryItems = await Database.models.libraryItem.findAll({
const libraryItems = await Database.libraryItemModel.findAll({
where: {
id: libraryItemIds
}
@ -391,7 +391,7 @@ class PlaylistController {
* @param {*} res
*/
async createFromCollection(req, res) {
const collection = await Database.models.collection.findByPk(req.params.collectionId)
const collection = await Database.collectionModel.findByPk(req.params.collectionId)
if (!collection) {
return res.status(404).send('Collection not found')
}
@ -416,7 +416,7 @@ class PlaylistController {
})
// Create Playlist record
const newPlaylist = await Database.models.playlist.createFromOld(oldPlaylist)
const newPlaylist = await Database.playlistModel.createFromOld(oldPlaylist)
// Create PlaylistMediaItem records
const mediaItemsToAdd = []
@ -438,7 +438,7 @@ class PlaylistController {
async middleware(req, res, next) {
if (req.params.id) {
const playlist = await Database.models.playlist.findByPk(req.params.id)
const playlist = await Database.playlistModel.findByPk(req.params.id)
if (!playlist) {
return res.status(404).send('Playlist not found')
}

View file

@ -19,7 +19,7 @@ class PodcastController {
}
const payload = req.body
const library = await Database.models.library.getOldById(payload.libraryId)
const library = await Database.libraryModel.getOldById(payload.libraryId)
if (!library) {
Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`)
return res.status(404).send('Library not found')
@ -34,7 +34,7 @@ class PodcastController {
const podcastPath = filePathToPOSIX(payload.path)
// Check if a library item with this podcast folder exists already
const existingLibraryItem = (await Database.models.libraryItem.count({
const existingLibraryItem = (await Database.libraryItemModel.count({
where: {
path: podcastPath
}
@ -272,13 +272,13 @@ class PodcastController {
}
// Update/remove playlists that had this podcast episode
const playlistMediaItems = await Database.models.playlistMediaItem.findAll({
const playlistMediaItems = await Database.playlistMediaItemModel.findAll({
where: {
mediaItemId: episodeId
},
include: {
model: Database.models.playlist,
include: Database.models.playlistMediaItem
model: Database.playlistModel,
include: Database.playlistMediaItemModel
}
})
for (const pmi of playlistMediaItems) {
@ -297,7 +297,7 @@ class PodcastController {
}
// Remove media progress for this episode
const mediaProgressRemoved = await Database.models.mediaProgress.destroy({
const mediaProgressRemoved = await Database.mediaProgressModel.destroy({
where: {
mediaItemId: episode.id
}
@ -312,7 +312,7 @@ class PodcastController {
}
async middleware(req, res, next) {
const item = await Database.models.libraryItem.getOldById(req.params.id)
const item = await Database.libraryItemModel.getOldById(req.params.id)
if (!item?.media) return res.sendStatus(404)
if (!item.isPodcast) {

View file

@ -17,7 +17,7 @@ class RSSFeedController {
async openRSSFeedForItem(req, res) {
const options = req.body || {}
const item = await Database.models.libraryItem.getOldById(req.params.itemId)
const item = await Database.libraryItemModel.getOldById(req.params.itemId)
if (!item) return res.sendStatus(404)
// Check user can access this library item
@ -54,7 +54,7 @@ class RSSFeedController {
async openRSSFeedForCollection(req, res) {
const options = req.body || {}
const collection = await Database.models.collection.findByPk(req.params.collectionId)
const collection = await Database.collectionModel.findByPk(req.params.collectionId)
if (!collection) return res.sendStatus(404)
// Check request body options exist

View file

@ -49,7 +49,7 @@ class SessionController {
return res.sendStatus(404)
}
const minifiedUserObjects = await Database.models.user.getMinifiedUserObjects()
const minifiedUserObjects = await Database.userModel.getMinifiedUserObjects()
const openSessions = this.playbackSessionManager.sessions.map(se => {
return {
...se.toJSON(),

View file

@ -106,7 +106,7 @@ class ToolsController {
}
if (req.params.id) {
const item = await Database.models.libraryItem.getOldById(req.params.id)
const item = await Database.libraryItemModel.getOldById(req.params.id)
if (!item?.media) return res.sendStatus(404)
// Check user can access this library item

View file

@ -17,7 +17,7 @@ class UserController {
const includes = (req.query.include || '').split(',').map(i => i.trim())
// Minimal toJSONForBrowser does not include mediaProgress and bookmarks
const allUsers = await Database.models.user.getOldUsers()
const allUsers = await Database.userModel.getOldUsers()
const users = allUsers.map(u => u.toJSONForBrowser(hideRootToken, true))
if (includes.includes('latestSession')) {
@ -47,20 +47,20 @@ class UserController {
}
// Get user media progress with associated mediaItem
const mediaProgresses = await Database.models.mediaProgress.findAll({
const mediaProgresses = await Database.mediaProgressModel.findAll({
where: {
userId: req.reqUser.id
},
include: [
{
model: Database.models.book,
model: Database.bookModel,
attributes: ['id', 'title', 'coverPath', 'updatedAt']
},
{
model: Database.models.podcastEpisode,
model: Database.podcastEpisodeModel,
attributes: ['id', 'title'],
include: {
model: Database.models.podcast,
model: Database.podcastModel,
attributes: ['id', 'title', 'coverPath', 'updatedAt']
}
}
@ -92,7 +92,7 @@ class UserController {
const account = req.body
const username = account.username
const usernameExists = await Database.models.user.getUserByUsername(username)
const usernameExists = await Database.userModel.getUserByUsername(username)
if (usernameExists) {
return res.status(500).send('Username already taken')
}
@ -127,7 +127,7 @@ class UserController {
var shouldUpdateToken = false
if (account.username !== undefined && account.username !== user.username) {
const usernameExists = await Database.models.user.getUserByUsername(account.username)
const usernameExists = await Database.userModel.getUserByUsername(account.username)
if (usernameExists) {
return res.status(500).send('Username already taken')
}
@ -169,7 +169,7 @@ class UserController {
// Todo: check if user is logged in and cancel streams
// Remove user playlists
const userPlaylists = await Database.models.playlist.findAll({
const userPlaylists = await Database.playlistModel.findAll({
where: {
userId: user.id
}
@ -233,7 +233,7 @@ class UserController {
}
if (req.params.id) {
req.reqUser = await Database.models.user.getUserById(req.params.id)
req.reqUser = await Database.userModel.getUserById(req.params.id)
if (!req.reqUser) {
return res.sendStatus(404)
}