Add jsdoc types to remaining models

This commit is contained in:
advplyr 2023-08-16 16:38:48 -05:00
parent 0bc89cd40f
commit a98942a361
11 changed files with 2302 additions and 1939 deletions

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,148 +1,184 @@
const { DataTypes, Model } = require('sequelize') const { DataTypes, Model } = require('sequelize')
/* class MediaProgress extends Model {
* Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ constructor(values, options) {
* Book has many MediaProgress. PodcastEpisode has many MediaProgress. super(values, options)
*/
module.exports = (sequelize) => {
class MediaProgress extends Model {
getOldMediaProgress() {
const isPodcastEpisode = this.mediaItemType === 'podcastEpisode'
return { /** @type {UUIDV4} */
id: this.id, this.id
userId: this.userId, /** @type {UUIDV4} */
libraryItemId: this.extraData?.libraryItemId || null, this.mediaItemId
episodeId: isPodcastEpisode ? this.mediaItemId : null, /** @type {string} */
mediaItemId: this.mediaItemId, this.mediaItemType
mediaItemType: this.mediaItemType, /** @type {number} */
duration: this.duration, this.duration
progress: this.extraData?.progress || 0, /** @type {number} */
currentTime: this.currentTime, this.currentTime
isFinished: !!this.isFinished, /** @type {boolean} */
hideFromContinueListening: !!this.hideFromContinueListening, this.isFinished
ebookLocation: this.ebookLocation, /** @type {boolean} */
ebookProgress: this.ebookProgress, this.hideFromContinueListening
lastUpdate: this.updatedAt.valueOf(), /** @type {string} */
startedAt: this.createdAt.valueOf(), this.ebookLocation
finishedAt: this.finishedAt?.valueOf() || null /** @type {number} */
} this.ebookProgress
} /** @type {Date} */
this.finishedAt
/** @type {Object} */
this.extraData
/** @type {UUIDV4} */
this.userId
/** @type {Date} */
this.updatedAt
/** @type {Date} */
this.createdAt
}
static upsertFromOld(oldMediaProgress) { getOldMediaProgress() {
const mediaProgress = this.getFromOld(oldMediaProgress) const isPodcastEpisode = this.mediaItemType === 'podcastEpisode'
return this.upsert(mediaProgress)
}
static getFromOld(oldMediaProgress) { return {
return { id: this.id,
id: oldMediaProgress.id, userId: this.userId,
userId: oldMediaProgress.userId, libraryItemId: this.extraData?.libraryItemId || null,
mediaItemId: oldMediaProgress.mediaItemId, episodeId: isPodcastEpisode ? this.mediaItemId : null,
mediaItemType: oldMediaProgress.mediaItemType, mediaItemId: this.mediaItemId,
duration: oldMediaProgress.duration, mediaItemType: this.mediaItemType,
currentTime: oldMediaProgress.currentTime, duration: this.duration,
ebookLocation: oldMediaProgress.ebookLocation || null, progress: this.extraData?.progress || 0,
ebookProgress: oldMediaProgress.ebookProgress || null, currentTime: this.currentTime,
isFinished: !!oldMediaProgress.isFinished, isFinished: !!this.isFinished,
hideFromContinueListening: !!oldMediaProgress.hideFromContinueListening, hideFromContinueListening: !!this.hideFromContinueListening,
finishedAt: oldMediaProgress.finishedAt, ebookLocation: this.ebookLocation,
createdAt: oldMediaProgress.startedAt || oldMediaProgress.lastUpdate, ebookProgress: this.ebookProgress,
updatedAt: oldMediaProgress.lastUpdate, lastUpdate: this.updatedAt.valueOf(),
extraData: { startedAt: this.createdAt.valueOf(),
libraryItemId: oldMediaProgress.libraryItemId, finishedAt: this.finishedAt?.valueOf() || null
progress: oldMediaProgress.progress
}
}
}
static removeById(mediaProgressId) {
return this.destroy({
where: {
id: mediaProgressId
}
})
}
getMediaItem(options) {
if (!this.mediaItemType) return Promise.resolve(null)
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
return this[mixinMethodName](options)
} }
} }
static upsertFromOld(oldMediaProgress) {
const mediaProgress = this.getFromOld(oldMediaProgress)
return this.upsert(mediaProgress)
}
MediaProgress.init({ static getFromOld(oldMediaProgress) {
id: { return {
type: DataTypes.UUID, id: oldMediaProgress.id,
defaultValue: DataTypes.UUIDV4, userId: oldMediaProgress.userId,
primaryKey: true mediaItemId: oldMediaProgress.mediaItemId,
}, mediaItemType: oldMediaProgress.mediaItemType,
mediaItemId: DataTypes.UUIDV4, duration: oldMediaProgress.duration,
mediaItemType: DataTypes.STRING, currentTime: oldMediaProgress.currentTime,
duration: DataTypes.FLOAT, ebookLocation: oldMediaProgress.ebookLocation || null,
currentTime: DataTypes.FLOAT, ebookProgress: oldMediaProgress.ebookProgress || null,
isFinished: DataTypes.BOOLEAN, isFinished: !!oldMediaProgress.isFinished,
hideFromContinueListening: DataTypes.BOOLEAN, hideFromContinueListening: !!oldMediaProgress.hideFromContinueListening,
ebookLocation: DataTypes.STRING, finishedAt: oldMediaProgress.finishedAt,
ebookProgress: DataTypes.FLOAT, createdAt: oldMediaProgress.startedAt || oldMediaProgress.lastUpdate,
finishedAt: DataTypes.DATE, updatedAt: oldMediaProgress.lastUpdate,
extraData: DataTypes.JSON extraData: {
}, { libraryItemId: oldMediaProgress.libraryItemId,
sequelize, progress: oldMediaProgress.progress
modelName: 'mediaProgress',
indexes: [
{
fields: ['updatedAt']
} }
]
})
const { book, podcastEpisode, user } = sequelize.models
book.hasMany(MediaProgress, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'book'
} }
}) }
MediaProgress.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
podcastEpisode.hasMany(MediaProgress, { static removeById(mediaProgressId) {
foreignKey: 'mediaItemId', return this.destroy({
constraints: false, where: {
scope: { id: mediaProgressId
mediaItemType: 'podcastEpisode'
}
})
MediaProgress.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
MediaProgress.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.mediaItem = instance.book
instance.dataValues.mediaItem = instance.dataValues.book
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: })
delete instance.book }
delete instance.dataValues.book
delete instance.podcastEpisode
delete instance.dataValues.podcastEpisode
}
})
user.hasMany(MediaProgress, { getMediaItem(options) {
onDelete: 'CASCADE' if (!this.mediaItemType) return Promise.resolve(null)
}) const mixinMethodName = `get${this.sequelize.uppercaseFirst(this.mediaItemType)}`
MediaProgress.belongsTo(user) return this[mixinMethodName](options)
}
return MediaProgress /**
} * Initialize model
*
* Polymorphic association: Book has many MediaProgress. PodcastEpisode has many MediaProgress.
* @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,
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
}, {
sequelize,
modelName: 'mediaProgress',
indexes: [
{
fields: ['updatedAt']
}
]
})
const { book, podcastEpisode, user } = sequelize.models
book.hasMany(MediaProgress, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'book'
}
})
MediaProgress.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
podcastEpisode.hasMany(MediaProgress, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'podcastEpisode'
}
})
MediaProgress.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
MediaProgress.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.mediaItem = instance.book
instance.dataValues.mediaItem = instance.dataValues.book
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
}
// To prevent mistakes:
delete instance.book
delete instance.dataValues.book
delete instance.podcastEpisode
delete instance.dataValues.podcastEpisode
}
})
user.hasMany(MediaProgress, {
onDelete: 'CASCADE'
})
MediaProgress.belongsTo(user)
}
}
module.exports = MediaProgress

View File

@ -2,197 +2,251 @@ const { DataTypes, Model } = require('sequelize')
const oldPlaybackSession = require('../objects/PlaybackSession') const oldPlaybackSession = require('../objects/PlaybackSession')
module.exports = (sequelize) => {
class PlaybackSession extends Model {
static async getOldPlaybackSessions(where = null) {
const playbackSessions = await this.findAll({
where,
include: [
{
model: sequelize.models.device
}
]
})
return playbackSessions.map(session => this.getOldPlaybackSession(session))
}
static async getById(sessionId) { class PlaybackSession extends Model {
const playbackSession = await this.findByPk(sessionId, { constructor(values, options) {
include: [ super(values, options)
{
model: sequelize.models.device
}
]
})
if (!playbackSession) return null
return this.getOldPlaybackSession(playbackSession)
}
static getOldPlaybackSession(playbackSessionExpanded) { /** @type {UUIDV4} */
const isPodcastEpisode = playbackSessionExpanded.mediaItemType === 'podcastEpisode' this.id
/** @type {UUIDV4} */
this.mediaItemId
/** @type {string} */
this.mediaItemType
/** @type {string} */
this.displayTitle
/** @type {string} */
this.displayAuthor
/** @type {number} */
this.duration
/** @type {number} */
this.playMethod
/** @type {string} */
this.mediaPlayer
/** @type {number} */
this.startTime
/** @type {number} */
this.currentTime
/** @type {string} */
this.serverVersion
/** @type {string} */
this.coverPath
/** @type {number} */
this.timeListening
/** @type {Object} */
this.mediaMetadata
/** @type {string} */
this.date
/** @type {string} */
this.dayOfWeek
/** @type {Object} */
this.extraData
/** @type {UUIDV4} */
this.userId
/** @type {UUIDV4} */
this.deviceId
/** @type {UUIDV4} */
this.libraryId
/** @type {Date} */
this.updatedAt
/** @type {Date} */
this.createdAt
}
return new oldPlaybackSession({ static async getOldPlaybackSessions(where = null) {
id: playbackSessionExpanded.id, const playbackSessions = await this.findAll({
userId: playbackSessionExpanded.userId, where,
libraryId: playbackSessionExpanded.libraryId, include: [
libraryItemId: playbackSessionExpanded.extraData?.libraryItemId || null, {
bookId: isPodcastEpisode ? null : playbackSessionExpanded.mediaItemId, model: this.sequelize.models.device
episodeId: isPodcastEpisode ? playbackSessionExpanded.mediaItemId : null,
mediaType: isPodcastEpisode ? 'podcast' : 'book',
mediaMetadata: playbackSessionExpanded.mediaMetadata,
chapters: null,
displayTitle: playbackSessionExpanded.displayTitle,
displayAuthor: playbackSessionExpanded.displayAuthor,
coverPath: playbackSessionExpanded.coverPath,
duration: playbackSessionExpanded.duration,
playMethod: playbackSessionExpanded.playMethod,
mediaPlayer: playbackSessionExpanded.mediaPlayer,
deviceInfo: playbackSessionExpanded.device?.getOldDevice() || null,
serverVersion: playbackSessionExpanded.serverVersion,
date: playbackSessionExpanded.date,
dayOfWeek: playbackSessionExpanded.dayOfWeek,
timeListening: playbackSessionExpanded.timeListening,
startTime: playbackSessionExpanded.startTime,
currentTime: playbackSessionExpanded.currentTime,
startedAt: playbackSessionExpanded.createdAt.valueOf(),
updatedAt: playbackSessionExpanded.updatedAt.valueOf()
})
}
static removeById(sessionId) {
return this.destroy({
where: {
id: sessionId
} }
}) ]
} })
return playbackSessions.map(session => this.getOldPlaybackSession(session))
}
static createFromOld(oldPlaybackSession) { static async getById(sessionId) {
const playbackSession = this.getFromOld(oldPlaybackSession) const playbackSession = await this.findByPk(sessionId, {
return this.create(playbackSession) include: [
} {
model: this.sequelize.models.device
static updateFromOld(oldPlaybackSession) {
const playbackSession = this.getFromOld(oldPlaybackSession)
return this.update(playbackSession, {
where: {
id: playbackSession.id
} }
}) ]
} })
if (!playbackSession) return null
return this.getOldPlaybackSession(playbackSession)
}
static getFromOld(oldPlaybackSession) { static getOldPlaybackSession(playbackSessionExpanded) {
return { const isPodcastEpisode = playbackSessionExpanded.mediaItemType === 'podcastEpisode'
id: oldPlaybackSession.id,
mediaItemId: oldPlaybackSession.episodeId || oldPlaybackSession.bookId, return new oldPlaybackSession({
mediaItemType: oldPlaybackSession.episodeId ? 'podcastEpisode' : 'book', id: playbackSessionExpanded.id,
libraryId: oldPlaybackSession.libraryId, userId: playbackSessionExpanded.userId,
displayTitle: oldPlaybackSession.displayTitle, libraryId: playbackSessionExpanded.libraryId,
displayAuthor: oldPlaybackSession.displayAuthor, libraryItemId: playbackSessionExpanded.extraData?.libraryItemId || null,
duration: oldPlaybackSession.duration, bookId: isPodcastEpisode ? null : playbackSessionExpanded.mediaItemId,
playMethod: oldPlaybackSession.playMethod, episodeId: isPodcastEpisode ? playbackSessionExpanded.mediaItemId : null,
mediaPlayer: oldPlaybackSession.mediaPlayer, mediaType: isPodcastEpisode ? 'podcast' : 'book',
startTime: oldPlaybackSession.startTime, mediaMetadata: playbackSessionExpanded.mediaMetadata,
currentTime: oldPlaybackSession.currentTime, chapters: null,
serverVersion: oldPlaybackSession.serverVersion || null, displayTitle: playbackSessionExpanded.displayTitle,
createdAt: oldPlaybackSession.startedAt, displayAuthor: playbackSessionExpanded.displayAuthor,
updatedAt: oldPlaybackSession.updatedAt, coverPath: playbackSessionExpanded.coverPath,
userId: oldPlaybackSession.userId, duration: playbackSessionExpanded.duration,
deviceId: oldPlaybackSession.deviceInfo?.id || null, playMethod: playbackSessionExpanded.playMethod,
timeListening: oldPlaybackSession.timeListening, mediaPlayer: playbackSessionExpanded.mediaPlayer,
coverPath: oldPlaybackSession.coverPath, deviceInfo: playbackSessionExpanded.device?.getOldDevice() || null,
mediaMetadata: oldPlaybackSession.mediaMetadata, serverVersion: playbackSessionExpanded.serverVersion,
date: oldPlaybackSession.date, date: playbackSessionExpanded.date,
dayOfWeek: oldPlaybackSession.dayOfWeek, dayOfWeek: playbackSessionExpanded.dayOfWeek,
extraData: { timeListening: playbackSessionExpanded.timeListening,
libraryItemId: oldPlaybackSession.libraryItemId startTime: playbackSessionExpanded.startTime,
} currentTime: playbackSessionExpanded.currentTime,
startedAt: playbackSessionExpanded.createdAt.valueOf(),
updatedAt: playbackSessionExpanded.updatedAt.valueOf()
})
}
static removeById(sessionId) {
return this.destroy({
where: {
id: sessionId
} }
} })
}
getMediaItem(options) { static createFromOld(oldPlaybackSession) {
if (!this.mediaItemType) return Promise.resolve(null) const playbackSession = this.getFromOld(oldPlaybackSession)
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}` return this.create(playbackSession)
return this[mixinMethodName](options) }
static updateFromOld(oldPlaybackSession) {
const playbackSession = this.getFromOld(oldPlaybackSession)
return this.update(playbackSession, {
where: {
id: playbackSession.id
}
})
}
static getFromOld(oldPlaybackSession) {
return {
id: oldPlaybackSession.id,
mediaItemId: oldPlaybackSession.episodeId || oldPlaybackSession.bookId,
mediaItemType: oldPlaybackSession.episodeId ? 'podcastEpisode' : 'book',
libraryId: oldPlaybackSession.libraryId,
displayTitle: oldPlaybackSession.displayTitle,
displayAuthor: oldPlaybackSession.displayAuthor,
duration: oldPlaybackSession.duration,
playMethod: oldPlaybackSession.playMethod,
mediaPlayer: oldPlaybackSession.mediaPlayer,
startTime: oldPlaybackSession.startTime,
currentTime: oldPlaybackSession.currentTime,
serverVersion: oldPlaybackSession.serverVersion || null,
createdAt: oldPlaybackSession.startedAt,
updatedAt: oldPlaybackSession.updatedAt,
userId: oldPlaybackSession.userId,
deviceId: oldPlaybackSession.deviceInfo?.id || null,
timeListening: oldPlaybackSession.timeListening,
coverPath: oldPlaybackSession.coverPath,
mediaMetadata: oldPlaybackSession.mediaMetadata,
date: oldPlaybackSession.date,
dayOfWeek: oldPlaybackSession.dayOfWeek,
extraData: {
libraryItemId: oldPlaybackSession.libraryItemId
}
} }
} }
PlaybackSession.init({ getMediaItem(options) {
id: { if (!this.mediaItemType) return Promise.resolve(null)
type: DataTypes.UUID, const mixinMethodName = `get${this.sequelize.uppercaseFirst(this.mediaItemType)}`
defaultValue: DataTypes.UUIDV4, return this[mixinMethodName](options)
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
}, {
sequelize,
modelName: 'playbackSession'
})
const { book, podcastEpisode, user, device, library } = sequelize.models /**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
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
}, {
sequelize,
modelName: 'playbackSession'
})
user.hasMany(PlaybackSession) const { book, podcastEpisode, user, device, library } = sequelize.models
PlaybackSession.belongsTo(user)
device.hasMany(PlaybackSession) user.hasMany(PlaybackSession)
PlaybackSession.belongsTo(device) PlaybackSession.belongsTo(user)
library.hasMany(PlaybackSession) device.hasMany(PlaybackSession)
PlaybackSession.belongsTo(library) PlaybackSession.belongsTo(device)
book.hasMany(PlaybackSession, { library.hasMany(PlaybackSession)
foreignKey: 'mediaItemId', PlaybackSession.belongsTo(library)
constraints: false,
scope: {
mediaItemType: 'book'
}
})
PlaybackSession.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
podcastEpisode.hasOne(PlaybackSession, { book.hasMany(PlaybackSession, {
foreignKey: 'mediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'podcastEpisode' mediaItemType: 'book'
}
})
PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaybackSession.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.mediaItem = instance.book
instance.dataValues.mediaItem = instance.dataValues.book
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: })
delete instance.book PlaybackSession.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
delete instance.dataValues.book
delete instance.podcastEpisode
delete instance.dataValues.podcastEpisode
}
})
return PlaybackSession podcastEpisode.hasOne(PlaybackSession, {
} foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'podcastEpisode'
}
})
PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaybackSession.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.mediaItem = instance.book
instance.dataValues.mediaItem = instance.dataValues.book
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
}
// To prevent mistakes:
delete instance.book
delete instance.dataValues.book
delete instance.podcastEpisode
delete instance.dataValues.podcastEpisode
}
})
}
}
module.exports = PlaybackSession

View File

@ -3,318 +3,341 @@ const Logger = require('../Logger')
const oldPlaylist = require('../objects/Playlist') const oldPlaylist = require('../objects/Playlist')
module.exports = (sequelize) => { class Playlist extends Model {
class Playlist extends Model { constructor(values, options) {
static async getOldPlaylists() { super(values, options)
const playlists = await this.findAll({
include: {
model: sequelize.models.playlistMediaItem,
include: [
{
model: sequelize.models.book,
include: sequelize.models.libraryItem
},
{
model: sequelize.models.podcastEpisode,
include: {
model: sequelize.models.podcast,
include: sequelize.models.libraryItem
}
}
]
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
return playlists.map(p => this.getOldPlaylist(p))
}
static getOldPlaylist(playlistExpanded) { /** @type {UUIDV4} */
const items = playlistExpanded.playlistMediaItems.map(pmi => { this.id
const libraryItemId = pmi.mediaItem?.podcast?.libraryItem?.id || pmi.mediaItem?.libraryItem?.id || null /** @type {string} */
if (!libraryItemId) { this.name
Logger.error(`[Playlist] Invalid playlist media item - No library item id found`, JSON.stringify(pmi, null, 2)) /** @type {string} */
return null this.description
} /** @type {UUIDV4} */
return { this.libraryId
episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '', /** @type {UUIDV4} */
libraryItemId this.userId
} /** @type {Date} */
}).filter(pmi => pmi) this.createdAt
/** @type {Date} */
this.updatedAt
}
return new oldPlaylist({ static async getOldPlaylists() {
id: playlistExpanded.id, const playlists = await this.findAll({
libraryId: playlistExpanded.libraryId, include: {
userId: playlistExpanded.userId, model: this.sequelize.models.playlistMediaItem,
name: playlistExpanded.name,
description: playlistExpanded.description,
items,
lastUpdate: playlistExpanded.updatedAt.valueOf(),
createdAt: playlistExpanded.createdAt.valueOf()
})
}
/**
* Get old playlist toJSONExpanded
* @param {[string[]]} include
* @returns {Promise<object>} oldPlaylist.toJSONExpanded
*/
async getOldJsonExpanded(include) {
this.playlistMediaItems = await this.getPlaylistMediaItems({
include: [ include: [
{ {
model: sequelize.models.book, model: this.sequelize.models.book,
include: sequelize.models.libraryItem include: this.sequelize.models.libraryItem
}, },
{ {
model: sequelize.models.podcastEpisode, model: this.sequelize.models.podcastEpisode,
include: { include: {
model: sequelize.models.podcast, model: this.sequelize.models.podcast,
include: sequelize.models.libraryItem include: this.sequelize.models.libraryItem
} }
} }
],
order: [['order', 'ASC']]
}) || []
const oldPlaylist = sequelize.models.playlist.getOldPlaylist(this)
const libraryItemIds = oldPlaylist.items.map(i => i.libraryItemId)
let libraryItems = await sequelize.models.libraryItem.getAllOldLibraryItems({
id: libraryItemIds
})
const playlistExpanded = oldPlaylist.toJSONExpanded(libraryItems)
if (include?.includes('rssfeed')) {
const feeds = await this.getFeeds()
if (feeds?.length) {
playlistExpanded.rssFeed = sequelize.models.feed.getOldFeed(feeds[0])
}
}
return playlistExpanded
}
static createFromOld(oldPlaylist) {
const playlist = this.getFromOld(oldPlaylist)
return this.create(playlist)
}
static getFromOld(oldPlaylist) {
return {
id: oldPlaylist.id,
name: oldPlaylist.name,
description: oldPlaylist.description,
userId: oldPlaylist.userId,
libraryId: oldPlaylist.libraryId
}
}
static removeById(playlistId) {
return this.destroy({
where: {
id: playlistId
}
})
}
/**
* Get playlist by id
* @param {string} playlistId
* @returns {Promise<oldPlaylist|null>} returns null if not found
*/
static async getById(playlistId) {
if (!playlistId) return null
const playlist = await this.findByPk(playlistId, {
include: {
model: sequelize.models.playlistMediaItem,
include: [
{
model: sequelize.models.book,
include: sequelize.models.libraryItem
},
{
model: sequelize.models.podcastEpisode,
include: {
model: sequelize.models.podcast,
include: sequelize.models.libraryItem
}
}
]
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
if (!playlist) return null
return this.getOldPlaylist(playlist)
}
/**
* Get playlists for user and optionally for library
* @param {string} userId
* @param {[string]} libraryId optional
* @returns {Promise<Playlist[]>}
*/
static async getPlaylistsForUserAndLibrary(userId, libraryId = null) {
if (!userId && !libraryId) return []
const whereQuery = {}
if (userId) {
whereQuery.userId = userId
}
if (libraryId) {
whereQuery.libraryId = libraryId
}
const playlists = await this.findAll({
where: whereQuery,
include: {
model: sequelize.models.playlistMediaItem,
include: [
{
model: sequelize.models.book,
include: sequelize.models.libraryItem
},
{
model: sequelize.models.podcastEpisode,
include: {
model: sequelize.models.podcast,
include: sequelize.models.libraryItem
}
}
]
},
order: [
[literal('name COLLATE NOCASE'), 'ASC'],
['playlistMediaItems', 'order', 'ASC']
] ]
}) },
return playlists order: [['playlistMediaItems', 'order', 'ASC']]
} })
return playlists.map(p => this.getOldPlaylist(p))
}
/** static getOldPlaylist(playlistExpanded) {
* Get number of playlists for a user and library const items = playlistExpanded.playlistMediaItems.map(pmi => {
* @param {string} userId const libraryItemId = pmi.mediaItem?.podcast?.libraryItem?.id || pmi.mediaItem?.libraryItem?.id || null
* @param {string} libraryId if (!libraryItemId) {
* @returns Logger.error(`[Playlist] Invalid playlist media item - No library item id found`, JSON.stringify(pmi, null, 2))
*/ return null
static async getNumPlaylistsForUserAndLibrary(userId, libraryId) {
return this.count({
where: {
userId,
libraryId
}
})
}
/**
* Get all playlists for mediaItemIds
* @param {string[]} mediaItemIds
* @returns {Promise<Playlist[]>}
*/
static async getPlaylistsForMediaItemIds(mediaItemIds) {
if (!mediaItemIds?.length) return []
const playlistMediaItemsExpanded = await sequelize.models.playlistMediaItem.findAll({
where: {
mediaItemId: {
[Op.in]: mediaItemIds
}
},
include: [
{
model: sequelize.models.playlist,
include: {
model: sequelize.models.playlistMediaItem,
include: [
{
model: sequelize.models.book,
include: sequelize.models.libraryItem
},
{
model: sequelize.models.podcastEpisode,
include: {
model: sequelize.models.podcast,
include: sequelize.models.libraryItem
}
}
]
}
}
],
order: [['playlist', 'playlistMediaItems', 'order', 'ASC']]
})
const playlists = []
for (const playlistMediaItem of playlistMediaItemsExpanded) {
const playlist = playlistMediaItem.playlist
if (playlists.some(p => p.id === playlist.id)) continue
playlist.playlistMediaItems = playlist.playlistMediaItems.map(pmi => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
pmi.mediaItem = pmi.podcastEpisode
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
}
delete pmi.book
delete pmi.dataValues.book
delete pmi.podcastEpisode
delete pmi.dataValues.podcastEpisode
return pmi
})
playlists.push(playlist)
} }
return playlists return {
episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '',
libraryItemId
}
}).filter(pmi => pmi)
return new oldPlaylist({
id: playlistExpanded.id,
libraryId: playlistExpanded.libraryId,
userId: playlistExpanded.userId,
name: playlistExpanded.name,
description: playlistExpanded.description,
items,
lastUpdate: playlistExpanded.updatedAt.valueOf(),
createdAt: playlistExpanded.createdAt.valueOf()
})
}
/**
* Get old playlist toJSONExpanded
* @param {[string[]]} include
* @returns {Promise<object>} oldPlaylist.toJSONExpanded
*/
async getOldJsonExpanded(include) {
this.playlistMediaItems = await this.getPlaylistMediaItems({
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: [['order', 'ASC']]
}) || []
const oldPlaylist = this.sequelize.models.playlist.getOldPlaylist(this)
const libraryItemIds = oldPlaylist.items.map(i => i.libraryItemId)
let libraryItems = await this.sequelize.models.libraryItem.getAllOldLibraryItems({
id: libraryItemIds
})
const playlistExpanded = oldPlaylist.toJSONExpanded(libraryItems)
if (include?.includes('rssfeed')) {
const feeds = await this.getFeeds()
if (feeds?.length) {
playlistExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
}
}
return playlistExpanded
}
static createFromOld(oldPlaylist) {
const playlist = this.getFromOld(oldPlaylist)
return this.create(playlist)
}
static getFromOld(oldPlaylist) {
return {
id: oldPlaylist.id,
name: oldPlaylist.name,
description: oldPlaylist.description,
userId: oldPlaylist.userId,
libraryId: oldPlaylist.libraryId
} }
} }
Playlist.init({ static removeById(playlistId) {
id: { return this.destroy({
type: DataTypes.UUID, where: {
defaultValue: DataTypes.UUIDV4, id: playlistId
primaryKey: true
},
name: DataTypes.STRING,
description: DataTypes.TEXT
}, {
sequelize,
modelName: 'playlist'
})
const { library, user } = sequelize.models
library.hasMany(Playlist)
Playlist.belongsTo(library)
user.hasMany(Playlist, {
onDelete: 'CASCADE'
})
Playlist.belongsTo(user)
Playlist.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.playlistMediaItems?.length) {
instance.playlistMediaItems = instance.playlistMediaItems.map(pmi => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
pmi.mediaItem = pmi.podcastEpisode
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
}
// To prevent mistakes:
delete pmi.book
delete pmi.dataValues.book
delete pmi.podcastEpisode
delete pmi.dataValues.podcastEpisode
return pmi
})
} }
})
}
/**
* Get playlist by id
* @param {string} playlistId
* @returns {Promise<oldPlaylist|null>} returns null if not found
*/
static async getById(playlistId) {
if (!playlistId) return null
const playlist = await this.findByPk(playlistId, {
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']]
})
if (!playlist) return null
return this.getOldPlaylist(playlist)
}
/**
* Get playlists for user and optionally for library
* @param {string} userId
* @param {[string]} libraryId optional
* @returns {Promise<Playlist[]>}
*/
static async getPlaylistsForUserAndLibrary(userId, libraryId = null) {
if (!userId && !libraryId) return []
const whereQuery = {}
if (userId) {
whereQuery.userId = userId
} }
}) if (libraryId) {
whereQuery.libraryId = libraryId
}
const playlists = await this.findAll({
where: whereQuery,
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: [
[literal('name COLLATE NOCASE'), 'ASC'],
['playlistMediaItems', 'order', 'ASC']
]
})
return playlists
}
return Playlist /**
} * Get number of playlists for a user and library
* @param {string} userId
* @param {string} libraryId
* @returns
*/
static async getNumPlaylistsForUserAndLibrary(userId, libraryId) {
return this.count({
where: {
userId,
libraryId
}
})
}
/**
* Get all playlists for mediaItemIds
* @param {string[]} mediaItemIds
* @returns {Promise<Playlist[]>}
*/
static async getPlaylistsForMediaItemIds(mediaItemIds) {
if (!mediaItemIds?.length) return []
const playlistMediaItemsExpanded = await this.sequelize.models.playlistMediaItem.findAll({
where: {
mediaItemId: {
[Op.in]: mediaItemIds
}
},
include: [
{
model: this.sequelize.models.playlist,
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: [['playlist', 'playlistMediaItems', 'order', 'ASC']]
})
const playlists = []
for (const playlistMediaItem of playlistMediaItemsExpanded) {
const playlist = playlistMediaItem.playlist
if (playlists.some(p => p.id === playlist.id)) continue
playlist.playlistMediaItems = playlist.playlistMediaItems.map(pmi => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
pmi.mediaItem = pmi.podcastEpisode
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
}
delete pmi.book
delete pmi.dataValues.book
delete pmi.podcastEpisode
delete pmi.dataValues.podcastEpisode
return pmi
})
playlists.push(playlist)
}
return playlists
}
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
description: DataTypes.TEXT
}, {
sequelize,
modelName: 'playlist'
})
const { library, user } = sequelize.models
library.hasMany(Playlist)
Playlist.belongsTo(library)
user.hasMany(Playlist, {
onDelete: 'CASCADE'
})
Playlist.belongsTo(user)
Playlist.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.playlistMediaItems?.length) {
instance.playlistMediaItems = instance.playlistMediaItems.map(pmi => {
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
pmi.mediaItem = pmi.book
pmi.dataValues.mediaItem = pmi.dataValues.book
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
pmi.mediaItem = pmi.podcastEpisode
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
}
// To prevent mistakes:
delete pmi.book
delete pmi.dataValues.book
delete pmi.podcastEpisode
delete pmi.dataValues.podcastEpisode
return pmi
})
}
}
})
}
}
module.exports = Playlist

View File

@ -1,84 +1,105 @@
const { DataTypes, Model } = require('sequelize') const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => { class PlaylistMediaItem extends Model {
class PlaylistMediaItem extends Model { constructor(values, options) {
static removeByIds(playlistId, mediaItemId) { super(values, options)
return this.destroy({
where: {
playlistId,
mediaItemId
}
})
}
getMediaItem(options) { /** @type {UUIDV4} */
if (!this.mediaItemType) return Promise.resolve(null) this.id
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}` /** @type {UUIDV4} */
return this[mixinMethodName](options) this.mediaItemId
} /** @type {string} */
this.mediaItemType
/** @type {number} */
this.order
/** @type {UUIDV4} */
this.playlistId
/** @type {Date} */
this.createdAt
} }
PlaylistMediaItem.init({ static removeByIds(playlistId, mediaItemId) {
id: { return this.destroy({
type: DataTypes.UUID, where: {
defaultValue: DataTypes.UUIDV4, playlistId,
primaryKey: true mediaItemId
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
order: DataTypes.INTEGER
}, {
sequelize,
timestamps: true,
updatedAt: false,
modelName: 'playlistMediaItem'
})
const { book, podcastEpisode, playlist } = sequelize.models
book.hasMany(PlaylistMediaItem, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'book'
}
})
PlaylistMediaItem.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
podcastEpisode.hasOne(PlaylistMediaItem, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'podcastEpisode'
}
})
PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaylistMediaItem.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.mediaItem = instance.book
instance.dataValues.mediaItem = instance.dataValues.book
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: })
delete instance.book }
delete instance.dataValues.book
delete instance.podcastEpisode
delete instance.dataValues.podcastEpisode
}
})
playlist.hasMany(PlaylistMediaItem, { getMediaItem(options) {
onDelete: 'CASCADE' if (!this.mediaItemType) return Promise.resolve(null)
}) const mixinMethodName = `get${this.sequelize.uppercaseFirst(this.mediaItemType)}`
PlaylistMediaItem.belongsTo(playlist) return this[mixinMethodName](options)
}
return PlaylistMediaItem /**
} * Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
order: DataTypes.INTEGER
}, {
sequelize,
timestamps: true,
updatedAt: false,
modelName: 'playlistMediaItem'
})
const { book, podcastEpisode, playlist } = sequelize.models
book.hasMany(PlaylistMediaItem, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'book'
}
})
PlaylistMediaItem.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
podcastEpisode.hasOne(PlaylistMediaItem, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'podcastEpisode'
}
})
PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaylistMediaItem.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.mediaItem = instance.book
instance.dataValues.mediaItem = instance.dataValues.book
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
}
// To prevent mistakes:
delete instance.book
delete instance.dataValues.book
delete instance.podcastEpisode
delete instance.dataValues.podcastEpisode
}
})
playlist.hasMany(PlaylistMediaItem, {
onDelete: 'CASCADE'
})
PlaylistMediaItem.belongsTo(playlist)
}
}
module.exports = PlaylistMediaItem

View File

@ -1,100 +1,155 @@
const { DataTypes, Model } = require('sequelize') const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => { class Podcast extends Model {
class Podcast extends Model { constructor(values, options) {
static getOldPodcast(libraryItemExpanded) { super(values, options)
const podcastExpanded = libraryItemExpanded.media
const podcastEpisodes = podcastExpanded.podcastEpisodes?.map(ep => ep.getOldPodcastEpisode(libraryItemExpanded.id)).sort((a, b) => a.index - b.index)
return {
id: podcastExpanded.id,
libraryItemId: libraryItemExpanded.id,
metadata: {
title: podcastExpanded.title,
author: podcastExpanded.author,
description: podcastExpanded.description,
releaseDate: podcastExpanded.releaseDate,
genres: podcastExpanded.genres,
feedUrl: podcastExpanded.feedURL,
imageUrl: podcastExpanded.imageURL,
itunesPageUrl: podcastExpanded.itunesPageURL,
itunesId: podcastExpanded.itunesId,
itunesArtistId: podcastExpanded.itunesArtistId,
explicit: podcastExpanded.explicit,
language: podcastExpanded.language,
type: podcastExpanded.podcastType
},
coverPath: podcastExpanded.coverPath,
tags: podcastExpanded.tags,
episodes: podcastEpisodes || [],
autoDownloadEpisodes: podcastExpanded.autoDownloadEpisodes,
autoDownloadSchedule: podcastExpanded.autoDownloadSchedule,
lastEpisodeCheck: podcastExpanded.lastEpisodeCheck?.valueOf() || null,
maxEpisodesToKeep: podcastExpanded.maxEpisodesToKeep,
maxNewEpisodesToDownload: podcastExpanded.maxNewEpisodesToDownload
}
}
static getFromOld(oldPodcast) { /** @type {UUIDV4} */
const oldPodcastMetadata = oldPodcast.metadata this.id
return { /** @type {string} */
id: oldPodcast.id, this.title
title: oldPodcastMetadata.title, /** @type {string} */
titleIgnorePrefix: oldPodcastMetadata.titleIgnorePrefix, this.titleIgnorePrefix
author: oldPodcastMetadata.author, /** @type {string} */
releaseDate: oldPodcastMetadata.releaseDate, this.author
feedURL: oldPodcastMetadata.feedUrl, /** @type {string} */
imageURL: oldPodcastMetadata.imageUrl, this.releaseDate
description: oldPodcastMetadata.description, /** @type {string} */
itunesPageURL: oldPodcastMetadata.itunesPageUrl, this.feedURL
itunesId: oldPodcastMetadata.itunesId, /** @type {string} */
itunesArtistId: oldPodcastMetadata.itunesArtistId, this.imageURL
language: oldPodcastMetadata.language, /** @type {string} */
podcastType: oldPodcastMetadata.type, this.description
explicit: !!oldPodcastMetadata.explicit, /** @type {string} */
autoDownloadEpisodes: !!oldPodcast.autoDownloadEpisodes, this.itunesPageURL
autoDownloadSchedule: oldPodcast.autoDownloadSchedule, /** @type {string} */
lastEpisodeCheck: oldPodcast.lastEpisodeCheck, this.itunesId
maxEpisodesToKeep: oldPodcast.maxEpisodesToKeep, /** @type {string} */
maxNewEpisodesToDownload: oldPodcast.maxNewEpisodesToDownload, this.itunesArtistId
coverPath: oldPodcast.coverPath, /** @type {string} */
tags: oldPodcast.tags, this.language
genres: oldPodcastMetadata.genres /** @type {string} */
} this.podcastType
/** @type {boolean} */
this.explicit
/** @type {boolean} */
this.autoDownloadEpisodes
/** @type {string} */
this.autoDownloadSchedule
/** @type {Date} */
this.lastEpisodeCheck
/** @type {number} */
this.maxEpisodesToKeep
/** @type {string} */
this.coverPath
/** @type {Object} */
this.tags
/** @type {Object} */
this.genres
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
static getOldPodcast(libraryItemExpanded) {
const podcastExpanded = libraryItemExpanded.media
const podcastEpisodes = podcastExpanded.podcastEpisodes?.map(ep => ep.getOldPodcastEpisode(libraryItemExpanded.id)).sort((a, b) => a.index - b.index)
return {
id: podcastExpanded.id,
libraryItemId: libraryItemExpanded.id,
metadata: {
title: podcastExpanded.title,
author: podcastExpanded.author,
description: podcastExpanded.description,
releaseDate: podcastExpanded.releaseDate,
genres: podcastExpanded.genres,
feedUrl: podcastExpanded.feedURL,
imageUrl: podcastExpanded.imageURL,
itunesPageUrl: podcastExpanded.itunesPageURL,
itunesId: podcastExpanded.itunesId,
itunesArtistId: podcastExpanded.itunesArtistId,
explicit: podcastExpanded.explicit,
language: podcastExpanded.language,
type: podcastExpanded.podcastType
},
coverPath: podcastExpanded.coverPath,
tags: podcastExpanded.tags,
episodes: podcastEpisodes || [],
autoDownloadEpisodes: podcastExpanded.autoDownloadEpisodes,
autoDownloadSchedule: podcastExpanded.autoDownloadSchedule,
lastEpisodeCheck: podcastExpanded.lastEpisodeCheck?.valueOf() || null,
maxEpisodesToKeep: podcastExpanded.maxEpisodesToKeep,
maxNewEpisodesToDownload: podcastExpanded.maxNewEpisodesToDownload
} }
} }
Podcast.init({ static getFromOld(oldPodcast) {
id: { const oldPodcastMetadata = oldPodcast.metadata
type: DataTypes.UUID, return {
defaultValue: DataTypes.UUIDV4, id: oldPodcast.id,
primaryKey: true title: oldPodcastMetadata.title,
}, titleIgnorePrefix: oldPodcastMetadata.titleIgnorePrefix,
title: DataTypes.STRING, author: oldPodcastMetadata.author,
titleIgnorePrefix: DataTypes.STRING, releaseDate: oldPodcastMetadata.releaseDate,
author: DataTypes.STRING, feedURL: oldPodcastMetadata.feedUrl,
releaseDate: DataTypes.STRING, imageURL: oldPodcastMetadata.imageUrl,
feedURL: DataTypes.STRING, description: oldPodcastMetadata.description,
imageURL: DataTypes.STRING, itunesPageURL: oldPodcastMetadata.itunesPageUrl,
description: DataTypes.TEXT, itunesId: oldPodcastMetadata.itunesId,
itunesPageURL: DataTypes.STRING, itunesArtistId: oldPodcastMetadata.itunesArtistId,
itunesId: DataTypes.STRING, language: oldPodcastMetadata.language,
itunesArtistId: DataTypes.STRING, podcastType: oldPodcastMetadata.type,
language: DataTypes.STRING, explicit: !!oldPodcastMetadata.explicit,
podcastType: DataTypes.STRING, autoDownloadEpisodes: !!oldPodcast.autoDownloadEpisodes,
explicit: DataTypes.BOOLEAN, autoDownloadSchedule: oldPodcast.autoDownloadSchedule,
lastEpisodeCheck: oldPodcast.lastEpisodeCheck,
maxEpisodesToKeep: oldPodcast.maxEpisodesToKeep,
maxNewEpisodesToDownload: oldPodcast.maxNewEpisodesToDownload,
coverPath: oldPodcast.coverPath,
tags: oldPodcast.tags,
genres: oldPodcastMetadata.genres
}
}
autoDownloadEpisodes: DataTypes.BOOLEAN, /**
autoDownloadSchedule: DataTypes.STRING, * Initialize model
lastEpisodeCheck: DataTypes.DATE, * @param {import('../Database').sequelize} sequelize
maxEpisodesToKeep: DataTypes.INTEGER, */
maxNewEpisodesToDownload: DataTypes.INTEGER, static init(sequelize) {
coverPath: DataTypes.STRING, super.init({
tags: DataTypes.JSON, id: {
genres: DataTypes.JSON type: DataTypes.UUID,
}, { defaultValue: DataTypes.UUIDV4,
sequelize, primaryKey: true
modelName: 'podcast' },
}) title: DataTypes.STRING,
titleIgnorePrefix: DataTypes.STRING,
author: DataTypes.STRING,
releaseDate: DataTypes.STRING,
feedURL: DataTypes.STRING,
imageURL: DataTypes.STRING,
description: DataTypes.TEXT,
itunesPageURL: DataTypes.STRING,
itunesId: DataTypes.STRING,
itunesArtistId: DataTypes.STRING,
language: DataTypes.STRING,
podcastType: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
return Podcast autoDownloadEpisodes: DataTypes.BOOLEAN,
} autoDownloadSchedule: DataTypes.STRING,
lastEpisodeCheck: DataTypes.DATE,
maxEpisodesToKeep: DataTypes.INTEGER,
maxNewEpisodesToDownload: DataTypes.INTEGER,
coverPath: DataTypes.STRING,
tags: DataTypes.JSON,
genres: DataTypes.JSON
}, {
sequelize,
modelName: 'podcast'
})
}
}
module.exports = Podcast

