Remove unused functions, jsdoc updates, auto-formatting

This commit is contained in:
advplyr 2024-05-28 17:24:02 -05:00
parent 964ef910b6
commit 3fd290c518
21 changed files with 889 additions and 872 deletions

View File

@ -26,11 +26,6 @@ class Author extends Model {
this.createdAt this.createdAt
} }
static async getOldAuthors() {
const authors = await this.findAll()
return authors.map(au => au.getOldAuthor())
}
getOldAuthor() { getOldAuthor() {
return new oldAuthor({ return new oldAuthor({
id: this.id, id: this.id,
@ -85,7 +80,7 @@ class Author extends Model {
/** /**
* Get oldAuthor by id * Get oldAuthor by id
* @param {string} authorId * @param {string} authorId
* @returns {Promise<oldAuthor>} * @returns {Promise<oldAuthor>}
*/ */
static async getOldById(authorId) { static async getOldById(authorId) {
@ -96,7 +91,7 @@ class Author extends Model {
/** /**
* Check if author exists * Check if author exists
* @param {string} authorId * @param {string} authorId
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
static async checkExistsById(authorId) { static async checkExistsById(authorId) {
@ -106,60 +101,67 @@ class Author extends Model {
/** /**
* Get old author by name and libraryId. name case insensitive * Get old author by name and libraryId. name case insensitive
* TODO: Look for authors ignoring punctuation * TODO: Look for authors ignoring punctuation
* *
* @param {string} authorName * @param {string} authorName
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<oldAuthor>} * @returns {Promise<oldAuthor>}
*/ */
static async getOldByNameAndLibrary(authorName, libraryId) { static async getOldByNameAndLibrary(authorName, libraryId) {
const author = (await this.findOne({ const author = (
where: [ await this.findOne({
where(fn('lower', col('name')), authorName.toLowerCase()), where: [
{ where(fn('lower', col('name')), authorName.toLowerCase()),
libraryId {
} libraryId
] }
}))?.getOldAuthor() ]
})
)?.getOldAuthor()
return author return author
} }
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
name: DataTypes.STRING,
lastFirst: DataTypes.STRING,
asin: DataTypes.STRING,
description: DataTypes.TEXT,
imagePath: DataTypes.STRING
}, {
sequelize,
modelName: 'author',
indexes: [
{
fields: [{
name: 'name',
collate: 'NOCASE'
}]
}, },
// { name: DataTypes.STRING,
// fields: [{ lastFirst: DataTypes.STRING,
// name: 'lastFirst', asin: DataTypes.STRING,
// collate: 'NOCASE' description: DataTypes.TEXT,
// }] imagePath: DataTypes.STRING
// }, },
{ {
fields: ['libraryId'] sequelize,
} modelName: 'author',
] indexes: [
}) {
fields: [
{
name: 'name',
collate: 'NOCASE'
}
]
},
// {
// fields: [{
// name: 'lastFirst',
// collate: 'NOCASE'
// }]
// },
{
fields: ['libraryId']
}
]
}
)
const { library } = sequelize.models const { library } = sequelize.models
library.hasMany(Author, { library.hasMany(Author, {

View File

@ -21,13 +21,13 @@ const Logger = require('../Logger')
/** /**
* @typedef SeriesExpandedProperties * @typedef SeriesExpandedProperties
* @property {{sequence:string}} bookSeries * @property {{sequence:string}} bookSeries
* *
* @typedef {import('./Series') & SeriesExpandedProperties} SeriesExpanded * @typedef {import('./Series') & SeriesExpandedProperties} SeriesExpanded
* *
* @typedef BookExpandedProperties * @typedef BookExpandedProperties
* @property {import('./Author')[]} authors * @property {import('./Author')[]} authors
* @property {SeriesExpanded[]} series * @property {SeriesExpanded[]} series
* *
* @typedef {Book & BookExpandedProperties} BookExpanded * @typedef {Book & BookExpandedProperties} BookExpanded
*/ */
@ -112,29 +112,31 @@ class Book extends Model {
const bookExpanded = libraryItemExpanded.media const bookExpanded = libraryItemExpanded.media
let authors = [] let authors = []
if (bookExpanded.authors?.length) { if (bookExpanded.authors?.length) {
authors = bookExpanded.authors.map(au => { authors = bookExpanded.authors.map((au) => {
return { return {
id: au.id, id: au.id,
name: au.name name: au.name
} }
}) })
} else if (bookExpanded.bookAuthors?.length) { } else if (bookExpanded.bookAuthors?.length) {
authors = bookExpanded.bookAuthors.map(ba => { authors = bookExpanded.bookAuthors
if (ba.author) { .map((ba) => {
return { if (ba.author) {
id: ba.author.id, return {
name: ba.author.name id: ba.author.id,
name: ba.author.name
}
} else {
Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba)
return null
} }
} else { })
Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba) .filter((a) => a)
return null
}
}).filter(a => a)
} }
let series = [] let series = []
if (bookExpanded.series?.length) { if (bookExpanded.series?.length) {
series = bookExpanded.series.map(se => { series = bookExpanded.series.map((se) => {
return { return {
id: se.id, id: se.id,
name: se.name, name: se.name,
@ -142,18 +144,20 @@ class Book extends Model {
} }
}) })
} else if (bookExpanded.bookSeries?.length) { } else if (bookExpanded.bookSeries?.length) {
series = bookExpanded.bookSeries.map(bs => { series = bookExpanded.bookSeries
if (bs.series) { .map((bs) => {
return { if (bs.series) {
id: bs.series.id, return {
name: bs.series.name, id: bs.series.id,
sequence: bs.sequence name: bs.series.name,
sequence: bs.sequence
}
} else {
Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs)
return null
} }
} else { })
Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs) .filter((s) => s)
return null
}
}).filter(s => s)
} }
return { return {
@ -185,7 +189,7 @@ class Book extends Model {
} }
/** /**
* @param {object} oldBook * @param {object} oldBook
* @returns {boolean} true if updated * @returns {boolean} true if updated
*/ */
static saveFromOld(oldBook) { static saveFromOld(oldBook) {
@ -194,10 +198,12 @@ class Book extends Model {
where: { where: {
id: book.id id: book.id
} }
}).then(result => result[0] > 0).catch((error) => {
Logger.error(`[Book] Failed to save book ${book.id}`, error)
return false
}) })
.then((result) => result[0] > 0)
.catch((error) => {
Logger.error(`[Book] Failed to save book ${book.id}`, error)
return false
})
} }
static getFromOld(oldBook) { static getFromOld(oldBook) {
@ -219,7 +225,7 @@ class Book extends Model {
ebookFile: oldBook.ebookFile?.toJSON() || null, ebookFile: oldBook.ebookFile?.toJSON() || null,
coverPath: oldBook.coverPath, coverPath: oldBook.coverPath,
duration: oldBook.duration, duration: oldBook.duration,
audioFiles: oldBook.audioFiles?.map(af => af.toJSON()) || [], audioFiles: oldBook.audioFiles?.map((af) => af.toJSON()) || [],
chapters: oldBook.chapters, chapters: oldBook.chapters,
tags: oldBook.tags, tags: oldBook.tags,
genres: oldBook.metadata.genres genres: oldBook.metadata.genres
@ -229,12 +235,12 @@ class Book extends Model {
getAbsMetadataJson() { getAbsMetadataJson() {
return { return {
tags: this.tags || [], tags: this.tags || [],
chapters: this.chapters?.map(c => ({ ...c })) || [], chapters: this.chapters?.map((c) => ({ ...c })) || [],
title: this.title, title: this.title,
subtitle: this.subtitle, subtitle: this.subtitle,
authors: this.authors.map(a => a.name), authors: this.authors.map((a) => a.name),
narrators: this.narrators, narrators: this.narrators,
series: this.series.map(se => { series: this.series.map((se) => {
const sequence = se.bookSeries?.sequence || '' const sequence = se.bookSeries?.sequence || ''
if (!sequence) return se.name if (!sequence) return se.name
return `${se.name} #${sequence}` return `${se.name} #${sequence}`
@ -254,61 +260,66 @@ class Book extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
title: DataTypes.STRING, },
titleIgnorePrefix: DataTypes.STRING, title: DataTypes.STRING,
subtitle: DataTypes.STRING, titleIgnorePrefix: DataTypes.STRING,
publishedYear: DataTypes.STRING, subtitle: DataTypes.STRING,
publishedDate: DataTypes.STRING, publishedYear: DataTypes.STRING,
publisher: DataTypes.STRING, publishedDate: DataTypes.STRING,
description: DataTypes.TEXT, publisher: DataTypes.STRING,
isbn: DataTypes.STRING, description: DataTypes.TEXT,
asin: DataTypes.STRING, isbn: DataTypes.STRING,
language: DataTypes.STRING, asin: DataTypes.STRING,
explicit: DataTypes.BOOLEAN, language: DataTypes.STRING,
abridged: DataTypes.BOOLEAN, explicit: DataTypes.BOOLEAN,
coverPath: DataTypes.STRING, abridged: DataTypes.BOOLEAN,
duration: DataTypes.FLOAT, coverPath: DataTypes.STRING,
duration: DataTypes.FLOAT,
narrators: DataTypes.JSON, narrators: DataTypes.JSON,
audioFiles: DataTypes.JSON, audioFiles: DataTypes.JSON,
ebookFile: DataTypes.JSON, ebookFile: DataTypes.JSON,
chapters: DataTypes.JSON, chapters: DataTypes.JSON,
tags: DataTypes.JSON, tags: DataTypes.JSON,
genres: DataTypes.JSON genres: DataTypes.JSON
}, { },
sequelize, {
modelName: 'book', sequelize,
indexes: [ modelName: 'book',
{ indexes: [
fields: [{ {
name: 'title', fields: [
collate: 'NOCASE' {
}] name: 'title',
}, collate: 'NOCASE'
// { }
// fields: [{ ]
// name: 'titleIgnorePrefix', },
// collate: 'NOCASE' // {
// }] // fields: [{
// }, // name: 'titleIgnorePrefix',
{ // collate: 'NOCASE'
fields: ['publishedYear'] // }]
}, // },
// { {
// fields: ['duration'] fields: ['publishedYear']
// } }
] // {
}) // fields: ['duration']
// }
]
}
)
} }
} }
module.exports = Book module.exports = Book

View File

@ -25,21 +25,24 @@ class BookAuthor extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
}
},
{
sequelize,
modelName: 'bookAuthor',
timestamps: true,
updatedAt: false
} }
}, { )
sequelize,
modelName: 'bookAuthor',
timestamps: true,
updatedAt: false
})
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
@ -58,4 +61,4 @@ class BookAuthor extends Model {
BookAuthor.belongsTo(author) BookAuthor.belongsTo(author)
} }
} }
module.exports = BookAuthor module.exports = BookAuthor

View File

@ -27,22 +27,25 @@ class BookSeries extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
sequence: DataTypes.STRING
}, },
sequence: DataTypes.STRING {
}, { sequelize,
sequelize, modelName: 'bookSeries',
modelName: 'bookSeries', timestamps: true,
timestamps: true, updatedAt: false
updatedAt: false }
}) )
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
@ -62,4 +65,4 @@ class BookSeries extends Model {
} }
} }
module.exports = BookSeries module.exports = BookSeries

