Add jsdoc types for models

This commit is contained in:
advplyr 2023-08-15 18:03:43 -05:00
parent 7afda1295b
commit c707bcf0f6
11 changed files with 1537 additions and 1233 deletions

View File

@ -93,25 +93,25 @@ class Database {
buildModels(force = false) {
require('./models/User')(this.sequelize)
require('./models/Library')(this.sequelize)
require('./models/LibraryFolder')(this.sequelize)
require('./models/Book')(this.sequelize)
require('./models/Library').init(this.sequelize)
require('./models/LibraryFolder').init(this.sequelize)
require('./models/Book').init(this.sequelize)
require('./models/Podcast')(this.sequelize)
require('./models/PodcastEpisode')(this.sequelize)
require('./models/LibraryItem')(this.sequelize)
require('./models/MediaProgress')(this.sequelize)
require('./models/Series')(this.sequelize)
require('./models/BookSeries')(this.sequelize)
require('./models/BookSeries').init(this.sequelize)
require('./models/Author').init(this.sequelize)
require('./models/BookAuthor')(this.sequelize)
require('./models/Collection')(this.sequelize)
require('./models/CollectionBook')(this.sequelize)
require('./models/BookAuthor').init(this.sequelize)
require('./models/Collection').init(this.sequelize)
require('./models/CollectionBook').init(this.sequelize)
require('./models/Playlist')(this.sequelize)
require('./models/PlaylistMediaItem')(this.sequelize)
require('./models/Device')(this.sequelize)
require('./models/Device').init(this.sequelize)
require('./models/PlaybackSession')(this.sequelize)
require('./models/Feed')(this.sequelize)
require('./models/FeedEpisode')(this.sequelize)
require('./models/Feed').init(this.sequelize)
require('./models/FeedEpisode').init(this.sequelize)
require('./models/Setting')(this.sequelize)
return this.sequelize.sync({ force, alter: false })

View File

@ -1,8 +1,56 @@
const { DataTypes, Model } = require('sequelize')
const Logger = require('../Logger')
module.exports = (sequelize) => {
class Book extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.title
/** @type {string} */
this.titleIgnorePrefix
/** @type {string} */
this.publishedYear
/** @type {string} */
this.publishedDate
/** @type {string} */
this.publisher
/** @type {string} */
this.description
/** @type {string} */
this.isbn
/** @type {string} */
this.asin
/** @type {string} */
this.language
/** @type {boolean} */
this.explicit
/** @type {boolean} */
this.abridged
/** @type {string} */
this.coverPath
/** @type {number} */
this.duration
/** @type {Object} */
this.narrators
/** @type {Object} */
this.audioFiles
/** @type {Object} */
this.ebookFile
/** @type {Object} */
this.chapters
/** @type {Object} */
this.tags
/** @type {Object} */
this.genres
/** @type {Date} */
this.updatedAt
/** @type {Date} */
this.createdAt
}
static getOldBook(libraryItemExpanded) {
const bookExpanded = libraryItemExpanded.media
let authors = []
@ -120,9 +168,13 @@ module.exports = (sequelize) => {
genres: oldBook.metadata.genres
}
}
}
Book.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -173,6 +225,7 @@ module.exports = (sequelize) => {
}
]
})
return Book
}
}
module.exports = Book

View File

@ -1,7 +1,19 @@
const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => {
class BookAuthor extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {UUIDV4} */
this.bookId
/** @type {UUIDV4} */
this.authorId
/** @type {Date} */
this.createdAt
}
static removeByIds(authorId = null, bookId = null) {
const where = {}
if (authorId) where.authorId = authorId
@ -10,9 +22,13 @@ module.exports = (sequelize) => {
where
})
}
}
BookAuthor.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -36,6 +52,6 @@ module.exports = (sequelize) => {
author.hasMany(BookAuthor)
BookAuthor.belongsTo(author)
return BookAuthor
}
}
module.exports = BookAuthor

View File

@ -1,7 +1,21 @@
const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => {
class BookSeries extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.sequence
/** @type {UUIDV4} */
this.bookId
/** @type {UUIDV4} */
this.seriesId
/** @type {Date} */
this.createdAt
}
static removeByIds(seriesId = null, bookId = null) {
const where = {}
if (seriesId) where.seriesId = seriesId
@ -10,9 +24,13 @@ module.exports = (sequelize) => {
where
})
}
}
BookSeries.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -37,6 +55,7 @@ module.exports = (sequelize) => {
series.hasMany(BookSeries)
BookSeries.belongsTo(series)
return BookSeries
}
}
module.exports = BookSeries

View File