View File

@ -1,102 +1,149 @@
const { DataTypes, Model } = require('sequelize') const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => { class PodcastEpisode extends Model {
class PodcastEpisode extends Model { constructor(values, options) {
getOldPodcastEpisode(libraryItemId = null) { super(values, options)
let enclosure = null
if (this.enclosureURL) { /** @type {UUIDV4} */
enclosure = { this.id
url: this.enclosureURL, /** @type {number} */
type: this.enclosureType, this.index
length: this.enclosureSize !== null ? String(this.enclosureSize) : null /** @type {string} */
} this.season
} /** @type {string} */
return { this.episode
libraryItemId: libraryItemId || null, /** @type {string} */
podcastId: this.podcastId, this.episodeType
id: this.id, /** @type {string} */
oldEpisodeId: this.extraData?.oldEpisodeId || null, this.title
index: this.index, /** @type {string} */
season: this.season, this.subtitle
episode: this.episode, /** @type {string} */
episodeType: this.episodeType, this.description
title: this.title, /** @type {string} */
subtitle: this.subtitle, this.pubDate
description: this.description, /** @type {string} */
enclosure, this.enclosureURL
pubDate: this.pubDate, /** @type {BigInt} */
chapters: this.chapters, this.enclosureSize
audioFile: this.audioFile, /** @type {string} */
publishedAt: this.publishedAt?.valueOf() || null, this.enclosureType
addedAt: this.createdAt.valueOf(), /** @type {Date} */
updatedAt: this.updatedAt.valueOf() this.publishedAt
/** @type {Object} */
this.audioFile
/** @type {Object} */
this.chapters
/** @type {Object} */
this.extraData
/** @type {UUIDV4} */
this.podcastId
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
getOldPodcastEpisode(libraryItemId = null) {
let enclosure = null
if (this.enclosureURL) {
enclosure = {
url: this.enclosureURL,
type: this.enclosureType,
length: this.enclosureSize !== null ? String(this.enclosureSize) : null
} }
} }
return {
static createFromOld(oldEpisode) { libraryItemId: libraryItemId || null,
const podcastEpisode = this.getFromOld(oldEpisode) podcastId: this.podcastId,
return this.create(podcastEpisode) id: this.id,
} oldEpisodeId: this.extraData?.oldEpisodeId || null,
index: this.index,
static getFromOld(oldEpisode) { season: this.season,
const extraData = {} episode: this.episode,
if (oldEpisode.oldEpisodeId) { episodeType: this.episodeType,
extraData.oldEpisodeId = oldEpisode.oldEpisodeId title: this.title,
} subtitle: this.subtitle,
return { description: this.description,
id: oldEpisode.id, enclosure,
index: oldEpisode.index, pubDate: this.pubDate,
season: oldEpisode.season, chapters: this.chapters,
episode: oldEpisode.episode, audioFile: this.audioFile,
episodeType: oldEpisode.episodeType, publishedAt: this.publishedAt?.valueOf() || null,
title: oldEpisode.title, addedAt: this.createdAt.valueOf(),
subtitle: oldEpisode.subtitle, updatedAt: this.updatedAt.valueOf()
description: oldEpisode.description,
pubDate: oldEpisode.pubDate,
enclosureURL: oldEpisode.enclosure?.url || null,
enclosureSize: oldEpisode.enclosure?.length || null,
enclosureType: oldEpisode.enclosure?.type || null,
publishedAt: oldEpisode.publishedAt,
podcastId: oldEpisode.podcastId,
audioFile: oldEpisode.audioFile?.toJSON() || null,
chapters: oldEpisode.chapters,
extraData
}
} }
} }
PodcastEpisode.init({ static createFromOld(oldEpisode) {
id: { const podcastEpisode = this.getFromOld(oldEpisode)
type: DataTypes.UUID, return this.create(podcastEpisode)
defaultValue: DataTypes.UUIDV4, }
primaryKey: true
},
index: DataTypes.INTEGER,
season: DataTypes.STRING,
episode: DataTypes.STRING,
episodeType: DataTypes.STRING,
title: DataTypes.STRING,
subtitle: DataTypes.STRING(1000),
description: DataTypes.TEXT,
pubDate: DataTypes.STRING,
enclosureURL: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT,
enclosureType: DataTypes.STRING,
publishedAt: DataTypes.DATE,
audioFile: DataTypes.JSON, static getFromOld(oldEpisode) {
chapters: DataTypes.JSON, const extraData = {}
extraData: DataTypes.JSON if (oldEpisode.oldEpisodeId) {
}, { extraData.oldEpisodeId = oldEpisode.oldEpisodeId
sequelize, }
modelName: 'podcastEpisode' return {
}) id: oldEpisode.id,
index: oldEpisode.index,
season: oldEpisode.season,
episode: oldEpisode.episode,
episodeType: oldEpisode.episodeType,
title: oldEpisode.title,
subtitle: oldEpisode.subtitle,
description: oldEpisode.description,
pubDate: oldEpisode.pubDate,
enclosureURL: oldEpisode.enclosure?.url || null,
enclosureSize: oldEpisode.enclosure?.length || null,
enclosureType: oldEpisode.enclosure?.type || null,
publishedAt: oldEpisode.publishedAt,
podcastId: oldEpisode.podcastId,
audioFile: oldEpisode.audioFile?.toJSON() || null,
chapters: oldEpisode.chapters,
extraData
}
}
const { podcast } = sequelize.models /**
podcast.hasMany(PodcastEpisode, { * Initialize model
onDelete: 'CASCADE' * @param {import('../Database').sequelize} sequelize
}) */
PodcastEpisode.belongsTo(podcast) static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
index: DataTypes.INTEGER,
season: DataTypes.STRING,
episode: DataTypes.STRING,
episodeType: DataTypes.STRING,
title: DataTypes.STRING,
subtitle: DataTypes.STRING(1000),
description: DataTypes.TEXT,
pubDate: DataTypes.STRING,
enclosureURL: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT,
enclosureType: DataTypes.STRING,
publishedAt: DataTypes.DATE,
return PodcastEpisode audioFile: DataTypes.JSON,
} chapters: DataTypes.JSON,
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'podcastEpisode'
})
const { podcast } = sequelize.models
podcast.hasMany(PodcastEpisode, {
onDelete: 'CASCADE'
})
PodcastEpisode.belongsTo(podcast)
}
}
module.exports = PodcastEpisode