View File

@ -2,7 +2,6 @@ const { DataTypes, Model, Sequelize } = require('sequelize')
const oldCollection = require('../objects/Collection') const oldCollection = require('../objects/Collection')
class Collection extends Model { class Collection extends Model {
constructor(values, options) { constructor(values, options) {
super(values, options) super(values, options)
@ -20,27 +19,13 @@ class Collection extends Model {
/** @type {Date} */ /** @type {Date} */
this.createdAt this.createdAt
} }
/**
* Get all old collections
* @returns {Promise<oldCollection[]>}
*/
static async getOldCollections() {
const collections = await this.findAll({
include: {
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
return collections.map(c => this.getOldCollection(c))
}
/** /**
* Get all old collections toJSONExpanded, items filtered for user permissions * Get all old collections toJSONExpanded, items filtered for user permissions
* @param {[oldUser]} user * @param {oldUser} [user]
* @param {[string]} libraryId * @param {string} [libraryId]
* @param {[string[]]} include * @param {string[]} [include]
* @returns {Promise<object[]>} oldCollection.toJSONExpanded * @returns {Promise<oldCollection[]>} oldCollection.toJSONExpanded
*/ */
static async getOldCollectionsJsonExpanded(user, libraryId, include) { static async getOldCollectionsJsonExpanded(user, libraryId, include) {
let collectionWhere = null let collectionWhere = null
@ -78,8 +63,7 @@ class Collection extends Model {
through: { through: {
attributes: ['sequence'] attributes: ['sequence']
} }
}, }
] ]
}, },
...collectionIncludes ...collectionIncludes
@ -87,11 +71,84 @@ class Collection extends Model {
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']] order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
}) })
// TODO: Handle user permission restrictions on initial query // TODO: Handle user permission restrictions on initial query
return collections.map(c => { return collections
const oldCollection = this.getOldCollection(c) .map((c) => {
const oldCollection = this.getOldCollection(c)
// Filter books using user permissions // Filter books using user permissions
const books = c.books?.filter(b => { const books =
c.books?.filter((b) => {
if (user) {
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
return false
}
if (b.explicit === true && !user.canAccessExplicitContent) {
return false
}
}
return true
}) || []
// Map to library items
const libraryItems = books.map((b) => {
const libraryItem = b.libraryItem
delete b.libraryItem
libraryItem.media = b
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
})
// Users with restricted permissions will not see this collection
if (!books.length && oldCollection.books.length) {
return null
}
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
// Map feed if found
if (c.feeds?.length) {
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(c.feeds[0])
}
return collectionExpanded
})
.filter((c) => c)
}
/**
* Get old collection toJSONExpanded, items filtered for user permissions
* @param {oldUser} [user]
* @param {string[]} [include]
* @returns {Promise<oldCollection>} oldCollection.toJSONExpanded
*/
async getOldJsonExpanded(user, include) {
this.books =
(await this.getBooks({
include: [
{
model: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.author,
through: {
attributes: []
}
},
{
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
}
],
order: [Sequelize.literal('`collectionBook.order` ASC')]
})) || []
const oldCollection = this.sequelize.models.collection.getOldCollection(this)
// Filter books using user permissions
// TODO: Handle user permission restrictions on initial query
const books =
this.books?.filter((b) => {
if (user) { if (user) {
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) { if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
return false return false
@ -103,77 +160,8 @@ class Collection extends Model {
return true return true
}) || [] }) || []
// Map to library items
const libraryItems = books.map(b => {
const libraryItem = b.libraryItem
delete b.libraryItem
libraryItem.media = b
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
})
// Users with restricted permissions will not see this collection
if (!books.length && oldCollection.books.length) {
return null
}
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
// Map feed if found
if (c.feeds?.length) {
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(c.feeds[0])
}
return collectionExpanded
}).filter(c => c)
}
/**
* Get old collection toJSONExpanded, items filtered for user permissions
* @param {[oldUser]} user
* @param {[string[]]} include
* @returns {Promise<object>} oldCollection.toJSONExpanded
*/
async getOldJsonExpanded(user, include) {
this.books = await this.getBooks({
include: [
{
model: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.author,
through: {
attributes: []
}
},
{
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
},
],
order: [Sequelize.literal('`collectionBook.order` ASC')]
}) || []
const oldCollection = this.sequelize.models.collection.getOldCollection(this)
// Filter books using user permissions
// TODO: Handle user permission restrictions on initial query
const books = this.books?.filter(b => {
if (user) {
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
return false
}
if (b.explicit === true && !user.canAccessExplicitContent) {
return false
}
}
return true
}) || []
// Map to library items // Map to library items
const libraryItems = books.map(b => { const libraryItems = books.map((b) => {
const libraryItem = b.libraryItem const libraryItem = b.libraryItem
delete b.libraryItem delete b.libraryItem
libraryItem.media = b libraryItem.media = b
@ -199,11 +187,11 @@ class Collection extends Model {
/** /**
* Get old collection from Collection * Get old collection from Collection
* @param {Collection} collectionExpanded * @param {Collection} collectionExpanded
* @returns {oldCollection} * @returns {oldCollection}
*/ */
static getOldCollection(collectionExpanded) { static getOldCollection(collectionExpanded) {
const libraryItemIds = collectionExpanded.books?.map(b => b.libraryItem?.id || null).filter(lid => lid) || [] const libraryItemIds = collectionExpanded.books?.map((b) => b.libraryItem?.id || null).filter((lid) => lid) || []
return new oldCollection({ return new oldCollection({
id: collectionExpanded.id, id: collectionExpanded.id,
libraryId: collectionExpanded.libraryId, libraryId: collectionExpanded.libraryId,
@ -215,6 +203,11 @@ class Collection extends Model {
}) })
} }
/**
*
* @param {oldCollection} oldCollection
* @returns {Promise<Collection>}
*/
static createFromOld(oldCollection) { static createFromOld(oldCollection) {
const collection = this.getFromOld(oldCollection) const collection = this.getFromOld(oldCollection)
return this.create(collection) return this.create(collection)
@ -239,7 +232,7 @@ class Collection extends Model {
/** /**
* Get old collection by id * Get old collection by id
* @param {string} collectionId * @param {string} collectionId
* @returns {Promise<oldCollection|null>} returns null if not found * @returns {Promise<oldCollection|null>} returns null if not found
*/ */
static async getOldById(collectionId) { static async getOldById(collectionId) {
@ -260,34 +253,34 @@ class Collection extends Model {
* @returns {Promise<oldCollection>} * @returns {Promise<oldCollection>}
*/ */
async getOld() { async getOld() {
this.books = await this.getBooks({ this.books =
include: [ (await this.getBooks({
{ include: [
model: this.sequelize.models.libraryItem {
}, model: this.sequelize.models.libraryItem
{ },
model: this.sequelize.models.author, {
through: { model: this.sequelize.models.author,
attributes: [] through: {
attributes: []
}
},
{
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
} }
}, ],
{ order: [Sequelize.literal('`collectionBook.order` ASC')]
model: this.sequelize.models.series, })) || []
through: {
attributes: ['sequence']
}
},
],
order: [Sequelize.literal('`collectionBook.order` ASC')]
}) || []
return this.sequelize.models.collection.getOldCollection(this) return this.sequelize.models.collection.getOldCollection(this)
} }
/** /**
* Remove all collections belonging to library * Remove all collections belonging to library
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<number>} number of collections destroyed * @returns {Promise<number>} number of collections destroyed
*/ */
static async removeAllForLibrary(libraryId) { static async removeAllForLibrary(libraryId) {
@ -299,38 +292,26 @@ class Collection extends Model {
}) })
} }
static async getAllForBook(bookId) {
const collections = await this.findAll({
include: {
model: this.sequelize.models.book,
where: {
id: bookId
},
required: true,
include: this.sequelize.models.libraryItem
},
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
return collections.map(c => this.getOldCollection(c))
}
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
description: DataTypes.TEXT
}, },
name: DataTypes.STRING, {
description: DataTypes.TEXT sequelize,
}, { modelName: 'collection'
sequelize, }
modelName: 'collection' )
})
const { library } = sequelize.models const { library } = sequelize.models
@ -339,4 +320,4 @@ class Collection extends Model {
} }
} }
module.exports = Collection module.exports = Collection

View File

@ -26,19 +26,22 @@ class CollectionBook extends Model {
} }
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
order: DataTypes.INTEGER
}, },
order: DataTypes.INTEGER {
}, { sequelize,
sequelize, timestamps: true,
timestamps: true, updatedAt: false,
updatedAt: false, modelName: 'collectionBook'
modelName: 'collectionBook' }
}) )
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
@ -58,4 +61,4 @@ class CollectionBook extends Model {
} }
} }
module.exports = CollectionBook module.exports = CollectionBook