@ -1,10 +1,25 @@
const { DataTypes, Model, Sequelize } = require('sequelize')
const oldCollection = require('../objects/Collection')
const { areEquivalent } = require('../utils/index')
module.exports = (sequelize) => {
class Collection extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.name
/** @type {string} */
this.description
/** @type {UUIDV4} */
this.libraryId
/** @type {Date} */
this.updatedAt
/** @type {Date} */
this.createdAt
}
/**
* Get all old collections
* @returns {Promise<oldCollection[]>}
@ -12,10 +27,10 @@ module.exports = (sequelize) => {
static async getOldCollections() {
const collections = await this.findAll({
include: {
model: sequelize.models.book,
include: sequelize.models.libraryItem
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
return collections.map(c => this.getOldCollection(c))
}
@ -39,7 +54,7 @@ module.exports = (sequelize) => {
const collectionIncludes = []
if (include.includes('rssfeed')) {
collectionIncludes.push({
model: sequelize.models.feed
model: this.sequelize.models.feed
})
}
@ -47,19 +62,19 @@ module.exports = (sequelize) => {
where: collectionWhere,
include: [
{
model: sequelize.models.book,
model: this.sequelize.models.book,
include: [
{
model: sequelize.models.libraryItem
model: this.sequelize.models.libraryItem
},
{
model: sequelize.models.author,
model: this.sequelize.models.author,
through: {
attributes: []
}
},
{
model: sequelize.models.series,
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
@ -69,7 +84,7 @@ module.exports = (sequelize) => {
},
...collectionIncludes
],
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
// TODO: Handle user permission restrictions on initial query
return collections.map(c => {
@ -93,7 +108,7 @@ module.exports = (sequelize) => {
const libraryItem = b.libraryItem
delete b.libraryItem
libraryItem.media = b
return sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
})
// Users with restricted permissions will not see this collection
@ -105,7 +120,7 @@ module.exports = (sequelize) => {
// Map feed if found
if (c.feeds?.length) {
collectionExpanded.rssFeed = sequelize.models.feed.getOldFeed(c.feeds[0])
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(c.feeds[0])
}
return collectionExpanded
@ -122,16 +137,16 @@ module.exports = (sequelize) => {
this.books = await this.getBooks({
include: [
{
model: sequelize.models.libraryItem
model: this.sequelize.models.libraryItem
},
{
model: sequelize.models.author,
model: this.sequelize.models.author,
through: {
attributes: []
}
},
{
model: sequelize.models.series,
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
@ -141,7 +156,7 @@ module.exports = (sequelize) => {
order: [Sequelize.literal('`collectionBook.order` ASC')]
}) || []
const oldCollection = sequelize.models.collection.getOldCollection(this)
const oldCollection = this.sequelize.models.collection.getOldCollection(this)
// Filter books using user permissions
// TODO: Handle user permission restrictions on initial query
@ -162,7 +177,7 @@ module.exports = (sequelize) => {
const libraryItem = b.libraryItem
delete b.libraryItem
libraryItem.media = b
return sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
})
// Users with restricted permissions will not see this collection
@ -175,7 +190,7 @@ module.exports = (sequelize) => {
if (include?.includes('rssfeed')) {
const feeds = await this.getFeeds()
if (feeds?.length) {
collectionExpanded.rssFeed = sequelize.models.feed.getOldFeed(feeds[0])
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
}
}
@ -231,10 +246,10 @@ module.exports = (sequelize) => {
if (!collectionId) return null
const collection = await this.findByPk(collectionId, {
include: {
model: sequelize.models.book,
include: sequelize.models.libraryItem
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
if (!collection) return null
return this.getOldCollection(collection)
@ -248,16 +263,16 @@ module.exports = (sequelize) => {
this.books = await this.getBooks({
include: [
{
model: sequelize.models.libraryItem
model: this.sequelize.models.libraryItem
},
{
model: sequelize.models.author,
model: this.sequelize.models.author,
through: {
attributes: []
}
},
{
model: sequelize.models.series,
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
@ -267,7 +282,7 @@ module.exports = (sequelize) => {
order: [Sequelize.literal('`collectionBook.order` ASC')]
}) || []
return sequelize.models.collection.getOldCollection(this)
return this.sequelize.models.collection.getOldCollection(this)
}
/**
@ -287,20 +302,24 @@ module.exports = (sequelize) => {
static async getAllForBook(bookId) {
const collections = await this.findAll({
include: {
model: sequelize.models.book,
model: this.sequelize.models.book,
where: {
id: bookId
},
required: true,
include: sequelize.models.libraryItem
include: this.sequelize.models.libraryItem
},
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
return collections.map(c => this.getOldCollection(c))
}
}
Collection.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -317,6 +336,7 @@ module.exports = (sequelize) => {
library.hasMany(Collection)
Collection.belongsTo(library)
return Collection
}
}
module.exports = Collection

View File

@ -1,7 +1,21 @@
const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => {
class CollectionBook extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {number} */
this.order
/** @type {UUIDV4} */
this.bookId
/** @type {UUIDV4} */
this.collectionId
/** @type {Date} */
this.createdAt
}
static removeByIds(collectionId, bookId) {
return this.destroy({
where: {
@ -10,9 +24,9 @@ module.exports = (sequelize) => {
}
})
}
}
CollectionBook.init({
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -41,6 +55,7 @@ module.exports = (sequelize) => {
onDelete: 'CASCADE'
})
CollectionBook.belongsTo(collection)
return CollectionBook
}
}
module.exports = CollectionBook