View File

@ -2,81 +2,104 @@ const { DataTypes, Model } = require('sequelize')
const oldSeries = require('../objects/entities/Series') const oldSeries = require('../objects/entities/Series')
module.exports = (sequelize) => { class Series extends Model {
class Series extends Model { constructor(values, options) {
static async getAllOldSeries() { super(values, options)
const series = await this.findAll()
return series.map(se => se.getOldSeries())
}
getOldSeries() { /** @type {UUIDV4} */
return new oldSeries({ this.id
id: this.id, /** @type {string} */
name: this.name, this.name
description: this.description, /** @type {string} */
libraryId: this.libraryId, this.nameIgnorePrefix
addedAt: this.createdAt.valueOf(), /** @type {string} */
updatedAt: this.updatedAt.valueOf() this.description
}) /** @type {UUIDV4} */
} this.libraryId
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
static updateFromOld(oldSeries) { static async getAllOldSeries() {
const series = this.getFromOld(oldSeries) const series = await this.findAll()
return this.update(series, { return series.map(se => se.getOldSeries())
where: { }
id: series.id
}
})
}
static createFromOld(oldSeries) { getOldSeries() {
const series = this.getFromOld(oldSeries) return new oldSeries({
return this.create(series) id: this.id,
} name: this.name,
description: this.description,
libraryId: this.libraryId,
addedAt: this.createdAt.valueOf(),
updatedAt: this.updatedAt.valueOf()
})
}
static createBulkFromOld(oldSeriesObjs) { static updateFromOld(oldSeries) {
const series = oldSeriesObjs.map(this.getFromOld) const series = this.getFromOld(oldSeries)
return this.bulkCreate(series) return this.update(series, {
} where: {
id: series.id
static getFromOld(oldSeries) {
return {
id: oldSeries.id,
name: oldSeries.name,
nameIgnorePrefix: oldSeries.nameIgnorePrefix,
description: oldSeries.description,
libraryId: oldSeries.libraryId
} }
} })
}
static removeById(seriesId) { static createFromOld(oldSeries) {
return this.destroy({ const series = this.getFromOld(oldSeries)
where: { return this.create(series)
id: seriesId }
}
}) static createBulkFromOld(oldSeriesObjs) {
const series = oldSeriesObjs.map(this.getFromOld)
return this.bulkCreate(series)
}
static getFromOld(oldSeries) {
return {
id: oldSeries.id,
name: oldSeries.name,
nameIgnorePrefix: oldSeries.nameIgnorePrefix,
description: oldSeries.description,
libraryId: oldSeries.libraryId
} }
} }
Series.init({ static removeById(seriesId) {
id: { return this.destroy({
type: DataTypes.UUID, where: {
defaultValue: DataTypes.UUIDV4, id: seriesId
primaryKey: true }
}, })
name: DataTypes.STRING, }
nameIgnorePrefix: DataTypes.STRING,
description: DataTypes.TEXT
}, {
sequelize,
modelName: 'series'
})
const { library } = sequelize.models /**
library.hasMany(Series, { * Initialize model
onDelete: 'CASCADE' * @param {import('../Database').sequelize} sequelize
}) */
Series.belongsTo(library) static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
nameIgnorePrefix: DataTypes.STRING,
description: DataTypes.TEXT
}, {
sequelize,
modelName: 'series'
})
return Series const { library } = sequelize.models
} library.hasMany(Series, {
onDelete: 'CASCADE'
})
Series.belongsTo(library)
}
}
module.exports = Series