View File

@ -114,26 +114,29 @@ class Device extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
deviceId: DataTypes.STRING,
clientName: DataTypes.STRING, // e.g. Abs Web, Abs Android
clientVersion: DataTypes.STRING, // e.g. Server version or mobile version
ipAddress: DataTypes.STRING,
deviceName: DataTypes.STRING, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3
deviceVersion: DataTypes.STRING, // e.g. Browser version or Android SDK
extraData: DataTypes.JSON
}, },
deviceId: DataTypes.STRING, {
clientName: DataTypes.STRING, // e.g. Abs Web, Abs Android sequelize,
clientVersion: DataTypes.STRING, // e.g. Server version or mobile version modelName: 'device'
ipAddress: DataTypes.STRING, }
deviceName: DataTypes.STRING, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3 )
deviceVersion: DataTypes.STRING, // e.g. Browser version or Android SDK
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'device'
})
const { user } = sequelize.models const { user } = sequelize.models
@ -144,4 +147,4 @@ class Device extends Model {
} }
} }
module.exports = Device module.exports = Device

View File

@ -58,7 +58,7 @@ class Feed extends Model {
model: this.sequelize.models.feedEpisode model: this.sequelize.models.feedEpisode
} }
}) })
return feeds.map(f => this.getOldFeed(f)) return feeds.map((f) => this.getOldFeed(f))
} }
/** /**
@ -117,7 +117,7 @@ class Feed extends Model {
entityType: 'libraryItem' entityType: 'libraryItem'
} }
}) })
return feeds.map(f => f.entityId).filter(f => f) || [] return feeds.map((f) => f.entityId).filter((f) => f) || []
} }
/** /**
@ -179,7 +179,7 @@ class Feed extends Model {
// Remove and update existing feed episodes // Remove and update existing feed episodes
for (const feedEpisode of existingFeed.feedEpisodes) { for (const feedEpisode of existingFeed.feedEpisodes) {
const oldFeedEpisode = oldFeedEpisodes.find(ep => ep.id === feedEpisode.id) const oldFeedEpisode = oldFeedEpisodes.find((ep) => ep.id === feedEpisode.id)
// Episode removed // Episode removed
if (!oldFeedEpisode) { if (!oldFeedEpisode) {
feedEpisode.destroy() feedEpisode.destroy()
@ -200,7 +200,7 @@ class Feed extends Model {
// Add new feed episodes // Add new feed episodes
for (const episode of oldFeedEpisodes) { for (const episode of oldFeedEpisodes) {
if (!existingFeed.feedEpisodes.some(fe => fe.id === episode.id)) { if (!existingFeed.feedEpisodes.some((fe) => fe.id === episode.id)) {
await this.sequelize.models.feedEpisode.createFromOld(feedObj.id, episode) await this.sequelize.models.feedEpisode.createFromOld(feedObj.id, episode)
hasUpdates = true hasUpdates = true
} }
@ -258,41 +258,44 @@ class Feed extends Model {
/** /**
* Initialize model * Initialize model
* *
* Polymorphic association: Feeds can be created from LibraryItem, Collection, Playlist or Series * Polymorphic association: Feeds can be created from LibraryItem, Collection, Playlist or Series
* @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ * @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
* *
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
slug: DataTypes.STRING,
entityType: DataTypes.STRING,
entityId: DataTypes.UUIDV4,
entityUpdatedAt: DataTypes.DATE,
serverAddress: DataTypes.STRING,
feedURL: DataTypes.STRING,
imageURL: DataTypes.STRING,
siteURL: DataTypes.STRING,
title: DataTypes.STRING,
description: DataTypes.TEXT,
author: DataTypes.STRING,
podcastType: DataTypes.STRING,
language: DataTypes.STRING,
ownerName: DataTypes.STRING,
ownerEmail: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
preventIndexing: DataTypes.BOOLEAN,
coverPath: DataTypes.STRING
}, },
slug: DataTypes.STRING, {
entityType: DataTypes.STRING, sequelize,
entityId: DataTypes.UUIDV4, modelName: 'feed'
entityUpdatedAt: DataTypes.DATE, }
serverAddress: DataTypes.STRING, )
feedURL: DataTypes.STRING,
imageURL: DataTypes.STRING,
siteURL: DataTypes.STRING,
title: DataTypes.STRING,
description: DataTypes.TEXT,
author: DataTypes.STRING,
podcastType: DataTypes.STRING,
language: DataTypes.STRING,
ownerName: DataTypes.STRING,
ownerEmail: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
preventIndexing: DataTypes.BOOLEAN,
coverPath: DataTypes.STRING
}, {
sequelize,
modelName: 'feed'
})
const { user, libraryItem, collection, series, playlist } = sequelize.models const { user, libraryItem, collection, series, playlist } = sequelize.models
@ -335,7 +338,7 @@ class Feed extends Model {
}) })
Feed.belongsTo(playlist, { foreignKey: 'entityId', constraints: false }) Feed.belongsTo(playlist, { foreignKey: 'entityId', constraints: false })
Feed.addHook('afterFind', findResult => { Feed.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
@ -368,4 +371,4 @@ class Feed extends Model {
} }
} }
module.exports = Feed module.exports = Feed

View File

@ -65,9 +65,9 @@ class FeedEpisode extends Model {
/** /**
* Create feed episode from old model * Create feed episode from old model
* *
* @param {string} feedId * @param {string} feedId
* @param {Object} oldFeedEpisode * @param {Object} oldFeedEpisode
* @returns {Promise<FeedEpisode>} * @returns {Promise<FeedEpisode>}
*/ */
static createFromOld(feedId, oldFeedEpisode) { static createFromOld(feedId, oldFeedEpisode) {
@ -98,33 +98,36 @@ class FeedEpisode extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
title: DataTypes.STRING,
author: DataTypes.STRING,
description: DataTypes.TEXT,
siteURL: DataTypes.STRING,
enclosureURL: DataTypes.STRING,
enclosureType: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT,
pubDate: DataTypes.STRING,
season: DataTypes.STRING,
episode: DataTypes.STRING,
episodeType: DataTypes.STRING,
duration: DataTypes.FLOAT,
filePath: DataTypes.STRING,
explicit: DataTypes.BOOLEAN
}, },
title: DataTypes.STRING, {
author: DataTypes.STRING, sequelize,
description: DataTypes.TEXT, modelName: 'feedEpisode'
siteURL: DataTypes.STRING, }
enclosureURL: DataTypes.STRING, )
enclosureType: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT,
pubDate: DataTypes.STRING,
season: DataTypes.STRING,
episode: DataTypes.STRING,
episodeType: DataTypes.STRING,
duration: DataTypes.FLOAT,
filePath: DataTypes.STRING,
explicit: DataTypes.BOOLEAN
}, {
sequelize,
modelName: 'feedEpisode'
})
const { feed } = sequelize.models const { feed } = sequelize.models
@ -135,4 +138,4 @@ class FeedEpisode extends Model {
} }
} }
module.exports = FeedEpisode module.exports = FeedEpisode

View File