View File

@ -1,8 +1,34 @@
const { DataTypes, Model } = require('sequelize')
const oldDevice = require('../objects/DeviceInfo')
module.exports = (sequelize) => {
class Device extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.deviceId
/** @type {string} */
this.clientName
/** @type {string} */
this.clientVersion
/** @type {string} */
this.ipAddress
/** @type {string} */
this.deviceName
/** @type {string} */
this.deviceVersion
/** @type {object} */
this.extraData
/** @type {UUIDV4} */
this.userId
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
getOldDevice() {
let browserVersion = null
let sdkVersion = null
@ -85,9 +111,13 @@ module.exports = (sequelize) => {
extraData
}
}
}
Device.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -111,6 +141,7 @@ module.exports = (sequelize) => {
onDelete: 'CASCADE'
})
Device.belongsTo(user)
return Device
}
}
module.exports = Device

View File

@ -1,16 +1,61 @@
const { DataTypes, Model } = require('sequelize')
const oldFeed = require('../objects/Feed')
const areEquivalent = require('../utils/areEquivalent')
/*
* Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
* Feeds can be created from LibraryItem, Collection, Playlist or Series
*/
module.exports = (sequelize) => {
class Feed extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.slug
/** @type {string} */
this.entityType
/** @type {UUIDV4} */
this.entityId
/** @type {Date} */
this.entityUpdatedAt
/** @type {string} */
this.serverAddress
/** @type {string} */
this.feedURL
/** @type {string} */
this.imageURL
/** @type {string} */
this.siteURL
/** @type {string} */
this.title
/** @type {string} */
this.description
/** @type {string} */
this.author
/** @type {string} */
this.podcastType
/** @type {string} */
this.language
/** @type {string} */
this.ownerName
/** @type {string} */
this.ownerEmail
/** @type {boolean} */
this.explicit
/** @type {boolean} */
this.preventIndexing
/** @type {string} */
this.coverPath
/** @type {UUIDV4} */
this.userId
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
static async getOldFeeds() {
const feeds = await this.findAll({
include: {
model: sequelize.models.feedEpisode
model: this.sequelize.models.feedEpisode
}
})
return feeds.map(f => this.getOldFeed(f))
@ -85,7 +130,7 @@ module.exports = (sequelize) => {
const feedExpanded = await this.findOne({
where,
include: {
model: sequelize.models.feedEpisode
model: this.sequelize.models.feedEpisode
}
})
if (!feedExpanded) return null
@ -101,7 +146,7 @@ module.exports = (sequelize) => {
if (!id) return null
const feedExpanded = await this.findByPk(id, {
include: {
model: sequelize.models.feedEpisode
model: this.sequelize.models.feedEpisode
}
})
if (!feedExpanded) return null
@ -114,9 +159,9 @@ module.exports = (sequelize) => {
if (oldFeed.episodes?.length) {
for (const oldFeedEpisode of oldFeed.episodes) {
const feedEpisode = sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
const feedEpisode = this.sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
feedEpisode.feedId = newFeed.id
await sequelize.models.feedEpisode.create(feedEpisode)
await this.sequelize.models.feedEpisode.create(feedEpisode)
}
}
}
@ -126,7 +171,7 @@ module.exports = (sequelize) => {
const feedObj = this.getFromOld(oldFeed)
const existingFeed = await this.findByPk(feedObj.id, {
include: sequelize.models.feedEpisode
include: this.sequelize.models.feedEpisode
})
if (!existingFeed) return false
@ -138,7 +183,7 @@ module.exports = (sequelize) => {
feedEpisode.destroy()
} else {
let episodeHasUpdates = false
const oldFeedEpisodeCleaned = sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
const oldFeedEpisodeCleaned = this.sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
for (const key in oldFeedEpisodeCleaned) {
if (!areEquivalent(oldFeedEpisodeCleaned[key], feedEpisode[key])) {
episodeHasUpdates = true
@ -197,12 +242,20 @@ module.exports = (sequelize) => {
getEntity(options) {
if (!this.entityType) return Promise.resolve(null)
const mixinMethodName = `get${sequelize.uppercaseFirst(this.entityType)}`
const mixinMethodName = `get${this.sequelize.uppercaseFirst(this.entityType)}`
return this[mixinMethodName](options)
}
}
Feed.init({
/**
* Initialize model
*
* Polymorphic association: Feeds can be created from LibraryItem, Collection, Playlist or Series
* @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
*
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -302,6 +355,7 @@ module.exports = (sequelize) => {
delete instance.dataValues.playlist
}
})
return Feed
}
}
module.exports = Feed

View File

@ -1,7 +1,45 @@
const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => {
class FeedEpisode extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.title
/** @type {string} */
this.description
/** @type {string} */
this.siteURL
/** @type {string} */
this.enclosureURL
/** @type {string} */
this.enclosureType
/** @type {BigInt} */
this.enclosureSize
/** @type {string} */
this.pubDate
/** @type {string} */
this.season
/** @type {string} */
this.episode
/** @type {string} */
this.episodeType
/** @type {number} */
this.duration
/** @type {string} */
this.filePath
/** @type {boolean} */
this.explicit
/** @type {UUIDV4} */
this.feedId
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
getOldEpisode() {
const enclosure = {
url: this.enclosureURL,
@ -44,9 +82,13 @@ module.exports = (sequelize) => {
explicit: !!oldFeedEpisode.explicit
}
}
}
FeedEpisode.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -77,6 +119,7 @@ module.exports = (sequelize) => {
onDelete: 'CASCADE'
})
FeedEpisode.belongsTo(feed)
return FeedEpisode
}
}
module.exports = FeedEpisode

View File

@ -2,15 +2,44 @@ const { DataTypes, Model } = require('sequelize')
const Logger = require('../Logger')
const oldLibrary = require('../objects/Library')
module.exports = (sequelize) => {
class Library extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.name
/** @type {number} */
this.displayOrder
/** @type {string} */
this.icon
/** @type {string} */
this.mediaType
/** @type {string} */
this.provider
/** @type {Date} */
this.lastScan
/** @type {string} */
this.lastScanVersion
/** @type {Object} */
this.settings
/** @type {Object} */
this.extraData
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
/**
* Get all old libraries
* @returns {Promise<oldLibrary[]>}
*/
static async getAllOldLibraries() {
const libraries = await this.findAll({
include: sequelize.models.libraryFolder,
include: this.sequelize.models.libraryFolder,
order: [['displayOrder', 'ASC']]
})
return libraries.map(lib => this.getOldLibrary(lib))
@ -60,7 +89,7 @@ module.exports = (sequelize) => {
})
return this.create(library, {
include: sequelize.models.libraryFolder
include: this.sequelize.models.libraryFolder
}).catch((error) => {
Logger.error(`[Library] Failed to create library ${library.id}`, error)
return null
@ -74,7 +103,7 @@ module.exports = (sequelize) => {
*/
static async updateFromOld(oldLibrary) {
const existingLibrary = await this.findByPk(oldLibrary.id, {
include: sequelize.models.libraryFolder
include: this.sequelize.models.libraryFolder
})
if (!existingLibrary) {
Logger.error(`[Library] Failed to update library ${oldLibrary.id} - not found`)
@ -93,7 +122,7 @@ module.exports = (sequelize) => {
for (const libraryFolder of libraryFolders) {
const existingLibraryFolder = existingLibrary.libraryFolders.find(lf => lf.id === libraryFolder.id)
if (!existingLibraryFolder) {
await sequelize.models.libraryFolder.create(libraryFolder)
await this.sequelize.models.libraryFolder.create(libraryFolder)
} else if (existingLibraryFolder.path !== libraryFolder.path) {
await existingLibraryFolder.update({ path: libraryFolder.path })
}
@ -192,9 +221,13 @@ module.exports = (sequelize) => {
}
}
}
}
Library.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -213,6 +246,7 @@ module.exports = (sequelize) => {
sequelize,
modelName: 'library'
})
return Library
}
}
module.exports = Library

View File

@ -1,7 +1,21 @@
const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => {
class LibraryFolder extends Model {
constructor(values, options) {
super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {string} */
this.path
/** @type {UUIDV4} */
this.libraryId
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
/**
* Gets all library folder path strings
* @returns {Promise<string[]>} array of library folder paths
@ -12,9 +26,13 @@ module.exports = (sequelize) => {
})
return libraryFolders.map(l => l.path)
}
}
LibraryFolder.init({
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -31,6 +49,7 @@ module.exports = (sequelize) => {
onDelete: 'CASCADE'
})
LibraryFolder.belongsTo(library)
return LibraryFolder
}
}
module.exports = LibraryFolder