View File

@ -4,42 +4,59 @@ const oldEmailSettings = require('../objects/settings/EmailSettings')
const oldServerSettings = require('../objects/settings/ServerSettings') const oldServerSettings = require('../objects/settings/ServerSettings')
const oldNotificationSettings = require('../objects/settings/NotificationSettings') const oldNotificationSettings = require('../objects/settings/NotificationSettings')
module.exports = (sequelize) => { class Setting extends Model {
class Setting extends Model { constructor(values, options) {
static async getOldSettings() { super(values, options)
const settings = (await this.findAll()).map(se => se.value)
/** @type {string} */
this.key
/** @type {Object} */
this.value
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
static async getOldSettings() {
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,
emailSettings: new oldEmailSettings(emailSettingsJson), emailSettings: new oldEmailSettings(emailSettingsJson),
serverSettings: new oldServerSettings(serverSettingsJson), serverSettings: new oldServerSettings(serverSettingsJson),
notificationSettings: new oldNotificationSettings(notificationSettingsJson) notificationSettings: new oldNotificationSettings(notificationSettingsJson)
}
}
static updateSettingObj(setting) {
return this.upsert({
key: setting.id,
value: setting
})
} }
} }
Setting.init({ static updateSettingObj(setting) {
key: { return this.upsert({
type: DataTypes.STRING, key: setting.id,
primaryKey: true value: setting
}, })
value: DataTypes.JSON }
}, {
sequelize,
modelName: 'setting'
})
return Setting /**
} * Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
key: {
type: DataTypes.STRING,
primaryKey: true
},
value: DataTypes.JSON
}, {
sequelize,
modelName: 'setting'
})
}
}
module.exports = Setting

View File

@ -3,238 +3,273 @@ const { DataTypes, Model, Op } = require('sequelize')
const Logger = require('../Logger') const Logger = require('../Logger')
const oldUser = require('../objects/user/User') const oldUser = require('../objects/user/User')
module.exports = (sequelize) => { class User extends Model {
class User extends Model { constructor(values, options) {
/** super(values, options)
* Get all oldUsers
* @returns {Promise<oldUser>}
*/
static async getOldUsers() {
const users = await this.findAll({
include: sequelize.models.mediaProgress
})
return users.map(u => this.getOldUser(u))
}
static getOldUser(userExpanded) { /** @type {UUIDV4} */
const mediaProgress = userExpanded.mediaProgresses.map(mp => mp.getOldMediaProgress()) this.id
/** @type {string} */
this.username
/** @type {string} */
this.email
/** @type {string} */
this.pash
/** @type {string} */
this.type
/** @type {boolean} */
this.isActive
/** @type {boolean} */
this.isLocked
/** @type {Date} */
this.lastSeen
/** @type {Object} */
this.permissions
/** @type {Object} */
this.bookmarks
/** @type {Object} */
this.extraData
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
}
const librariesAccessible = userExpanded.permissions?.librariesAccessible || [] /**
const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || [] * Get all oldUsers
const permissions = userExpanded.permissions || {} * @returns {Promise<oldUser>}
delete permissions.librariesAccessible */
delete permissions.itemTagsSelected static async getOldUsers() {
const users = await this.findAll({
include: this.sequelize.models.mediaProgress
})
return users.map(u => this.getOldUser(u))
}
return new oldUser({ static getOldUser(userExpanded) {
id: userExpanded.id, const mediaProgress = userExpanded.mediaProgresses.map(mp => mp.getOldMediaProgress())
oldUserId: userExpanded.extraData?.oldUserId || null,
username: userExpanded.username,
pash: userExpanded.pash,
type: userExpanded.type,
token: userExpanded.token,
mediaProgress,
seriesHideFromContinueListening: userExpanded.extraData?.seriesHideFromContinueListening || [],
bookmarks: userExpanded.bookmarks,
isActive: userExpanded.isActive,
isLocked: userExpanded.isLocked,
lastSeen: userExpanded.lastSeen?.valueOf() || null,
createdAt: userExpanded.createdAt.valueOf(),
permissions,
librariesAccessible,
itemTagsSelected
})
}
static createFromOld(oldUser) { const librariesAccessible = userExpanded.permissions?.librariesAccessible || []
const user = this.getFromOld(oldUser) const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || []
return this.create(user) const permissions = userExpanded.permissions || {}
} delete permissions.librariesAccessible
delete permissions.itemTagsSelected
static updateFromOld(oldUser) { return new oldUser({
const user = this.getFromOld(oldUser) id: userExpanded.id,
return this.update(user, { oldUserId: userExpanded.extraData?.oldUserId || null,
where: { username: userExpanded.username,
id: user.id pash: userExpanded.pash,
} type: userExpanded.type,
}).then((result) => result[0] > 0).catch((error) => { token: userExpanded.token,
Logger.error(`[User] Failed to save user ${oldUser.id}`, error) mediaProgress,
return false seriesHideFromContinueListening: userExpanded.extraData?.seriesHideFromContinueListening || [],
}) bookmarks: userExpanded.bookmarks,
} isActive: userExpanded.isActive,
isLocked: userExpanded.isLocked,
lastSeen: userExpanded.lastSeen?.valueOf() || null,
createdAt: userExpanded.createdAt.valueOf(),
permissions,
librariesAccessible,
itemTagsSelected
})
}
static getFromOld(oldUser) { static createFromOld(oldUser) {
return { const user = this.getFromOld(oldUser)
id: oldUser.id, return this.create(user)
username: oldUser.username, }
pash: oldUser.pash || null,
type: oldUser.type || null, static updateFromOld(oldUser) {
token: oldUser.token || null, const user = this.getFromOld(oldUser)
isActive: !!oldUser.isActive, return this.update(user, {
lastSeen: oldUser.lastSeen || null, where: {
extraData: { id: user.id
seriesHideFromContinueListening: oldUser.seriesHideFromContinueListening || [],
oldUserId: oldUser.oldUserId
},
createdAt: oldUser.createdAt || Date.now(),
permissions: {
...oldUser.permissions,
librariesAccessible: oldUser.librariesAccessible || [],
itemTagsSelected: oldUser.itemTagsSelected || []
},
bookmarks: oldUser.bookmarks
} }
} }).then((result) => result[0] > 0).catch((error) => {
Logger.error(`[User] Failed to save user ${oldUser.id}`, error)
return false
})
}
static removeById(userId) { static getFromOld(oldUser) {
return this.destroy({ return {
where: { id: oldUser.id,
id: userId username: oldUser.username,
} pash: oldUser.pash || null,
}) type: oldUser.type || null,
} token: oldUser.token || null,
isActive: !!oldUser.isActive,
/** lastSeen: oldUser.lastSeen || null,
* Create root user extraData: {
* @param {string} username seriesHideFromContinueListening: oldUser.seriesHideFromContinueListening || [],
* @param {string} pash oldUserId: oldUser.oldUserId
* @param {Auth} auth },
* @returns {oldUser} createdAt: oldUser.createdAt || Date.now(),
*/ permissions: {
static async createRootUser(username, pash, auth) { ...oldUser.permissions,
const userId = uuidv4() librariesAccessible: oldUser.librariesAccessible || [],
itemTagsSelected: oldUser.itemTagsSelected || []
const token = await auth.generateAccessToken({ userId, username }) },
bookmarks: oldUser.bookmarks
const newRoot = new oldUser({
id: userId,
type: 'root',
username,
pash,
token,
isActive: true,
createdAt: Date.now()
})
await this.createFromOld(newRoot)
return newRoot
}
/**
* 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
* @param {string} userId
* @returns {Promise<oldUser|null>} null if not found
*/
static async getUserByIdOrOldId(userId) {
if (!userId) return null
const user = await this.findOne({
where: {
[Op.or]: [
{
id: userId
},
{
extraData: {
[Op.substring]: userId
}
}
]
},
include: sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get user by username case insensitive
* @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found
*/
static async getUserByUsername(username) {
if (!username) return null
const user = await this.findOne({
where: {
username: {
[Op.like]: username
}
},
include: sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get user by id
* @param {string} userId
* @returns {Promise<oldUser|null>} returns null if not found
*/
static async getUserById(userId) {
if (!userId) return null
const user = await this.findByPk(userId, {
include: sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get array of user id and username
* @returns {object[]} { id, username }
*/
static async getMinifiedUserObjects() {
const users = await this.findAll({
attributes: ['id', 'username']
})
return users.map(u => {
return {
id: u.id,
username: u.username
}
})
}
/**
* Return true if root user exists
* @returns {boolean}
*/
static async getHasRootUser() {
const count = await this.count({
where: {
type: 'root'
}
})
return count > 0
} }
} }
User.init({ static removeById(userId) {
id: { return this.destroy({
type: DataTypes.UUID, where: {
defaultValue: DataTypes.UUIDV4, id: userId
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
}, {
sequelize,
modelName: 'user'
})
return User /**
} * Create root user
* @param {string} username
* @param {string} pash
* @param {Auth} auth
* @returns {oldUser}
*/
static async createRootUser(username, pash, auth) {
const userId = uuidv4()
const token = await auth.generateAccessToken({ userId, username })
const newRoot = new oldUser({
id: userId,
type: 'root',
username,
pash,
token,
isActive: true,
createdAt: Date.now()
})
await this.createFromOld(newRoot)
return newRoot
}
/**
* 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
* @param {string} userId
* @returns {Promise<oldUser|null>} null if not found
*/
static async getUserByIdOrOldId(userId) {
if (!userId) return null
const user = await this.findOne({
where: {
[Op.or]: [
{
id: userId
},
{
extraData: {
[Op.substring]: userId
}
}
]
},
include: this.sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get user by username case insensitive
* @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found
*/
static async getUserByUsername(username) {
if (!username) return null
const user = await this.findOne({
where: {
username: {
[Op.like]: username
}
},
include: this.sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get user by id
* @param {string} userId
* @returns {Promise<oldUser|null>} returns null if not found
*/
static async getUserById(userId) {
if (!userId) return null
const user = await this.findByPk(userId, {
include: this.sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get array of user id and username
* @returns {object[]} { id, username }
*/
static async getMinifiedUserObjects() {
const users = await this.findAll({
attributes: ['id', 'username']
})
return users.map(u => {
return {
id: u.id,
username: u.username
}
})
}
/**
* Return true if root user exists
* @returns {boolean}
*/
static async getHasRootUser() {
const count = await this.count({
where: {
type: 'root'
}
})
return count > 0
}
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
*/
static init(sequelize) {
super.init({
id: {
type: DataTypes.UUID,
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
}, {
sequelize,
modelName: 'user'
})
}
}
module.exports = User