@ -10,7 +10,7 @@ const oldLibrary = require('../objects/Library')
* @property {boolean} skipMatchingMediaWithIsbn * @property {boolean} skipMatchingMediaWithIsbn
* @property {string} autoScanCronExpression * @property {string} autoScanCronExpression
* @property {boolean} audiobooksOnly * @property {boolean} audiobooksOnly
* @property {boolean} hideSingleBookSeries Do not show series that only have 1 book * @property {boolean} hideSingleBookSeries Do not show series that only have 1 book
* @property {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read * @property {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read
* @property {string[]} metadataPrecedence * @property {string[]} metadataPrecedence
*/ */
@ -54,16 +54,16 @@ class Library extends Model {
include: this.sequelize.models.libraryFolder, include: this.sequelize.models.libraryFolder,
order: [['displayOrder', 'ASC']] order: [['displayOrder', 'ASC']]
}) })
return libraries.map(lib => this.getOldLibrary(lib)) return libraries.map((lib) => this.getOldLibrary(lib))
} }
/** /**
* Convert expanded Library to oldLibrary * Convert expanded Library to oldLibrary
* @param {Library} libraryExpanded * @param {Library} libraryExpanded
* @returns {Promise<oldLibrary>} * @returns {Promise<oldLibrary>}
*/ */
static getOldLibrary(libraryExpanded) { static getOldLibrary(libraryExpanded) {
const folders = libraryExpanded.libraryFolders.map(folder => { const folders = libraryExpanded.libraryFolders.map((folder) => {
return { return {
id: folder.id, id: folder.id,
fullPath: folder.path, fullPath: folder.path,
@ -90,13 +90,13 @@ class Library extends Model {
} }
/** /**
* @param {object} oldLibrary * @param {object} oldLibrary
* @returns {Library|null} * @returns {Library|null}
*/ */
static async createFromOld(oldLibrary) { static async createFromOld(oldLibrary) {
const library = this.getFromOld(oldLibrary) const library = this.getFromOld(oldLibrary)
library.libraryFolders = oldLibrary.folders.map(folder => { library.libraryFolders = oldLibrary.folders.map((folder) => {
return { return {
id: folder.id, id: folder.id,
path: folder.fullPath path: folder.fullPath
@ -113,8 +113,8 @@ class Library extends Model {
/** /**
* Update library and library folders * Update library and library folders
* @param {object} oldLibrary * @param {object} oldLibrary
* @returns * @returns
*/ */
static async updateFromOld(oldLibrary) { static async updateFromOld(oldLibrary) {
const existingLibrary = await this.findByPk(oldLibrary.id, { const existingLibrary = await this.findByPk(oldLibrary.id, {
@ -127,7 +127,7 @@ class Library extends Model {
const library = this.getFromOld(oldLibrary) const library = this.getFromOld(oldLibrary)
const libraryFolders = oldLibrary.folders.map(folder => { const libraryFolders = oldLibrary.folders.map((folder) => {
return { return {
id: folder.id, id: folder.id,
path: folder.fullPath, path: folder.fullPath,
@ -135,7 +135,7 @@ class Library extends Model {
} }
}) })
for (const libraryFolder of libraryFolders) { for (const libraryFolder of libraryFolders) {
const existingLibraryFolder = existingLibrary.libraryFolders.find(lf => lf.id === libraryFolder.id) const existingLibraryFolder = existingLibrary.libraryFolders.find((lf) => lf.id === libraryFolder.id)
if (!existingLibraryFolder) { if (!existingLibraryFolder) {
await this.sequelize.models.libraryFolder.create(libraryFolder) await this.sequelize.models.libraryFolder.create(libraryFolder)
} else if (existingLibraryFolder.path !== libraryFolder.path) { } else if (existingLibraryFolder.path !== libraryFolder.path) {
@ -143,7 +143,7 @@ class Library extends Model {
} }
} }
const libraryFoldersRemoved = existingLibrary.libraryFolders.filter(lf => !libraryFolders.some(_lf => _lf.id === lf.id)) const libraryFoldersRemoved = existingLibrary.libraryFolders.filter((lf) => !libraryFolders.some((_lf) => _lf.id === lf.id))
for (const existingLibraryFolder of libraryFoldersRemoved) { for (const existingLibraryFolder of libraryFoldersRemoved) {
await existingLibraryFolder.destroy() await existingLibraryFolder.destroy()
} }
@ -177,8 +177,8 @@ class Library extends Model {
/** /**
* Destroy library by id * Destroy library by id
* @param {string} libraryId * @param {string} libraryId
* @returns * @returns
*/ */
static removeById(libraryId) { static removeById(libraryId) {
return this.destroy({ return this.destroy({
@ -197,12 +197,12 @@ class Library extends Model {
attributes: ['id', 'displayOrder'], attributes: ['id', 'displayOrder'],
order: [['displayOrder', 'ASC']] order: [['displayOrder', 'ASC']]
}) })
return libraries.map(l => l.id) return libraries.map((l) => l.id)
} }
/** /**
* Find Library by primary key & return oldLibrary * Find Library by primary key & return oldLibrary
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<oldLibrary|null>} Returns null if not found * @returns {Promise<oldLibrary|null>} Returns null if not found
*/ */
static async getOldById(libraryId) { static async getOldById(libraryId) {
@ -244,29 +244,32 @@ class Library extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
displayOrder: DataTypes.INTEGER,
icon: DataTypes.STRING,
mediaType: DataTypes.STRING,
provider: DataTypes.STRING,
lastScan: DataTypes.DATE,
lastScanVersion: DataTypes.STRING,
settings: DataTypes.JSON,
extraData: DataTypes.JSON
}, },
name: DataTypes.STRING, {
displayOrder: DataTypes.INTEGER, sequelize,
icon: DataTypes.STRING, modelName: 'library'
mediaType: DataTypes.STRING, }
provider: DataTypes.STRING, )
lastScan: DataTypes.DATE,
lastScanVersion: DataTypes.STRING,
settings: DataTypes.JSON,
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'library'
})
} }
} }
module.exports = Library module.exports = Library

View File

@ -16,33 +16,25 @@ class LibraryFolder extends Model {
this.updatedAt this.updatedAt
} }
/**
* Gets all library folder path strings
* @returns {Promise<string[]>} array of library folder paths
*/
static async getAllLibraryFolderPaths() {
const libraryFolders = await this.findAll({
attributes: ['path']
})
return libraryFolders.map(l => l.path)
}
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
path: DataTypes.STRING
}, },
path: DataTypes.STRING {
}, { sequelize,
sequelize, modelName: 'libraryFolder'
modelName: 'libraryFolder' }
}) )
const { library } = sequelize.models const { library } = sequelize.models
library.hasMany(LibraryFolder, { library.hasMany(LibraryFolder, {
@ -52,4 +44,4 @@ class LibraryFolder extends Model {
} }
} }
module.exports = LibraryFolder module.exports = LibraryFolder

View File

@ -21,8 +21,8 @@ const Podcast = require('./Podcast')
/** /**
* @typedef LibraryItemExpandedProperties * @typedef LibraryItemExpandedProperties
* @property {Book.BookExpanded|Podcast.PodcastExpanded} media * @property {Book.BookExpanded|Podcast.PodcastExpanded} media
* *
* @typedef {LibraryItem & LibraryItemExpandedProperties} LibraryItemExpanded * @typedef {LibraryItem & LibraryItemExpandedProperties} LibraryItemExpanded
*/ */
@ -77,7 +77,7 @@ class LibraryItem extends Model {
/** /**
* Gets library items partially expanded, not including podcast episodes * Gets library items partially expanded, not including podcast episodes
* @todo temporary solution * @todo temporary solution
* *
* @param {number} offset * @param {number} offset
* @param {number} limit * @param {number} limit
* @returns {Promise<LibraryItem[]>} LibraryItem * @returns {Promise<LibraryItem[]>} LibraryItem
@ -154,13 +154,13 @@ class LibraryItem extends Model {
} }
] ]
}) })
return libraryItems.map(ti => this.getOldLibraryItem(ti)) return libraryItems.map((ti) => this.getOldLibraryItem(ti))
} }
/** /**
* Convert an expanded LibraryItem into an old library item * Convert an expanded LibraryItem into an old library item
* *
* @param {Model<LibraryItem>} libraryItemExpanded * @param {Model<LibraryItem>} libraryItemExpanded
* @returns {oldLibraryItem} * @returns {oldLibraryItem}
*/ */
static getOldLibraryItem(libraryItemExpanded) { static getOldLibraryItem(libraryItemExpanded) {
@ -231,8 +231,8 @@ class LibraryItem extends Model {
/** /**
* Updates libraryItem, book, authors and series from old library item * Updates libraryItem, book, authors and series from old library item
* *
* @param {oldLibraryItem} oldLibraryItem * @param {oldLibraryItem} oldLibraryItem
* @returns {Promise<boolean>} true if updates were made * @returns {Promise<boolean>} true if updates were made
*/ */
static async fullUpdateFromOld(oldLibraryItem) { static async fullUpdateFromOld(oldLibraryItem) {
@ -280,14 +280,14 @@ class LibraryItem extends Model {
for (const existingPodcastEpisode of existingPodcastEpisodes) { for (const existingPodcastEpisode of existingPodcastEpisodes) {
// Episode was removed // Episode was removed
if (!updatedPodcastEpisodes.some(ep => ep.id === existingPodcastEpisode.id)) { if (!updatedPodcastEpisodes.some((ep) => ep.id === existingPodcastEpisode.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${existingPodcastEpisode.title}" was removed`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${existingPodcastEpisode.title}" was removed`)
await existingPodcastEpisode.destroy() await existingPodcastEpisode.destroy()
hasUpdates = true hasUpdates = true
} }
} }
for (const updatedPodcastEpisode of updatedPodcastEpisodes) { for (const updatedPodcastEpisode of updatedPodcastEpisodes) {
const existingEpisodeMatch = existingPodcastEpisodes.find(ep => ep.id === updatedPodcastEpisode.id) const existingEpisodeMatch = existingPodcastEpisodes.find((ep) => ep.id === updatedPodcastEpisode.id)
if (!existingEpisodeMatch) { if (!existingEpisodeMatch) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${updatedPodcastEpisode.title}" was added`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${updatedPodcastEpisode.title}" was added`)
await this.sequelize.models.podcastEpisode.createFromOld(updatedPodcastEpisode) await this.sequelize.models.podcastEpisode.createFromOld(updatedPodcastEpisode)
@ -316,12 +316,12 @@ class LibraryItem extends Model {
const existingAuthors = libraryItemExpanded.media.authors || [] const existingAuthors = libraryItemExpanded.media.authors || []
const existingSeriesAll = libraryItemExpanded.media.series || [] const existingSeriesAll = libraryItemExpanded.media.series || []
const updatedAuthors = oldLibraryItem.media.metadata.authors || [] const updatedAuthors = oldLibraryItem.media.metadata.authors || []
const uniqueUpdatedAuthors = updatedAuthors.filter((au, idx) => updatedAuthors.findIndex(a => a.id === au.id) === idx) const uniqueUpdatedAuthors = updatedAuthors.filter((au, idx) => updatedAuthors.findIndex((a) => a.id === au.id) === idx)
const updatedSeriesAll = oldLibraryItem.media.metadata.series || [] const updatedSeriesAll = oldLibraryItem.media.metadata.series || []
for (const existingAuthor of existingAuthors) { for (const existingAuthor of existingAuthors) {
// Author was removed from Book // Author was removed from Book
if (!uniqueUpdatedAuthors.some(au => au.id === existingAuthor.id)) { if (!uniqueUpdatedAuthors.some((au) => au.id === existingAuthor.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${existingAuthor.name}" was removed`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${existingAuthor.name}" was removed`)
await this.sequelize.models.bookAuthor.removeByIds(existingAuthor.id, libraryItemExpanded.media.id) await this.sequelize.models.bookAuthor.removeByIds(existingAuthor.id, libraryItemExpanded.media.id)
hasUpdates = true hasUpdates = true
@ -329,7 +329,7 @@ class LibraryItem extends Model {
} }
for (const updatedAuthor of uniqueUpdatedAuthors) { for (const updatedAuthor of uniqueUpdatedAuthors) {
// Author was added // Author was added
if (!existingAuthors.some(au => au.id === updatedAuthor.id)) { if (!existingAuthors.some((au) => au.id === updatedAuthor.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${updatedAuthor.name}" was added`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${updatedAuthor.name}" was added`)
await this.sequelize.models.bookAuthor.create({ authorId: updatedAuthor.id, bookId: libraryItemExpanded.media.id }) await this.sequelize.models.bookAuthor.create({ authorId: updatedAuthor.id, bookId: libraryItemExpanded.media.id })
hasUpdates = true hasUpdates = true
@ -337,7 +337,7 @@ class LibraryItem extends Model {
} }
for (const existingSeries of existingSeriesAll) { for (const existingSeries of existingSeriesAll) {
// Series was removed // Series was removed
if (!updatedSeriesAll.some(se => se.id === existingSeries.id)) { if (!updatedSeriesAll.some((se) => se.id === existingSeries.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${existingSeries.name}" was removed`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${existingSeries.name}" was removed`)
await this.sequelize.models.bookSeries.removeByIds(existingSeries.id, libraryItemExpanded.media.id) await this.sequelize.models.bookSeries.removeByIds(existingSeries.id, libraryItemExpanded.media.id)
hasUpdates = true hasUpdates = true
@ -345,7 +345,7 @@ class LibraryItem extends Model {
} }
for (const updatedSeries of updatedSeriesAll) { for (const updatedSeries of updatedSeriesAll) {
// Series was added/updated // Series was added/updated
const existingSeriesMatch = existingSeriesAll.find(se => se.id === updatedSeries.id) const existingSeriesMatch = existingSeriesAll.find((se) => se.id === updatedSeries.id)
if (!existingSeriesMatch) { if (!existingSeriesMatch) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${updatedSeries.name}" was added`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${updatedSeries.name}" was added`)
await this.sequelize.models.bookSeries.create({ seriesId: updatedSeries.id, bookId: libraryItemExpanded.media.id, sequence: updatedSeries.sequence }) await this.sequelize.models.bookSeries.create({ seriesId: updatedSeries.id, bookId: libraryItemExpanded.media.id, sequence: updatedSeries.sequence })
@ -420,7 +420,7 @@ class LibraryItem extends Model {
lastScanVersion: oldLibraryItem.scanVersion, lastScanVersion: oldLibraryItem.scanVersion,
libraryId: oldLibraryItem.libraryId, libraryId: oldLibraryItem.libraryId,
libraryFolderId: oldLibraryItem.folderId, libraryFolderId: oldLibraryItem.folderId,
libraryFiles: oldLibraryItem.libraryFiles?.map(lf => lf.toJSON()) || [], libraryFiles: oldLibraryItem.libraryFiles?.map((lf) => lf.toJSON()) || [],
extraData extraData
} }
} }
@ -435,8 +435,8 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {Promise<LibraryItemExpanded>} * @returns {Promise<LibraryItemExpanded>}
*/ */
static async getExpandedById(libraryItemId) { static async getExpandedById(libraryItemId) {
@ -485,7 +485,7 @@ class LibraryItem extends Model {
/** /**
* Get old library item by id * Get old library item by id
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {oldLibraryItem} * @returns {oldLibraryItem}
*/ */
static async getOldById(libraryItemId) { static async getOldById(libraryItemId) {
@ -534,9 +534,9 @@ class LibraryItem extends Model {
/** /**
* Get library items using filter and sort * Get library items using filter and sort
* @param {oldLibrary} library * @param {oldLibrary} library
* @param {oldUser} user * @param {oldUser} user
* @param {object} options * @param {object} options
* @returns {object} { libraryItems:oldLibraryItem[], count:number } * @returns {object} { libraryItems:oldLibraryItem[], count:number }
*/ */
static async getByFilterAndSort(library, user, options) { static async getByFilterAndSort(library, user, options) {
@ -545,7 +545,7 @@ class LibraryItem extends Model {
Logger.debug(`Loaded ${libraryItems.length} of ${count} items for libary page in ${((Date.now() - start) / 1000).toFixed(2)}s`) Logger.debug(`Loaded ${libraryItems.length} of ${count} items for libary page in ${((Date.now() - start) / 1000).toFixed(2)}s`)
return { return {
libraryItems: libraryItems.map(li => { libraryItems: libraryItems.map((li) => {
const oldLibraryItem = this.getOldLibraryItem(li).toJSONMinified() const oldLibraryItem = this.getOldLibraryItem(li).toJSONMinified()
if (li.collapsedSeries) { if (li.collapsedSeries) {
oldLibraryItem.collapsedSeries = li.collapsedSeries oldLibraryItem.collapsedSeries = li.collapsedSeries
@ -574,10 +574,10 @@ class LibraryItem extends Model {
/** /**
* Get home page data personalized shelves * Get home page data personalized shelves
* @param {oldLibrary} library * @param {oldLibrary} library
* @param {oldUser} user * @param {oldUser} user
* @param {string[]} include * @param {string[]} include
* @param {number} limit * @param {number} limit
* @returns {object[]} array of shelf objects * @returns {object[]} array of shelf objects
*/ */
static async getPersonalizedShelves(library, user, include, limit) { static async getPersonalizedShelves(library, user, include, limit) {
@ -588,8 +588,8 @@ class LibraryItem extends Model {
// "Continue Listening" shelf // "Continue Listening" shelf
const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, false) const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, false)
if (itemsInProgressPayload.items.length) { if (itemsInProgressPayload.items.length) {
const ebookOnlyItemsInProgress = itemsInProgressPayload.items.filter(li => li.media.isEBookOnly) const ebookOnlyItemsInProgress = itemsInProgressPayload.items.filter((li) => li.media.isEBookOnly)
const audioOnlyItemsInProgress = itemsInProgressPayload.items.filter(li => !li.media.isEBookOnly) const audioOnlyItemsInProgress = itemsInProgressPayload.items.filter((li) => !li.media.isEBookOnly)
shelves.push({ shelves.push({
id: 'continue-listening', id: 'continue-listening',
@ -697,8 +697,8 @@ class LibraryItem extends Model {
// "Listen Again" shelf // "Listen Again" shelf
const mediaFinishedPayload = await libraryFilters.getMediaFinished(library, user, include, limit) const mediaFinishedPayload = await libraryFilters.getMediaFinished(library, user, include, limit)
if (mediaFinishedPayload.items.length) { if (mediaFinishedPayload.items.length) {
const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter(li => li.media.isEBookOnly) const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.isEBookOnly)
const audioOnlyItemsInProgress = mediaFinishedPayload.items.filter(li => !li.media.isEBookOnly) const audioOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => !li.media.isEBookOnly)
shelves.push({ shelves.push({
id: 'listen-again', id: 'listen-again',
@ -748,27 +748,27 @@ class LibraryItem extends Model {
/** /**
* Get book library items for author, optional use user permissions * Get book library items for author, optional use user permissions
* @param {oldAuthor} author * @param {oldAuthor} author
* @param {[oldUser]} user * @param {[oldUser]} user
* @returns {Promise<oldLibraryItem[]>} * @returns {Promise<oldLibraryItem[]>}
*/ */
static async getForAuthor(author, user = null) { static async getForAuthor(author, user = null) {
const { libraryItems } = await libraryFilters.getLibraryItemsForAuthor(author, user, undefined, undefined) const { libraryItems } = await libraryFilters.getLibraryItemsForAuthor(author, user, undefined, undefined)
return libraryItems.map(li => this.getOldLibraryItem(li)) return libraryItems.map((li) => this.getOldLibraryItem(li))
} }
/** /**
* Get book library items in a collection * Get book library items in a collection
* @param {oldCollection} collection * @param {oldCollection} collection
* @returns {Promise<oldLibraryItem[]>} * @returns {Promise<oldLibraryItem[]>}
*/ */
static async getForCollection(collection) { static async getForCollection(collection) {
const libraryItems = await libraryFilters.getLibraryItemsForCollection(collection) const libraryItems = await libraryFilters.getLibraryItemsForCollection(collection)
return libraryItems.map(li => this.getOldLibraryItem(li)) return libraryItems.map((li) => this.getOldLibraryItem(li))
} }
/** /**
* Check if library item exists * Check if library item exists
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
static async checkExistsById(libraryItemId) { static async checkExistsById(libraryItemId) {
@ -776,8 +776,8 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @param {import('sequelize').WhereOptions} where * @param {import('sequelize').WhereOptions} where
* @param {import('sequelize').BindOrReplacements} replacements * @param {import('sequelize').BindOrReplacements} replacements
* @returns {Object} oldLibraryItem * @returns {Object} oldLibraryItem
*/ */
@ -822,8 +822,8 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @param {import('sequelize').FindOptions} options * @param {import('sequelize').FindOptions} options
* @returns {Promise<Book|Podcast>} * @returns {Promise<Book|Podcast>}
*/ */
getMedia(options) { getMedia(options) {
@ -833,7 +833,7 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @returns {Promise<Book|Podcast>} * @returns {Promise<Book|Podcast>}
*/ */
getMediaExpanded() { getMediaExpanded() {
@ -870,7 +870,7 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @returns {Promise} * @returns {Promise}
*/ */
async saveMetadataFile() { async saveMetadataFile() {
@ -887,18 +887,18 @@ class LibraryItem extends Model {
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`) const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
// Expanded with series, authors, podcastEpisodes // Expanded with series, authors, podcastEpisodes
const mediaExpanded = this.media || await this.getMediaExpanded() const mediaExpanded = this.media || (await this.getMediaExpanded())
let jsonObject = {} let jsonObject = {}
if (this.mediaType === 'book') { if (this.mediaType === 'book') {
jsonObject = { jsonObject = {
tags: mediaExpanded.tags || [], tags: mediaExpanded.tags || [],
chapters: mediaExpanded.chapters?.map(c => ({ ...c })) || [], chapters: mediaExpanded.chapters?.map((c) => ({ ...c })) || [],
title: mediaExpanded.title, title: mediaExpanded.title,
subtitle: mediaExpanded.subtitle, subtitle: mediaExpanded.subtitle,
authors: mediaExpanded.authors.map(a => a.name), authors: mediaExpanded.authors.map((a) => a.name),
narrators: mediaExpanded.narrators, narrators: mediaExpanded.narrators,
series: mediaExpanded.series.map(se => { series: mediaExpanded.series.map((se) => {
const sequence = se.bookSeries?.sequence || '' const sequence = se.bookSeries?.sequence || ''
if (!sequence) return se.name if (!sequence) return se.name
return `${se.name} #${sequence}` return `${se.name} #${sequence}`
@ -934,96 +934,101 @@ class LibraryItem extends Model {
} }
} }
return fsExtra
return fsExtra.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2)).then(async () => { .writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
// Add metadata.json to libraryFiles array if it is new .then(async () => {
let metadataLibraryFile = this.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath)) // Add metadata.json to libraryFiles array if it is new
if (storeMetadataWithItem) { let metadataLibraryFile = this.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
if (!metadataLibraryFile) { if (storeMetadataWithItem) {
const newLibraryFile = new LibraryFile() if (!metadataLibraryFile) {
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`) const newLibraryFile = new LibraryFile()
metadataLibraryFile = newLibraryFile.toJSON() await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
this.libraryFiles.push(metadataLibraryFile) metadataLibraryFile = newLibraryFile.toJSON()
} else { this.libraryFiles.push(metadataLibraryFile)
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath) } else {
if (fileTimestamps) { const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs if (fileTimestamps) {
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
metadataLibraryFile.metadata.size = fileTimestamps.size metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
metadataLibraryFile.ino = fileTimestamps.ino metadataLibraryFile.metadata.size = fileTimestamps.size
metadataLibraryFile.ino = fileTimestamps.ino
}
}
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
if (libraryItemDirTimestamps) {
this.mtime = libraryItemDirTimestamps.mtimeMs
this.ctime = libraryItemDirTimestamps.ctimeMs
let size = 0
this.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
this.size = size
await this.save()
} }
} }
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
if (libraryItemDirTimestamps) {
this.mtime = libraryItemDirTimestamps.mtimeMs
this.ctime = libraryItemDirTimestamps.ctimeMs
let size = 0
this.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
this.size = size
await this.save()
}
}
Logger.debug(`Success saving abmetadata to "${metadataFilePath}"`) Logger.debug(`Success saving abmetadata to "${metadataFilePath}"`)
return metadataLibraryFile return metadataLibraryFile
}).catch((error) => { })
Logger.error(`Failed to save json file at "${metadataFilePath}"`, error) .catch((error) => {
return null Logger.error(`Failed to save json file at "${metadataFilePath}"`, error)
}) return null
})
} }
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
ino: DataTypes.STRING,
path: DataTypes.STRING,
relPath: DataTypes.STRING,
mediaId: DataTypes.UUIDV4,
mediaType: DataTypes.STRING,
isFile: DataTypes.BOOLEAN,
isMissing: DataTypes.BOOLEAN,
isInvalid: DataTypes.BOOLEAN,
mtime: DataTypes.DATE(6),
ctime: DataTypes.DATE(6),
birthtime: DataTypes.DATE(6),
size: DataTypes.BIGINT,
lastScan: DataTypes.DATE,
lastScanVersion: DataTypes.STRING,
libraryFiles: DataTypes.JSON,
extraData: DataTypes.JSON
}, },
ino: DataTypes.STRING, {
path: DataTypes.STRING, sequelize,
relPath: DataTypes.STRING, modelName: 'libraryItem',
mediaId: DataTypes.UUIDV4, indexes: [
mediaType: DataTypes.STRING, {
isFile: DataTypes.BOOLEAN, fields: ['createdAt']
isMissing: DataTypes.BOOLEAN, },
isInvalid: DataTypes.BOOLEAN, {
mtime: DataTypes.DATE(6), fields: ['mediaId']
ctime: DataTypes.DATE(6), },
birthtime: DataTypes.DATE(6), {
size: DataTypes.BIGINT, fields: ['libraryId', 'mediaType']
lastScan: DataTypes.DATE, },
lastScanVersion: DataTypes.STRING, {
libraryFiles: DataTypes.JSON, fields: ['libraryId', 'mediaId', 'mediaType']
extraData: DataTypes.JSON },
}, { {
sequelize, fields: ['birthtime']
modelName: 'libraryItem', },
indexes: [ {
{ fields: ['mtime']
fields: ['createdAt'] }
}, ]
{ }
fields: ['mediaId'] )
},
{
fields: ['libraryId', 'mediaType']
},
{
fields: ['libraryId', 'mediaId', 'mediaType']
},
{
fields: ['birthtime']
},
{
fields: ['mtime']
}
]
})
const { library, libraryFolder, book, podcast } = sequelize.models const { library, libraryFolder, book, podcast } = sequelize.models
library.hasMany(LibraryItem) library.hasMany(LibraryItem)
@ -1050,7 +1055,7 @@ class LibraryItem extends Model {
}) })
LibraryItem.belongsTo(podcast, { foreignKey: 'mediaId', constraints: false }) LibraryItem.belongsTo(podcast, { foreignKey: 'mediaId', constraints: false })
LibraryItem.addHook('afterFind', findResult => { LibraryItem.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
@ -1070,7 +1075,7 @@ class LibraryItem extends Model {
} }
}) })
LibraryItem.addHook('afterDestroy', async instance => { LibraryItem.addHook('afterDestroy', async (instance) => {
if (!instance) return if (!instance) return
const media = await instance.getMedia() const media = await instance.getMedia()
if (media) { if (media) {

View File

@ -100,38 +100,41 @@ class MediaProgress extends Model {
/** /**
* Initialize model * Initialize model
* *
* Polymorphic association: Book has many MediaProgress. PodcastEpisode has many MediaProgress. * Polymorphic association: Book has many MediaProgress. PodcastEpisode has many MediaProgress.
* @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ * @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
* *
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
duration: DataTypes.FLOAT,
currentTime: DataTypes.FLOAT,
isFinished: DataTypes.BOOLEAN,
hideFromContinueListening: DataTypes.BOOLEAN,
ebookLocation: DataTypes.STRING,
ebookProgress: DataTypes.FLOAT,
finishedAt: DataTypes.DATE,
extraData: DataTypes.JSON
}, },
mediaItemId: DataTypes.UUIDV4, {
mediaItemType: DataTypes.STRING, sequelize,
duration: DataTypes.FLOAT, modelName: 'mediaProgress',
currentTime: DataTypes.FLOAT, indexes: [
isFinished: DataTypes.BOOLEAN, {
hideFromContinueListening: DataTypes.BOOLEAN, fields: ['updatedAt']
ebookLocation: DataTypes.STRING, }
ebookProgress: DataTypes.FLOAT, ]
finishedAt: DataTypes.DATE, }
extraData: DataTypes.JSON )
}, {
sequelize,
modelName: 'mediaProgress',
indexes: [
{
fields: ['updatedAt']
}
]
})
const { book, podcastEpisode, user } = sequelize.models const { book, podcastEpisode, user } = sequelize.models
@ -153,7 +156,7 @@ class MediaProgress extends Model {
}) })
MediaProgress.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) MediaProgress.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
MediaProgress.addHook('afterFind', findResult => { MediaProgress.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
@ -181,4 +184,4 @@ class MediaProgress extends Model {
} }
} }
module.exports = MediaProgress module.exports = MediaProgress

View File

@ -2,7 +2,6 @@ const { DataTypes, Model } = require('sequelize')
const oldPlaybackSession = require('../objects/PlaybackSession') const oldPlaybackSession = require('../objects/PlaybackSession')
class PlaybackSession extends Model { class PlaybackSession extends Model {
constructor(values, options) { constructor(values, options) {
super(values, options) super(values, options)
@ -62,7 +61,7 @@ class PlaybackSession extends Model {
} }
] ]
}) })
return playbackSessions.map(session => this.getOldPlaybackSession(session)) return playbackSessions.map((session) => this.getOldPlaybackSession(session))
} }
static async getById(sessionId) { static async getById(sessionId) {
@ -170,35 +169,38 @@ class PlaybackSession extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
displayTitle: DataTypes.STRING,
displayAuthor: DataTypes.STRING,
duration: DataTypes.FLOAT,
playMethod: DataTypes.INTEGER,
mediaPlayer: DataTypes.STRING,
startTime: DataTypes.FLOAT,
currentTime: DataTypes.FLOAT,
serverVersion: DataTypes.STRING,
coverPath: DataTypes.STRING,
timeListening: DataTypes.INTEGER,
mediaMetadata: DataTypes.JSON,
date: DataTypes.STRING,
dayOfWeek: DataTypes.STRING,
extraData: DataTypes.JSON
}, },
mediaItemId: DataTypes.UUIDV4, {
mediaItemType: DataTypes.STRING, sequelize,
displayTitle: DataTypes.STRING, modelName: 'playbackSession'
displayAuthor: DataTypes.STRING, }
duration: DataTypes.FLOAT, )
playMethod: DataTypes.INTEGER,
mediaPlayer: DataTypes.STRING,
startTime: DataTypes.FLOAT,
currentTime: DataTypes.FLOAT,
serverVersion: DataTypes.STRING,
coverPath: DataTypes.STRING,
timeListening: DataTypes.INTEGER,
mediaMetadata: DataTypes.JSON,
date: DataTypes.STRING,
dayOfWeek: DataTypes.STRING,
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'playbackSession'
})
const { book, podcastEpisode, user, device, library } = sequelize.models const { book, podcastEpisode, user, device, library } = sequelize.models
@ -229,7 +231,7 @@ class PlaybackSession extends Model {
}) })
PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaybackSession.addHook('afterFind', findResult => { PlaybackSession.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]

View File

@ -23,29 +23,6 @@ class Playlist extends Model {
this.updatedAt this.updatedAt
} }
static async getOldPlaylists() {
const playlists = await this.findAll({
include: {
model: this.sequelize.models.playlistMediaItem,
include: [
{
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.podcastEpisode,
include: {
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
}
]
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
return playlists.map((p) => this.getOldPlaylist(p))
}
static getOldPlaylist(playlistExpanded) { static getOldPlaylist(playlistExpanded) {
const items = playlistExpanded.playlistMediaItems const items = playlistExpanded.playlistMediaItems
.map((pmi) => { .map((pmi) => {
@ -76,8 +53,8 @@ class Playlist extends Model {
/** /**
* Get old playlist toJSONExpanded * Get old playlist toJSONExpanded
* @param {[string[]]} include * @param {string[]} [include]
* @returns {Promise<object>} oldPlaylist.toJSONExpanded * @returns {Promise<oldPlaylist>} oldPlaylist.toJSONExpanded
*/ */
async getOldJsonExpanded(include) { async getOldJsonExpanded(include) {
this.playlistMediaItems = this.playlistMediaItems =

View File

@ -35,24 +35,27 @@ class PlaylistMediaItem extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
order: DataTypes.INTEGER
}, },
mediaItemId: DataTypes.UUIDV4, {
mediaItemType: DataTypes.STRING, sequelize,
order: DataTypes.INTEGER timestamps: true,
}, { updatedAt: false,
sequelize, modelName: 'playlistMediaItem'
timestamps: true, }
updatedAt: false, )
modelName: 'playlistMediaItem'
})
const { book, podcastEpisode, playlist } = sequelize.models const { book, podcastEpisode, playlist } = sequelize.models
@ -74,7 +77,7 @@ class PlaylistMediaItem extends Model {
}) })
PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaylistMediaItem.addHook('afterFind', findResult => { PlaylistMediaItem.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]

View File

@ -3,7 +3,7 @@ const { DataTypes, Model } = require('sequelize')
/** /**
* @typedef PodcastExpandedProperties * @typedef PodcastExpandedProperties
* @property {import('./PodcastEpisode')[]} podcastEpisodes * @property {import('./PodcastEpisode')[]} podcastEpisodes
* *
* @typedef {Podcast & PodcastExpandedProperties} PodcastExpanded * @typedef {Podcast & PodcastExpandedProperties} PodcastExpanded
*/ */
@ -61,7 +61,7 @@ class Podcast extends Model {
static getOldPodcast(libraryItemExpanded) { static getOldPodcast(libraryItemExpanded) {
const podcastExpanded = libraryItemExpanded.media const podcastExpanded = libraryItemExpanded.media
const podcastEpisodes = podcastExpanded.podcastEpisodes?.map(ep => ep.getOldPodcastEpisode(libraryItemExpanded.id).toJSON()).sort((a, b) => a.index - b.index) const podcastEpisodes = podcastExpanded.podcastEpisodes?.map((ep) => ep.getOldPodcastEpisode(libraryItemExpanded.id).toJSON()).sort((a, b) => a.index - b.index)
return { return {
id: podcastExpanded.id, id: podcastExpanded.id,
libraryItemId: libraryItemExpanded.id, libraryItemId: libraryItemExpanded.id,
@ -140,42 +140,45 @@ class Podcast extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
title: DataTypes.STRING, },
titleIgnorePrefix: DataTypes.STRING, title: DataTypes.STRING,
author: DataTypes.STRING, titleIgnorePrefix: DataTypes.STRING,
releaseDate: DataTypes.STRING, author: DataTypes.STRING,
feedURL: DataTypes.STRING, releaseDate: DataTypes.STRING,
imageURL: DataTypes.STRING, feedURL: DataTypes.STRING,
description: DataTypes.TEXT, imageURL: DataTypes.STRING,
itunesPageURL: DataTypes.STRING, description: DataTypes.TEXT,
itunesId: DataTypes.STRING, itunesPageURL: DataTypes.STRING,
itunesArtistId: DataTypes.STRING, itunesId: DataTypes.STRING,
language: DataTypes.STRING, itunesArtistId: DataTypes.STRING,
podcastType: DataTypes.STRING, language: DataTypes.STRING,
explicit: DataTypes.BOOLEAN, podcastType: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
autoDownloadEpisodes: DataTypes.BOOLEAN, autoDownloadEpisodes: DataTypes.BOOLEAN,
autoDownloadSchedule: DataTypes.STRING, autoDownloadSchedule: DataTypes.STRING,
lastEpisodeCheck: DataTypes.DATE, lastEpisodeCheck: DataTypes.DATE,
maxEpisodesToKeep: DataTypes.INTEGER, maxEpisodesToKeep: DataTypes.INTEGER,
maxNewEpisodesToDownload: DataTypes.INTEGER, maxNewEpisodesToDownload: DataTypes.INTEGER,
coverPath: DataTypes.STRING, coverPath: DataTypes.STRING,
tags: DataTypes.JSON, tags: DataTypes.JSON,
genres: DataTypes.JSON genres: DataTypes.JSON
}, { },
sequelize, {
modelName: 'podcast' sequelize,
}) modelName: 'podcast'
}
)
} }
} }
module.exports = Podcast module.exports = Podcast

View File

@ -54,7 +54,7 @@ class PodcastEpisode extends Model {
} }
/** /**
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {oldPodcastEpisode} * @returns {oldPodcastEpisode}
*/ */
getOldPodcastEpisode(libraryItemId = null) { getOldPodcastEpisode(libraryItemId = null) {
@ -125,40 +125,43 @@ class PodcastEpisode extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
index: DataTypes.INTEGER, },
season: DataTypes.STRING, index: DataTypes.INTEGER,
episode: DataTypes.STRING, season: DataTypes.STRING,
episodeType: DataTypes.STRING, episode: DataTypes.STRING,
title: DataTypes.STRING, episodeType: DataTypes.STRING,
subtitle: DataTypes.STRING(1000), title: DataTypes.STRING,
description: DataTypes.TEXT, subtitle: DataTypes.STRING(1000),
pubDate: DataTypes.STRING, description: DataTypes.TEXT,
enclosureURL: DataTypes.STRING, pubDate: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT, enclosureURL: DataTypes.STRING,
enclosureType: DataTypes.STRING, enclosureSize: DataTypes.BIGINT,
publishedAt: DataTypes.DATE, enclosureType: DataTypes.STRING,
publishedAt: DataTypes.DATE,
audioFile: DataTypes.JSON, audioFile: DataTypes.JSON,
chapters: DataTypes.JSON, chapters: DataTypes.JSON,
extraData: DataTypes.JSON extraData: DataTypes.JSON
}, { },
sequelize, {
modelName: 'podcastEpisode', sequelize,
indexes: [ modelName: 'podcastEpisode',
{ indexes: [
fields: ['createdAt'] {
} fields: ['createdAt']
] }
}) ]
}
)
const { podcast } = sequelize.models const { podcast } = sequelize.models
podcast.hasMany(PodcastEpisode, { podcast.hasMany(PodcastEpisode, {
@ -168,4 +171,4 @@ class PodcastEpisode extends Model {
} }
} }
module.exports = PodcastEpisode module.exports = PodcastEpisode

View File

@ -24,7 +24,7 @@ class Series extends Model {
static async getAllOldSeries() { static async getAllOldSeries() {
const series = await this.findAll() const series = await this.findAll()
return series.map(se => se.getOldSeries()) return series.map((se) => se.getOldSeries())
} }
getOldSeries() { getOldSeries() {
@ -77,7 +77,7 @@ class Series extends Model {
/** /**
* Get oldSeries by id * Get oldSeries by id
* @param {string} seriesId * @param {string} seriesId
* @returns {Promise<oldSeries>} * @returns {Promise<oldSeries>}
*/ */
static async getOldById(seriesId) { static async getOldById(seriesId) {
@ -88,7 +88,7 @@ class Series extends Model {
/** /**
* Check if series exists * Check if series exists
* @param {string} seriesId * @param {string} seriesId
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
static async checkExistsById(seriesId) { static async checkExistsById(seriesId) {
@ -97,58 +97,65 @@ class Series extends Model {
/** /**
* Get old series by name and libraryId. name case insensitive * Get old series by name and libraryId. name case insensitive
* *
* @param {string} seriesName * @param {string} seriesName
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<oldSeries>} * @returns {Promise<oldSeries>}
*/ */
static async getOldByNameAndLibrary(seriesName, libraryId) { static async getOldByNameAndLibrary(seriesName, libraryId) {
const series = (await this.findOne({ const series = (
where: [ await this.findOne({
where(fn('lower', col('name')), seriesName.toLowerCase()), where: [
{ where(fn('lower', col('name')), seriesName.toLowerCase()),
libraryId {
} libraryId
] }
}))?.getOldSeries() ]
})
)?.getOldSeries()
return series return series
} }
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
name: DataTypes.STRING,
nameIgnorePrefix: DataTypes.STRING,
description: DataTypes.TEXT
}, {
sequelize,
modelName: 'series',
indexes: [
{
fields: [{
name: 'name',
collate: 'NOCASE'
}]
}, },
// { name: DataTypes.STRING,
// fields: [{ nameIgnorePrefix: DataTypes.STRING,
// name: 'nameIgnorePrefix', description: DataTypes.TEXT
// collate: 'NOCASE' },
// }] {
// }, sequelize,
{ modelName: 'series',
fields: ['libraryId'] indexes: [
} {
] fields: [
}) {
name: 'name',
collate: 'NOCASE'
}
]
},
// {
// fields: [{
// name: 'nameIgnorePrefix',
// collate: 'NOCASE'
// }]
// },
{
fields: ['libraryId']
}
]
}
)
const { library } = sequelize.models const { library } = sequelize.models
library.hasMany(Series, { library.hasMany(Series, {
@ -158,4 +165,4 @@ class Series extends Model {
} }
} }
module.exports = Series module.exports = Series

View File

@ -19,12 +19,11 @@ class Setting extends Model {
} }
static async getOldSettings() { static async getOldSettings() {
const settings = (await this.findAll()).map(se => se.value) const settings = (await this.findAll()).map((se) => se.value)
const emailSettingsJson = settings.find((se) => se.id === 'email-settings')
const emailSettingsJson = settings.find(se => se.id === 'email-settings') const serverSettingsJson = settings.find((se) => se.id === 'server-settings')
const serverSettingsJson = settings.find(se => se.id === 'server-settings') const notificationSettingsJson = settings.find((se) => se.id === 'notification-settings')
const notificationSettingsJson = settings.find(se => se.id === 'notification-settings')
return { return {
settings, settings,
@ -43,20 +42,23 @@ class Setting extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
key: { {
type: DataTypes.STRING, key: {
primaryKey: true type: DataTypes.STRING,
primaryKey: true
},
value: DataTypes.JSON
}, },
value: DataTypes.JSON {
}, { sequelize,
sequelize, modelName: 'setting'
modelName: 'setting' }
}) )
} }
} }
module.exports = Setting module.exports = Setting

View File

@ -1,4 +1,4 @@
const uuidv4 = require("uuid").v4 const uuidv4 = require('uuid').v4
const sequelize = require('sequelize') const sequelize = require('sequelize')
const Logger = require('../Logger') const Logger = require('../Logger')
const oldUser = require('../objects/user/User') const oldUser = require('../objects/user/User')
@ -45,17 +45,17 @@ class User extends Model {
const users = await this.findAll({ const users = await this.findAll({
include: this.sequelize.models.mediaProgress include: this.sequelize.models.mediaProgress
}) })
return users.map(u => this.getOldUser(u)) return users.map((u) => this.getOldUser(u))
} }
/** /**
* Get old user model from new * Get old user model from new
* *
* @param {Object} userExpanded * @param {Object} userExpanded
* @returns {oldUser} * @returns {oldUser}
*/ */
static getOldUser(userExpanded) { static getOldUser(userExpanded) {
const mediaProgress = userExpanded.mediaProgresses.map(mp => mp.getOldMediaProgress()) const mediaProgress = userExpanded.mediaProgresses.map((mp) => mp.getOldMediaProgress())
const librariesAccessible = userExpanded.permissions?.librariesAccessible || [] const librariesAccessible = userExpanded.permissions?.librariesAccessible || []
const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || [] const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || []
@ -86,8 +86,8 @@ class User extends Model {
} }
/** /**
* *
* @param {oldUser} oldUser * @param {oldUser} oldUser
* @returns {Promise<User>} * @returns {Promise<User>}
*/ */
static createFromOld(oldUser) { static createFromOld(oldUser) {
@ -97,8 +97,8 @@ class User extends Model {
/** /**
* Update User from old user model * Update User from old user model
* *
* @param {oldUser} oldUser * @param {oldUser} oldUser
* @param {boolean} [hooks=true] Run before / after bulk update hooks? * @param {boolean} [hooks=true] Run before / after bulk update hooks?
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
@ -109,16 +109,18 @@ class User extends Model {
where: { where: {
id: user.id id: user.id
} }
}).then((result) => result[0] > 0).catch((error) => {
Logger.error(`[User] Failed to save user ${oldUser.id}`, error)
return false
}) })
.then((result) => result[0] > 0)
.catch((error) => {
Logger.error(`[User] Failed to save user ${oldUser.id}`, error)
return false
})
} }
/** /**
* Get new User model from old * Get new User model from old
* *
* @param {oldUser} oldUser * @param {oldUser} oldUser
* @returns {Object} * @returns {Object}
*/ */
static getFromOld(oldUser) { static getFromOld(oldUser) {
@ -160,9 +162,9 @@ class User extends Model {
/** /**
* Create root user * Create root user
* @param {string} username * @param {string} username
* @param {string} pash * @param {string} pash
* @param {Auth} auth * @param {Auth} auth
* @returns {Promise<oldUser>} * @returns {Promise<oldUser>}
*/ */
static async createRootUser(username, pash, auth) { static async createRootUser(username, pash, auth) {
@ -185,15 +187,15 @@ class User extends Model {
/** /**
* Create user from openid userinfo * Create user from openid userinfo
* @param {Object} userinfo * @param {Object} userinfo
* @param {Auth} auth * @param {Auth} auth
* @returns {Promise<oldUser>} * @returns {Promise<oldUser>}
*/ */
static async createUserFromOpenIdUserInfo(userinfo, auth) { static async createUserFromOpenIdUserInfo(userinfo, auth) {
const userId = uuidv4() const userId = uuidv4()
// TODO: Ensure username is unique? // TODO: Ensure username is unique?
const username = userinfo.preferred_username || userinfo.name || userinfo.sub const username = userinfo.preferred_username || userinfo.name || userinfo.sub
const email = (userinfo.email && userinfo.email_verified) ? userinfo.email : null const email = userinfo.email && userinfo.email_verified ? userinfo.email : null
const token = await auth.generateAccessToken({ id: userId, username }) const token = await auth.generateAccessToken({ id: userId, username })
@ -218,7 +220,7 @@ class User extends Model {
/** /**
* Get a user by id or by the old database id * Get a user by id or by the old database id
* @temp User ids were updated in v2.3.0 migration and old API tokens may still use that id * @temp User ids were updated in v2.3.0 migration and old API tokens may still use that id
* @param {string} userId * @param {string} userId
* @returns {Promise<oldUser|null>} null if not found * @returns {Promise<oldUser|null>} null if not found
*/ */
static async getUserByIdOrOldId(userId) { static async getUserByIdOrOldId(userId) {
@ -244,7 +246,7 @@ class User extends Model {
/** /**
* Get user by username case insensitive * Get user by username case insensitive
* @param {string} username * @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserByUsername(username) { static async getUserByUsername(username) {
@ -263,7 +265,7 @@ class User extends Model {
/** /**
* Get user by email case insensitive * Get user by email case insensitive
* @param {string} username * @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserByEmail(email) { static async getUserByEmail(email) {
@ -282,7 +284,7 @@ class User extends Model {
/** /**
* Get user by id * Get user by id
* @param {string} userId * @param {string} userId
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserById(userId) { static async getUserById(userId) {
@ -296,7 +298,7 @@ class User extends Model {
/** /**
* Get user by openid sub * Get user by openid sub
* @param {string} sub * @param {string} sub
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserByOpenIDSub(sub) { static async getUserByOpenIDSub(sub) {
@ -317,7 +319,7 @@ class User extends Model {
const users = await this.findAll({ const users = await this.findAll({
attributes: ['id', 'username'] attributes: ['id', 'username']
}) })
return users.map(u => { return users.map((u) => {
return { return {
id: u.id, id: u.id,
username: u.username username: u.username
@ -340,37 +342,40 @@ class User extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
username: DataTypes.STRING,
email: DataTypes.STRING,
pash: DataTypes.STRING,
type: DataTypes.STRING,
token: DataTypes.STRING,
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
isLocked: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
lastSeen: DataTypes.DATE,
permissions: DataTypes.JSON,
bookmarks: DataTypes.JSON,
extraData: DataTypes.JSON
}, },
username: DataTypes.STRING, {
email: DataTypes.STRING, sequelize,
pash: DataTypes.STRING, modelName: 'user'
type: DataTypes.STRING, }
token: DataTypes.STRING, )
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
isLocked: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
lastSeen: DataTypes.DATE,
permissions: DataTypes.JSON,
bookmarks: DataTypes.JSON,
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'user'
})
} }
} }
module.exports = User module.exports = User