Update model casing & associations

This commit is contained in:
advplyr 2023-03-19 15:19:22 -05:00
parent 2131a65299
commit 54ca58e610
45 changed files with 830 additions and 561 deletions

View File

@ -12,12 +12,12 @@ class Database {
return this.sequelize?.models || {} return this.sequelize?.models || {}
} }
async init() { async init(force = false) {
if (!await this.connect()) { if (!await this.connect()) {
throw new Error('Database connection failed') throw new Error('Database connection failed')
} }
await this.buildModels() await this.buildModels(force)
Logger.info(`[Database] Db initialized`, Object.keys(this.sequelize.models)) Logger.info(`[Database] Db initialized`, Object.keys(this.sequelize.models))
} }
@ -30,6 +30,9 @@ class Database {
logging: false logging: false
}) })
// Helper function
this.sequelize.uppercaseFirst = str => str ? `${str[0].toUpperCase()}${str.substr(1)}` : ''
try { try {
await this.sequelize.authenticate() await this.sequelize.authenticate()
Logger.info(`[Database] Db connection was successful`) Logger.info(`[Database] Db connection was successful`)
@ -40,15 +43,15 @@ class Database {
} }
} }
buildModels() { buildModels(force = false) {
require('./models/User')(this.sequelize) require('./models/User')(this.sequelize)
require('./models/FileMetadata')(this.sequelize) require('./models/FileMetadata')(this.sequelize)
require('./models/Library')(this.sequelize)
require('./models/LibraryFolder')(this.sequelize)
require('./models/LibraryItem')(this.sequelize)
require('./models/EBookFile')(this.sequelize) require('./models/EBookFile')(this.sequelize)
require('./models/Book')(this.sequelize) require('./models/Book')(this.sequelize)
require('./models/Podcast')(this.sequelize) require('./models/Podcast')(this.sequelize)
require('./models/Library')(this.sequelize)
require('./models/LibraryFolder')(this.sequelize)
require('./models/LibraryItem')(this.sequelize)
require('./models/PodcastEpisode')(this.sequelize) require('./models/PodcastEpisode')(this.sequelize)
require('./models/MediaProgress')(this.sequelize) require('./models/MediaProgress')(this.sequelize)
require('./models/LibraryFile')(this.sequelize) require('./models/LibraryFile')(this.sequelize)
@ -82,24 +85,7 @@ class Database {
require('./models/Notification')(this.sequelize) require('./models/Notification')(this.sequelize)
require('./models/UserPermission')(this.sequelize) require('./models/UserPermission')(this.sequelize)
return this.sequelize.sync({ force: true }) return this.sequelize.sync({ force })
}
async createTestUser() {
const User = this.sequelize.models.User
let user = await User.findOne({
where: {
username: 'Tester'
}
})
if (user) {
Logger.info(`[Database] Tester user was found`, user.toJSON())
} else {
user = await User.create({ username: 'Tester' })
Logger.info(`[Database] Created Tester user`, user.toJSON())
}
} }
} }

View File

@ -22,6 +22,7 @@ const Database = require('./Database')
const SocketAuthority = require('./SocketAuthority') const SocketAuthority = require('./SocketAuthority')
const ApiRouter = require('./routers/ApiRouter') const ApiRouter = require('./routers/ApiRouter')
const ApiRouter2 = require('./routers/ApiRouter2')
const HlsRouter = require('./routers/HlsRouter') const HlsRouter = require('./routers/HlsRouter')
const StaticRouter = require('./routers/StaticRouter') const StaticRouter = require('./routers/StaticRouter')
@ -84,6 +85,7 @@ class Server {
// Routers // Routers
this.apiRouter = new ApiRouter(this) this.apiRouter = new ApiRouter(this)
this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager) this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager)
this.staticRouter = new StaticRouter(this.db) this.staticRouter = new StaticRouter(this.db)
@ -102,9 +104,9 @@ class Server {
await this.playbackSessionManager.removeOrphanStreams() await this.playbackSessionManager.removeOrphanStreams()
// TODO: Test new db connection // TODO: Test new db connection
await Database.init() const force = true
// await Database.createTestUser() await Database.init(force)
await dbMigration3.migrate() if (force) await dbMigration3.migrate()
const previousVersion = await this.db.checkPreviousVersion() // Returns null if same server version const previousVersion = await this.db.checkPreviousVersion() // Returns null if same server version
if (previousVersion) { if (previousVersion) {
@ -169,6 +171,7 @@ class Server {
// Static folder // Static folder
router.use(express.static(Path.join(global.appRoot, 'static'))) router.use(express.static(Path.join(global.appRoot, 'static')))
router.use('/api/v1', new ApiRouter2(this).router)
router.use('/api', this.authMiddleware.bind(this), this.apiRouter.router) router.use('/api', this.authMiddleware.bind(this), this.apiRouter.router)
router.use('/hls', this.authMiddleware.bind(this), this.hlsRouter.router) router.use('/hls', this.authMiddleware.bind(this), this.hlsRouter.router)
router.use('/s', this.authMiddleware.bind(this), this.staticRouter.router) router.use('/s', this.authMiddleware.bind(this), this.staticRouter.router)

View File

@ -0,0 +1,192 @@
const Database = require('../Database')
class LibraryItemController {
constructor() { }
// Example get library item fully expanded or minified
async get(req, res) {
const key = req.query.minified == 1 ? 'minified' : 'full'
const include = {
minified: [
{
model: Database.models.book,
include: [
{
model: Database.models.audioTrack
},
{
model: Database.models.genre,
through: {
attributes: []
}
},
{
model: Database.models.tag,
through: {
attributes: []
}
},
{
model: Database.models.person,
as: 'authors',
through: {
attributes: []
}
},
{
model: Database.models.person,
as: 'narrators',
through: {
attributes: []
}
},
{
model: Database.models.series,
through: {
attributes: ['sequence']
}
},
{
model: Database.models.bookChapter
},
{
model: Database.models.eBookFile,
include: 'fileMetadata'
}
]
},
{
model: Database.models.podcast,
include: [
{
model: Database.models.podcastEpisode,
include: {
model: Database.models.audioTrack
}
},
{
model: Database.models.genre,
through: {
attributes: []
}
},
{
model: Database.models.tag,
through: {
attributes: []
}
},
]
}
],
full: [
{
model: Database.models.book,
include: [
{
model: Database.models.fileMetadata,
as: 'imageFile'
},
{
model: Database.models.audioTrack,
include: {
model: Database.models.mediaFile,
include: [
'fileMetadata',
'mediaStreams'
]
}
},
{
model: Database.models.genre,
through: {
attributes: []
}
},
{
model: Database.models.tag,
through: {
attributes: []
}
},
{
model: Database.models.person,
as: 'authors',
through: {
attributes: []
}
},
{
model: Database.models.person,
as: 'narrators',
through: {
attributes: []
}
},
{
model: Database.models.series,
through: {
attributes: ['sequence']
}
},
{
model: Database.models.bookChapter
},
{
model: Database.models.eBookFile,
include: 'fileMetadata'
}
]
},
{
model: Database.models.podcast,
include: [
{
model: Database.models.fileMetadata,
as: 'imageFile'
},
{
model: Database.models.podcastEpisode,
include: {
model: Database.models.audioTrack,
include: {
model: Database.models.mediaFile,
include: [
'fileMetadata',
'mediaStreams'
]
}
}
},
{
model: Database.models.genre,
through: {
attributes: []
}
},
{
model: Database.models.tag,
through: {
attributes: []
}
},
]
},
{
model: Database.models.libraryFile,
include: 'fileMetadata'
},
{
model: Database.models.libraryFolder,
include: 'library'
}
]
}
const LibraryItem = await Database.models.libraryItem.findByPk(req.params.id, {
include: include[key]
})
res.json(LibraryItem)
}
}
module.exports = new LibraryItemController()

View File

@ -8,7 +8,7 @@ module.exports = (sequelize) => {
class AudioBookmark extends Model { class AudioBookmark extends Model {
getMediaItem(options) { getMediaItem(options) {
if (!this.mediaItemType) return Promise.resolve(null) if (!this.mediaItemType) return Promise.resolve(null)
const mixinMethodName = `get${this.mediaItemType}` const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
return this[mixinMethodName](options) return this[mixinMethodName](options)
} }
} }
@ -19,52 +19,57 @@ module.exports = (sequelize) => {
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true primaryKey: true
}, },
MediaItemId: DataTypes.UUIDV4, mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING, mediaItemType: DataTypes.STRING,
title: DataTypes.STRING, title: DataTypes.STRING,
time: DataTypes.INTEGER time: DataTypes.INTEGER
}, { }, {
sequelize, sequelize,
modelName: 'AudioBookmark' modelName: 'audioBookmark'
}) })
const { User, Book, PodcastEpisode } = sequelize.models const { user, book, podcastEpisode } = sequelize.models
Book.hasMany(AudioBookmark, { book.hasMany(AudioBookmark, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'Book' mediaItemType: 'book'
} }
}) })
AudioBookmark.belongsTo(Book, { foreignKey: 'MediaItemId', constraints: false }) AudioBookmark.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
PodcastEpisode.hasMany(AudioBookmark, { podcastEpisode.hasMany(AudioBookmark, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'PodcastEpisode' mediaItemType: 'podcastEpisode'
} }
}) })
AudioBookmark.belongsTo(PodcastEpisode, { foreignKey: 'MediaItemId', constraints: false }) AudioBookmark.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
AudioBookmark.addHook('afterFind', findResult => { AudioBookmark.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) { for (const instance of findResult) {
if (instance.mediaItemType === 'Book' && instance.Book !== undefined) { if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.MediaItem = instance.Book instance.mediaItem = instance.book
} else if (instance.mediaItemType === 'PodcastEpisode' && instance.PodcastEpisode !== undefined) { instance.dataValues.mediaItem = instance.dataValues.book
instance.MediaItem = instance.PodcastEpisode } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: // To prevent mistakes:
delete instance.Book delete instance.book
delete instance.dataValues.Book delete instance.dataValues.book
delete instance.PodcastEpisode delete instance.podcastEpisode
delete instance.dataValues.PodcastEpisode delete instance.dataValues.podcastEpisode
} }
}) })
User.hasMany(AudioBookmark) user.hasMany(AudioBookmark)
AudioBookmark.belongsTo(User) AudioBookmark.belongsTo(user)
return AudioBookmark return AudioBookmark
} }

View File

@ -8,7 +8,7 @@ module.exports = (sequelize) => {
class AudioTrack extends Model { class AudioTrack extends Model {
getMediaItem(options) { getMediaItem(options) {
if (!this.mediaItemType) return Promise.resolve(null) if (!this.mediaItemType) return Promise.resolve(null)
const mixinMethodName = `get${this.mediaItemType}` const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
return this[mixinMethodName](options) return this[mixinMethodName](options)
} }
} }
@ -19,7 +19,7 @@ module.exports = (sequelize) => {
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true primaryKey: true
}, },
MediaItemId: DataTypes.UUIDV4, mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING, mediaItemType: DataTypes.STRING,
index: DataTypes.INTEGER, index: DataTypes.INTEGER,
startOffset: DataTypes.FLOAT, startOffset: DataTypes.FLOAT,
@ -31,45 +31,50 @@ module.exports = (sequelize) => {
discNumber: DataTypes.INTEGER discNumber: DataTypes.INTEGER
}, { }, {
sequelize, sequelize,
modelName: 'AudioTrack' modelName: 'audioTrack'
}) })
const { Book, PodcastEpisode, MediaFile } = sequelize.models const { book, podcastEpisode, mediaFile } = sequelize.models
MediaFile.hasOne(AudioTrack) mediaFile.hasOne(AudioTrack)
AudioTrack.belongsTo(MediaFile) AudioTrack.belongsTo(mediaFile)
Book.hasMany(AudioTrack, { book.hasMany(AudioTrack, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'Book' mediaItemType: 'book'
} }
}) })
AudioTrack.belongsTo(Book, { foreignKey: 'MediaItemId', constraints: false }) AudioTrack.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
PodcastEpisode.hasOne(AudioTrack, { podcastEpisode.hasOne(AudioTrack, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'PodcastEpisode' mediaItemType: 'podcastEpisode'
} }
}) })
AudioTrack.belongsTo(PodcastEpisode, { foreignKey: 'MediaItemId', constraints: false }) AudioTrack.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
AudioTrack.addHook('afterFind', findResult => { AudioTrack.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) { for (const instance of findResult) {
if (instance.mediaItemType === 'Book' && instance.Book !== undefined) { if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.MediaItem = instance.Book instance.mediaItem = instance.book
} else if (instance.mediaItemType === 'PodcastEpisode' && instance.PodcastEpisode !== undefined) { instance.dataValues.mediaItem = instance.dataValues.book
instance.MediaItem = instance.PodcastEpisode } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: // To prevent mistakes:
delete instance.Book delete instance.book
delete instance.dataValues.Book delete instance.dataValues.book
delete instance.PodcastEpisode delete instance.podcastEpisode
delete instance.dataValues.PodcastEpisode delete instance.dataValues.podcastEpisode
} }
}) })

View File

@ -23,18 +23,16 @@ module.exports = (sequelize) => {
lastCoverSearch: DataTypes.DATE lastCoverSearch: DataTypes.DATE
}, { }, {
sequelize, sequelize,
modelName: 'Book' modelName: 'book'
}) })
const { LibraryItem, FileMetadata, EBookFile } = sequelize.models const { fileMetadata, eBookFile } = sequelize.models
LibraryItem.hasOne(Book)
Book.belongsTo(LibraryItem)
FileMetadata.hasOne(Book, { foreignKey: 'ImageFileId ' }) fileMetadata.hasOne(Book, { foreignKey: 'imageFileId' })
Book.belongsTo(FileMetadata, { as: 'ImageFile', foreignKey: 'ImageFileId' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias Book.belongsTo(fileMetadata, { as: 'imageFile', foreignKey: 'imageFileId' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias
EBookFile.hasOne(Book) eBookFile.hasOne(Book)
Book.belongsTo(EBookFile) Book.belongsTo(eBookFile)
return Book return Book
} }

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
} }
}, { }, {
sequelize, sequelize,
modelName: 'BookAuthor', modelName: 'bookAuthor',
timestamps: false timestamps: false
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Book, Person } = sequelize.models const { book, person } = sequelize.models
Book.belongsToMany(Person, { through: BookAuthor }) book.belongsToMany(person, { through: BookAuthor, as: 'authors', otherKey: 'authorId' })
Person.belongsToMany(Book, { through: BookAuthor }) person.belongsToMany(book, { through: BookAuthor, foreignKey: 'authorId' })
Book.hasMany(BookAuthor) book.hasMany(BookAuthor)
BookAuthor.belongsTo(Book) BookAuthor.belongsTo(book)
Person.hasMany(BookAuthor) person.hasMany(BookAuthor, { foreignKey: 'authorId' })
BookAuthor.belongsTo(Person) BookAuthor.belongsTo(person, { as: 'author', foreignKey: 'authorId' })
return BookAuthor return BookAuthor
} }

View File

@ -15,13 +15,13 @@ module.exports = (sequelize) => {
end: DataTypes.FLOAT end: DataTypes.FLOAT
}, { }, {
sequelize, sequelize,
modelName: 'BookChapter' modelName: 'bookChapter'
}) })
const { Book } = sequelize.models const { book } = sequelize.models
Book.hasMany(BookChapter) book.hasMany(BookChapter)
BookChapter.belongsTo(Book) BookChapter.belongsTo(book)
return BookChapter return BookChapter
} }

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
} }
}, { }, {
sequelize, sequelize,
modelName: 'BookGenre', modelName: 'bookGenre',
timestamps: false timestamps: false
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Book, Genre } = sequelize.models const { book, genre } = sequelize.models
Book.belongsToMany(Genre, { through: BookGenre }) book.belongsToMany(genre, { through: BookGenre })
Genre.belongsToMany(Book, { through: BookGenre }) genre.belongsToMany(book, { through: BookGenre })
Book.hasMany(BookGenre) book.hasMany(BookGenre)
BookGenre.belongsTo(Book) BookGenre.belongsTo(book)
Genre.hasMany(BookGenre) genre.hasMany(BookGenre)
BookGenre.belongsTo(Genre) BookGenre.belongsTo(genre)
return BookGenre return BookGenre
} }

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
} }
}, { }, {
sequelize, sequelize,
modelName: 'BookNarrator', modelName: 'bookNarrator',
timestamps: false timestamps: false
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Book, Person } = sequelize.models const { book, person } = sequelize.models
Book.belongsToMany(Person, { through: BookNarrator }) book.belongsToMany(person, { through: BookNarrator, as: 'narrators', otherKey: 'narratorId' })
Person.belongsToMany(Book, { through: BookNarrator }) person.belongsToMany(book, { through: BookNarrator, foreignKey: 'narratorId' })
Book.hasMany(BookNarrator) book.hasMany(BookNarrator)
BookNarrator.belongsTo(Book) BookNarrator.belongsTo(book)
Person.hasMany(BookNarrator) person.hasMany(BookNarrator, { foreignKey: 'narratorId' })
BookNarrator.belongsTo(Person) BookNarrator.belongsTo(person, { as: 'narrator', foreignKey: 'narratorId' })
return BookNarrator return BookNarrator
} }

View File

@ -12,21 +12,21 @@ module.exports = (sequelize) => {
sequence: DataTypes.STRING sequence: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'BookSeries', modelName: 'bookSeries',
timestamps: false timestamps: false
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Book, Series } = sequelize.models const { book, series } = sequelize.models
Book.belongsToMany(Series, { through: BookSeries }) book.belongsToMany(series, { through: BookSeries })
Series.belongsToMany(Book, { through: BookSeries }) series.belongsToMany(book, { through: BookSeries })
Book.hasMany(BookSeries) book.hasMany(BookSeries)
BookSeries.belongsTo(Book) BookSeries.belongsTo(book)
Series.hasMany(BookSeries) series.hasMany(BookSeries)
BookSeries.belongsTo(Series) BookSeries.belongsTo(series)
return BookSeries return BookSeries
} }

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
} }
}, { }, {
sequelize, sequelize,
modelName: 'BookTag', modelName: 'bookTag',
timestamps: false timestamps: false
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Book, Tag } = sequelize.models const { book, tag } = sequelize.models
Book.belongsToMany(Tag, { through: BookTag }) book.belongsToMany(tag, { through: BookTag })
Tag.belongsToMany(Book, { through: BookTag }) tag.belongsToMany(book, { through: BookTag })
Book.hasMany(BookTag) book.hasMany(BookTag)
BookTag.belongsTo(Book) BookTag.belongsTo(book)
Tag.hasMany(BookTag) tag.hasMany(BookTag)
BookTag.belongsTo(Tag) BookTag.belongsTo(tag)
return BookTag return BookTag
} }

View File

@ -13,13 +13,13 @@ module.exports = (sequelize) => {
description: DataTypes.TEXT description: DataTypes.TEXT
}, { }, {
sequelize, sequelize,
modelName: 'Collection' modelName: 'collection'
}) })
const { Library } = sequelize.models const { library } = sequelize.models
Library.hasMany(Collection) library.hasMany(Collection)
Collection.belongsTo(Library) Collection.belongsTo(library)
return Collection return Collection
} }

View File

@ -13,20 +13,20 @@ module.exports = (sequelize) => {
sequelize, sequelize,
timestamps: true, timestamps: true,
updatedAt: false, updatedAt: false,
modelName: 'CollectionBook' modelName: 'collectionBook'
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Book, Collection } = sequelize.models const { book, collection } = sequelize.models
Book.belongsToMany(Collection, { through: CollectionBook }) book.belongsToMany(collection, { through: CollectionBook })
Collection.belongsToMany(Book, { through: CollectionBook }) collection.belongsToMany(book, { through: CollectionBook })
Book.hasMany(CollectionBook) book.hasMany(CollectionBook)
CollectionBook.belongsTo(Book) CollectionBook.belongsTo(book)
Collection.hasMany(CollectionBook) collection.hasMany(CollectionBook)
CollectionBook.belongsTo(Collection) CollectionBook.belongsTo(collection)
return CollectionBook return CollectionBook
} }

View File

@ -17,13 +17,13 @@ module.exports = (sequelize) => {
deviceVersion: DataTypes.STRING // e.g. Browser version or Android SDK deviceVersion: DataTypes.STRING // e.g. Browser version or Android SDK
}, { }, {
sequelize, sequelize,
modelName: 'Device' modelName: 'device'
}) })
const { User } = sequelize.models const { user } = sequelize.models
User.hasMany(Device) user.hasMany(Device)
Device.belongsTo(User) Device.belongsTo(user)
return Device return Device
} }

View File

@ -8,16 +8,17 @@ module.exports = (sequelize) => {
type: DataTypes.UUID, type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true primaryKey: true
} },
format: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'EBookFile' modelName: 'eBookFile'
}) })
const { FileMetadata } = sequelize.models const { fileMetadata } = sequelize.models
FileMetadata.hasOne(EBookFile, { foreignKey: 'FileMetadataId' }) fileMetadata.hasOne(EBookFile, { foreignKey: 'fileMetadataId' })
EBookFile.belongsTo(FileMetadata, { as: 'FileMetadata', foreignKey: 'FileMetadataId' }) EBookFile.belongsTo(fileMetadata, { as: 'fileMetadata', foreignKey: 'fileMetadataId' })
return EBookFile return EBookFile
} }

View File

@ -8,7 +8,7 @@ module.exports = (sequelize) => {
class Feed extends Model { class Feed extends Model {
getEntity(options) { getEntity(options) {
if (!this.entityType) return Promise.resolve(null) if (!this.entityType) return Promise.resolve(null)
const mixinMethodName = `get${this.entityType}` const mixinMethodName = `get${sequelize.uppercaseFirst(this.entityType)}`
return this[mixinMethodName](options) return this[mixinMethodName](options)
} }
} }
@ -21,7 +21,7 @@ module.exports = (sequelize) => {
}, },
slug: DataTypes.STRING, slug: DataTypes.STRING,
entityType: DataTypes.STRING, entityType: DataTypes.STRING,
EntityId: DataTypes.UUIDV4, entityId: DataTypes.UUIDV4,
entityUpdatedAt: DataTypes.DATE, entityUpdatedAt: DataTypes.DATE,
serverAddress: DataTypes.STRING, serverAddress: DataTypes.STRING,
feedURL: DataTypes.STRING, feedURL: DataTypes.STRING,
@ -38,72 +38,78 @@ module.exports = (sequelize) => {
preventIndexing: DataTypes.BOOLEAN preventIndexing: DataTypes.BOOLEAN
}, { }, {
sequelize, sequelize,
modelName: 'Feed' modelName: 'feed'
}) })
const { User, LibraryItem, Collection, Series, Playlist } = sequelize.models const { user, libraryItem, collection, series, playlist } = sequelize.models
User.hasMany(Feed) user.hasMany(Feed)
Feed.belongsTo(User) Feed.belongsTo(user)
LibraryItem.hasMany(Feed, { libraryItem.hasMany(Feed, {
foreignKey: 'EntityId', foreignKey: 'entityId',
constraints: false, constraints: false,
scope: { scope: {
entityType: 'LibraryItem' entityType: 'libraryItem'
} }
}) })
Feed.belongsTo(LibraryItem, { foreignKey: 'EntityId', constraints: false }) Feed.belongsTo(libraryItem, { foreignKey: 'entityId', constraints: false })
Collection.hasMany(Feed, { collection.hasMany(Feed, {
foreignKey: 'EntityId', foreignKey: 'entityId',
constraints: false, constraints: false,
scope: { scope: {
entityType: 'Collection' entityType: 'collection'
} }
}) })
Feed.belongsTo(Collection, { foreignKey: 'EntityId', constraints: false }) Feed.belongsTo(collection, { foreignKey: 'entityId', constraints: false })
Series.hasMany(Feed, { series.hasMany(Feed, {
foreignKey: 'EntityId', foreignKey: 'entityId',
constraints: false, constraints: false,
scope: { scope: {
entityType: 'Series' entityType: 'series'
} }
}) })
Feed.belongsTo(Series, { foreignKey: 'EntityId', constraints: false }) Feed.belongsTo(series, { foreignKey: 'entityId', constraints: false })
Playlist.hasMany(Feed, { playlist.hasMany(Feed, {
foreignKey: 'EntityId', foreignKey: 'entityId',
constraints: false, constraints: false,
scope: { scope: {
entityType: 'Playlist' entityType: 'playlist'
} }
}) })
Feed.belongsTo(Playlist, { foreignKey: 'EntityId', constraints: false }) Feed.belongsTo(playlist, { foreignKey: 'entityId', constraints: false })
Feed.addHook('afterFind', findResult => { Feed.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) { for (const instance of findResult) {
if (instance.entityType === 'LibraryItem' && instance.LibraryItem !== undefined) { if (instance.entityType === 'libraryItem' && instance.libraryItem !== undefined) {
instance.Entity = instance.LibraryItem instance.entity = instance.libraryItem
} else if (instance.mediaItemType === 'Collection' && instance.Collection !== undefined) { instance.dataValues.entity = instance.dataValues.libraryItem
instance.Entity = instance.Collection } else if (instance.entityType === 'collection' && instance.collection !== undefined) {
} else if (instance.mediaItemType === 'Series' && instance.Series !== undefined) { instance.entity = instance.collection
instance.Entity = instance.Series instance.dataValues.entity = instance.dataValues.collection
} else if (instance.mediaItemType === 'Playlist' && instance.Playlist !== undefined) { } else if (instance.entityType === 'series' && instance.series !== undefined) {
instance.Entity = instance.Playlist instance.entity = instance.series
instance.dataValues.entity = instance.dataValues.series
} else if (instance.entityType === 'playlist' && instance.playlist !== undefined) {
instance.entity = instance.playlist
instance.dataValues.entity = instance.dataValues.playlist
} }
// To prevent mistakes: // To prevent mistakes:
delete instance.LibraryItem delete instance.libraryItem
delete instance.dataValues.LibraryItem delete instance.dataValues.libraryItem
delete instance.Collection delete instance.collection
delete instance.dataValues.Collection delete instance.dataValues.collection
delete instance.Series delete instance.series
delete instance.dataValues.Series delete instance.dataValues.series
delete instance.Playlist delete instance.playlist
delete instance.dataValues.Playlist delete instance.dataValues.playlist
} }
}) })

View File

@ -25,13 +25,13 @@ module.exports = (sequelize) => {
explicit: DataTypes.BOOLEAN explicit: DataTypes.BOOLEAN
}, { }, {
sequelize, sequelize,
modelName: 'FeedEpisode' modelName: 'feedEpisode'
}) })
const { Feed } = sequelize.models const { feed } = sequelize.models
Feed.hasMany(FeedEpisode) feed.hasMany(FeedEpisode)
FeedEpisode.belongsTo(Feed) FeedEpisode.belongsTo(feed)
return FeedEpisode return FeedEpisode
} }

View File

@ -21,10 +21,10 @@ module.exports = (sequelize) => {
sequelize, sequelize,
freezeTableName: true, // sequelize uses datum as singular of data freezeTableName: true, // sequelize uses datum as singular of data
name: { name: {
singular: 'FileMetadata', singular: 'fileMetadata',
plural: 'FileMetadata' plural: 'fileMetadata'
}, },
modelName: 'FileMetadata' modelName: 'fileMetadata'
}) })
return FileMetadata return FileMetadata

View File

@ -13,7 +13,7 @@ module.exports = (sequelize) => {
cleanName: DataTypes.STRING cleanName: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'Genre' modelName: 'genre'
}) })
return Genre return Genre

View File

@ -18,7 +18,7 @@ module.exports = (sequelize) => {
lastScanVersion: DataTypes.STRING lastScanVersion: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'Library' modelName: 'library'
}) })
return Library return Library

View File

@ -11,15 +11,15 @@ module.exports = (sequelize) => {
} }
}, { }, {
sequelize, sequelize,
modelName: 'LibraryFile' modelName: 'libraryFile'
}) })
const { LibraryItem, FileMetadata } = sequelize.models const { libraryItem, fileMetadata } = sequelize.models
LibraryItem.hasMany(LibraryFile) libraryItem.hasMany(LibraryFile)
LibraryFile.belongsTo(LibraryItem) LibraryFile.belongsTo(libraryItem)
FileMetadata.hasOne(LibraryFile, { foreignKey: 'FileMetadataId' }) fileMetadata.hasOne(LibraryFile, { foreignKey: 'fileMetadataId' })
LibraryFile.belongsTo(FileMetadata, { as: 'FileMetadata', foreignKey: 'FileMetadataId' }) LibraryFile.belongsTo(fileMetadata, { as: 'fileMetadata', foreignKey: 'fileMetadataId' })
return LibraryFile return LibraryFile
} }

View File

@ -12,12 +12,12 @@ module.exports = (sequelize) => {
path: DataTypes.STRING path: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'LibraryFolder' modelName: 'libraryFolder'
}) })
const { Library } = sequelize.models const { library } = sequelize.models
Library.hasMany(LibraryFolder) library.hasMany(LibraryFolder)
LibraryFolder.belongsTo(Library) LibraryFolder.belongsTo(library)
return LibraryFolder return LibraryFolder
} }

View File

@ -1,7 +1,13 @@
const { DataTypes, Model } = require('sequelize') const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => { module.exports = (sequelize) => {
class LibraryItem extends Model { } class LibraryItem extends Model {
getMedia(options) {
if (!this.mediaType) return Promise.resolve(null)
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaType)}`
return this[mixinMethodName](options)
}
}
LibraryItem.init({ LibraryItem.init({
id: { id: {
@ -12,6 +18,7 @@ module.exports = (sequelize) => {
ino: DataTypes.STRING, ino: DataTypes.STRING,
path: DataTypes.STRING, path: DataTypes.STRING,
relPath: DataTypes.STRING, relPath: DataTypes.STRING,
mediaId: DataTypes.UUIDV4,
mediaType: DataTypes.STRING, mediaType: DataTypes.STRING,
isFile: DataTypes.BOOLEAN, isFile: DataTypes.BOOLEAN,
isMissing: DataTypes.BOOLEAN, isMissing: DataTypes.BOOLEAN,
@ -23,12 +30,50 @@ module.exports = (sequelize) => {
lastScanVersion: DataTypes.STRING lastScanVersion: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'LibraryItem' modelName: 'libraryItem'
}) })
const { LibraryFolder } = sequelize.models const { libraryFolder, book, podcast } = sequelize.models
LibraryFolder.hasMany(LibraryItem) libraryFolder.hasMany(LibraryItem)
LibraryItem.belongsTo(LibraryFolder) LibraryItem.belongsTo(libraryFolder)
book.hasOne(LibraryItem, {
foreignKey: 'mediaId',
constraints: false,
scope: {
mediaType: 'book'
}
})
LibraryItem.belongsTo(book, { foreignKey: 'mediaId', constraints: false })
podcast.hasOne(LibraryItem, {
foreignKey: 'mediaId',
constraints: false,
scope: {
mediaType: 'podcast'
}
})
LibraryItem.belongsTo(podcast, { foreignKey: 'mediaId', constraints: false })
LibraryItem.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaType === 'book' && instance.book !== undefined) {
instance.media = instance.book
instance.dataValues.media = instance.dataValues.book
} else if (instance.mediaType === 'podcast' && instance.podcast !== undefined) {
instance.media = instance.podcast
instance.dataValues.media = instance.dataValues.podcast
}
// To prevent mistakes:
delete instance.book
delete instance.dataValues.book
delete instance.podcast
delete instance.dataValues.podcast
}
})
return LibraryItem return LibraryItem
} }

View File

@ -13,13 +13,13 @@ module.exports = (sequelize) => {
value: DataTypes.STRING value: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'LibrarySetting' modelName: 'librarySetting'
}) })
const { Library } = sequelize.models const { library } = sequelize.models
Library.hasMany(LibrarySetting) library.hasMany(LibrarySetting)
LibrarySetting.belongsTo(Library) LibrarySetting.belongsTo(library)
return LibrarySetting return LibrarySetting
} }

View File

@ -17,13 +17,13 @@ module.exports = (sequelize) => {
tags: DataTypes.JSON tags: DataTypes.JSON
}, { }, {
sequelize, sequelize,
modelName: 'MediaFile' modelName: 'mediaFile'
}) })
const { FileMetadata } = sequelize.models const { fileMetadata } = sequelize.models
FileMetadata.hasOne(MediaFile, { foreignKey: 'FileMetadataId' }) fileMetadata.hasOne(MediaFile, { foreignKey: 'fileMetadataId' })
MediaFile.belongsTo(FileMetadata, { as: 'FileMetadata', foreignKey: 'FileMetadataId' }) MediaFile.belongsTo(fileMetadata, { as: 'fileMetadata', foreignKey: 'fileMetadataId' })
return MediaFile return MediaFile
} }

View File

@ -8,7 +8,7 @@ module.exports = (sequelize) => {
class MediaProgress extends Model { class MediaProgress extends Model {
getMediaItem(options) { getMediaItem(options) {
if (!this.mediaItemType) return Promise.resolve(null) if (!this.mediaItemType) return Promise.resolve(null)
const mixinMethodName = `get${this.mediaItemType}` const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
return this[mixinMethodName](options) return this[mixinMethodName](options)
} }
} }
@ -19,7 +19,7 @@ module.exports = (sequelize) => {
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true primaryKey: true
}, },
MediaItemId: DataTypes.UUIDV4, mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING, mediaItemType: DataTypes.STRING,
duration: DataTypes.FLOAT, duration: DataTypes.FLOAT,
currentTime: DataTypes.FLOAT, currentTime: DataTypes.FLOAT,
@ -28,46 +28,52 @@ module.exports = (sequelize) => {
finishedAt: DataTypes.DATE finishedAt: DataTypes.DATE
}, { }, {
sequelize, sequelize,
modelName: 'MediaProgress' modelName: 'mediaProgress'
}) })
const { Book, PodcastEpisode, User } = sequelize.models 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, { book.hasMany(MediaProgress, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'PodcastEpisode' mediaItemType: 'book'
} }
}) })
MediaProgress.belongsTo(PodcastEpisode, { foreignKey: 'MediaItemId', constraints: false }) 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 => { MediaProgress.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) { for (const instance of findResult) {
if (instance.mediaItemType === 'Book' && instance.Book !== undefined) { if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.MediaItem = instance.Book instance.mediaItem = instance.book
} else if (instance.mediaItemType === 'PodcastEpisode' && instance.PodcastEpisode !== undefined) { instance.dataValues.mediaItem = instance.dataValues.book
instance.MediaItem = instance.PodcastEpisode } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: // To prevent mistakes:
delete instance.Book delete instance.book
delete instance.dataValues.Book delete instance.dataValues.book
delete instance.PodcastEpisode delete instance.podcastEpisode
delete instance.dataValues.PodcastEpisode delete instance.dataValues.podcastEpisode
} }
}) })
User.hasMany(MediaProgress) user.hasMany(MediaProgress)
MediaProgress.belongsTo(User) MediaProgress.belongsTo(user)
return MediaProgress return MediaProgress
} }

View File

@ -37,13 +37,13 @@ module.exports = (sequelize) => {
chapters: DataTypes.JSON chapters: DataTypes.JSON
}, { }, {
sequelize, sequelize,
modelName: 'MediaStream' modelName: 'mediaStream'
}) })
const { MediaFile } = sequelize.models const { mediaFile } = sequelize.models
MediaFile.hasMany(MediaStream) mediaFile.hasMany(MediaStream)
MediaStream.belongsTo(MediaFile) MediaStream.belongsTo(mediaFile)
return MediaStream return MediaStream
} }

View File

@ -22,7 +22,7 @@ module.exports = (sequelize) => {
extraData: DataTypes.JSON extraData: DataTypes.JSON
}, { }, {
sequelize, sequelize,
modelName: 'Notification' modelName: 'notification'
}) })
return Notification return Notification

View File

@ -15,12 +15,12 @@ module.exports = (sequelize) => {
description: DataTypes.TEXT description: DataTypes.TEXT
}, { }, {
sequelize, sequelize,
modelName: 'Person' modelName: 'person'
}) })
const { FileMetadata } = sequelize.models const { fileMetadata } = sequelize.models
FileMetadata.hasMany(Person, { foreignKey: 'ImageFileId' }) fileMetadata.hasMany(Person, { foreignKey: 'imageFileId' })
Person.belongsTo(FileMetadata, { as: 'ImageFile', foreignKey: 'ImageFileId' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias Person.belongsTo(fileMetadata, { as: 'imageFile', foreignKey: 'imageFileId' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias
return Person return Person
} }

View File

@ -4,7 +4,7 @@ module.exports = (sequelize) => {
class PlaybackSession extends Model { class PlaybackSession extends Model {
getMediaItem(options) { getMediaItem(options) {
if (!this.mediaItemType) return Promise.resolve(null) if (!this.mediaItemType) return Promise.resolve(null)
const mixinMethodName = `get${this.mediaItemType}` const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
return this[mixinMethodName](options) return this[mixinMethodName](options)
} }
} }
@ -15,7 +15,7 @@ module.exports = (sequelize) => {
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true primaryKey: true
}, },
MediaItemId: DataTypes.UUIDV4, mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING, mediaItemType: DataTypes.STRING,
displayTitle: DataTypes.STRING, displayTitle: DataTypes.STRING,
displayAuthor: DataTypes.STRING, displayAuthor: DataTypes.STRING,
@ -27,48 +27,53 @@ module.exports = (sequelize) => {
serverVersion: DataTypes.STRING serverVersion: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'PlaybackSession' modelName: 'playbackSession'
}) })
const { Book, PodcastEpisode, User, Device } = sequelize.models const { book, podcastEpisode, user, device } = sequelize.models
User.hasMany(PlaybackSession) user.hasMany(PlaybackSession)
PlaybackSession.belongsTo(User) PlaybackSession.belongsTo(user)
Device.hasMany(PlaybackSession) device.hasMany(PlaybackSession)
PlaybackSession.belongsTo(Device) PlaybackSession.belongsTo(device)
Book.hasMany(PlaybackSession, { book.hasMany(PlaybackSession, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'Book' mediaItemType: 'book'
} }
}) })
PlaybackSession.belongsTo(Book, { foreignKey: 'MediaItemId', constraints: false }) PlaybackSession.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
PodcastEpisode.hasOne(PlaybackSession, { podcastEpisode.hasOne(PlaybackSession, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'PodcastEpisode' mediaItemType: 'podcastEpisode'
} }
}) })
PlaybackSession.belongsTo(PodcastEpisode, { foreignKey: 'MediaItemId', constraints: false }) PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaybackSession.addHook('afterFind', findResult => { PlaybackSession.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) { for (const instance of findResult) {
if (instance.mediaItemType === 'Book' && instance.Book !== undefined) { if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.MediaItem = instance.Book instance.mediaItem = instance.book
} else if (instance.mediaItemType === 'PodcastEpisode' && instance.PodcastEpisode !== undefined) { instance.dataValues.mediaItem = instance.dataValues.book
instance.MediaItem = instance.PodcastEpisode } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: // To prevent mistakes:
delete instance.Book delete instance.book
delete instance.dataValues.Book delete instance.dataValues.book
delete instance.PodcastEpisode delete instance.podcastEpisode
delete instance.dataValues.PodcastEpisode delete instance.dataValues.podcastEpisode
} }
}) })

View File

@ -13,13 +13,13 @@ module.exports = (sequelize) => {
date: DataTypes.STRING date: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'PlaybackSessionListenTime' modelName: 'playbackSessionListenTime'
}) })
const { PlaybackSession } = sequelize.models const { playbackSession } = sequelize.models
PlaybackSession.hasMany(PlaybackSessionListenTime) playbackSession.hasMany(PlaybackSessionListenTime)
PlaybackSessionListenTime.belongsTo(PlaybackSession) PlaybackSessionListenTime.belongsTo(playbackSession)
return PlaybackSessionListenTime return PlaybackSessionListenTime
} }

View File

@ -13,15 +13,15 @@ module.exports = (sequelize) => {
description: DataTypes.TEXT description: DataTypes.TEXT
}, { }, {
sequelize, sequelize,
modelName: 'Playlist' modelName: 'playlist'
}) })
const { Library, User } = sequelize.models const { library, user } = sequelize.models
Library.hasMany(Playlist) library.hasMany(Playlist)
Playlist.belongsTo(Library) Playlist.belongsTo(library)
User.hasMany(Playlist) user.hasMany(Playlist)
Playlist.belongsTo(User) Playlist.belongsTo(user)
return Playlist return Playlist
} }

View File

@ -4,7 +4,7 @@ module.exports = (sequelize) => {
class PlaylistMediaItem extends Model { class PlaylistMediaItem extends Model {
getMediaItem(options) { getMediaItem(options) {
if (!this.mediaItemType) return Promise.resolve(null) if (!this.mediaItemType) return Promise.resolve(null)
const mixinMethodName = `get${this.mediaItemType}` const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
return this[mixinMethodName](options) return this[mixinMethodName](options)
} }
} }
@ -15,53 +15,58 @@ module.exports = (sequelize) => {
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true primaryKey: true
}, },
MediaItemId: DataTypes.UUIDV4, mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING mediaItemType: DataTypes.STRING
}, { }, {
sequelize, sequelize,
timestamps: true, timestamps: true,
updatedAt: false, updatedAt: false,
modelName: 'PlaylistMediaItem' modelName: 'playlistMediaItem'
}) })
const { Book, PodcastEpisode, Playlist } = sequelize.models const { book, podcastEpisode, playlist } = sequelize.models
Book.hasMany(PlaylistMediaItem, { book.hasMany(PlaylistMediaItem, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'Book' mediaItemType: 'book'
} }
}) })
PlaylistMediaItem.belongsTo(Book, { foreignKey: 'MediaItemId', constraints: false }) PlaylistMediaItem.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
PodcastEpisode.hasOne(PlaylistMediaItem, { podcastEpisode.hasOne(PlaylistMediaItem, {
foreignKey: 'MediaItemId', foreignKey: 'mediaItemId',
constraints: false, constraints: false,
scope: { scope: {
mediaItemType: 'PodcastEpisode' mediaItemType: 'podcastEpisode'
} }
}) })
PlaylistMediaItem.belongsTo(PodcastEpisode, { foreignKey: 'MediaItemId', constraints: false }) PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaylistMediaItem.addHook('afterFind', findResult => { PlaylistMediaItem.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) { for (const instance of findResult) {
if (instance.mediaItemType === 'Book' && instance.Book !== undefined) { if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.MediaItem = instance.Book instance.mediaItem = instance.book
} else if (instance.mediaItemType === 'PodcastEpisode' && instance.PodcastEpisode !== undefined) { instance.dataValues.mediaItem = instance.dataValues.book
instance.MediaItem = instance.PodcastEpisode } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
} }
// To prevent mistakes: // To prevent mistakes:
delete instance.Book delete instance.book
delete instance.dataValues.Book delete instance.dataValues.book
delete instance.PodcastEpisode delete instance.podcastEpisode
delete instance.dataValues.PodcastEpisode delete instance.dataValues.podcastEpisode
} }
}) })
Playlist.hasMany(PlaylistMediaItem) playlist.hasMany(PlaylistMediaItem)
PlaylistMediaItem.belongsTo(Playlist) PlaylistMediaItem.belongsTo(playlist)
return PlaylistMediaItem return PlaylistMediaItem
} }

View File

@ -9,7 +9,6 @@ module.exports = (sequelize) => {
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true primaryKey: true
}, },
// Metadata
title: DataTypes.STRING, title: DataTypes.STRING,
author: DataTypes.STRING, author: DataTypes.STRING,
releaseDate: DataTypes.STRING, releaseDate: DataTypes.STRING,
@ -32,15 +31,13 @@ module.exports = (sequelize) => {
lastCoverSearch: DataTypes.DATE lastCoverSearch: DataTypes.DATE
}, { }, {
sequelize, sequelize,
modelName: 'Podcast' modelName: 'podcast'
}) })
const { LibraryItem, FileMetadata } = sequelize.models const { fileMetadata } = sequelize.models
LibraryItem.hasOne(Podcast)
Podcast.belongsTo(LibraryItem)
FileMetadata.hasOne(Podcast, { foreignKey: 'ImageFileId' }) fileMetadata.hasOne(Podcast, { foreignKey: 'imageFileId' })
Podcast.belongsTo(FileMetadata, { as: 'ImageFile', foreignKey: 'ImageFileId' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias Podcast.belongsTo(fileMetadata, { as: 'imageFile', foreignKey: 'imageFileId' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias
return Podcast return Podcast
} }

View File

@ -23,12 +23,12 @@ module.exports = (sequelize) => {
publishedAt: DataTypes.DATE publishedAt: DataTypes.DATE
}, { }, {
sequelize, sequelize,
modelName: 'PodcastEpisode' modelName: 'podcastEpisode'
}) })
const { Podcast } = sequelize.models const { podcast } = sequelize.models
Podcast.hasMany(PodcastEpisode) podcast.hasMany(PodcastEpisode)
PodcastEpisode.belongsTo(Podcast) PodcastEpisode.belongsTo(podcast)
return PodcastEpisode return PodcastEpisode
} }

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
} }
}, { }, {
sequelize, sequelize,
modelName: 'PodcastGenre', modelName: 'podcastGenre',
timestamps: false timestamps: false
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Podcast, Genre } = sequelize.models const { podcast, genre } = sequelize.models
Podcast.belongsToMany(Genre, { through: PodcastGenre }) podcast.belongsToMany(genre, { through: PodcastGenre })
Genre.belongsToMany(Podcast, { through: PodcastGenre }) genre.belongsToMany(podcast, { through: PodcastGenre })
Podcast.hasMany(PodcastGenre) podcast.hasMany(PodcastGenre)
PodcastGenre.belongsTo(Podcast) PodcastGenre.belongsTo(podcast)
Genre.hasMany(PodcastGenre) genre.hasMany(PodcastGenre)
PodcastGenre.belongsTo(Genre) PodcastGenre.belongsTo(genre)
return PodcastGenre return PodcastGenre
} }

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
} }
}, { }, {
sequelize, sequelize,
modelName: 'PodcastTag', modelName: 'podcastTag',
timestamps: false timestamps: false
}) })
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
const { Podcast, Tag } = sequelize.models const { podcast, tag } = sequelize.models
Podcast.belongsToMany(Tag, { through: PodcastTag }) podcast.belongsToMany(tag, { through: PodcastTag })
Tag.belongsToMany(Podcast, { through: PodcastTag }) tag.belongsToMany(podcast, { through: PodcastTag })
Podcast.hasMany(PodcastTag) podcast.hasMany(PodcastTag)
PodcastTag.belongsTo(Podcast) PodcastTag.belongsTo(podcast)
Tag.hasMany(PodcastTag) tag.hasMany(PodcastTag)
PodcastTag.belongsTo(Tag) PodcastTag.belongsTo(tag)
return PodcastTag return PodcastTag
} }

View File

@ -13,7 +13,7 @@ module.exports = (sequelize) => {
description: DataTypes.TEXT description: DataTypes.TEXT
}, { }, {
sequelize, sequelize,
modelName: 'Series' modelName: 'series'
}) })
return Series return Series

View File

@ -12,7 +12,7 @@ module.exports = (sequelize) => {
type: DataTypes.INTEGER type: DataTypes.INTEGER
}, { }, {
sequelize, sequelize,
modelName: 'Setting' modelName: 'setting'
}) })
return Setting return Setting

View File

@ -13,7 +13,7 @@ module.exports = (sequelize) => {
cleanName: DataTypes.STRING cleanName: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'Tag' modelName: 'tag'
}) })
return Tag return Tag

View File

@ -26,7 +26,7 @@ module.exports = (sequelize) => {
extraData: DataTypes.JSON extraData: DataTypes.JSON
}, { }, {
sequelize, sequelize,
modelName: 'User' modelName: 'user'
}) })
return User return User

View File

@ -13,13 +13,13 @@ module.exports = (sequelize) => {
value: DataTypes.STRING value: DataTypes.STRING
}, { }, {
sequelize, sequelize,
modelName: 'UserPermission' modelName: 'userPermission'
}) })
const { User } = sequelize.models const { user } = sequelize.models
User.hasMany(UserPermission) user.hasMany(UserPermission)
UserPermission.belongsTo(User) UserPermission.belongsTo(user)
return UserPermission return UserPermission
} }

View File

@ -0,0 +1,15 @@
const express = require('express')
const LibraryItemController = require('../controllers2/LibraryItemController')
class ApiRouter2 {
constructor(Server) {
this.router = express()
this.router.disable('x-powered-by')
this.init()
}
init() {
this.router.get('/items/:id', LibraryItemController.get.bind(this))
}
}
module.exports = ApiRouter2

View File

@ -1,6 +1,5 @@
const Path = require('path') const Path = require('path')
const uuidv4 = require("uuid").v4 const uuidv4 = require("uuid").v4
const package = require('../../../package.json')
const { AudioMimeType } = require('../constants') const { AudioMimeType } = require('../constants')
const Logger = require('../../Logger') const Logger = require('../../Logger')
const Database = require('../../Database') const Database = require('../../Database')
@ -20,49 +19,50 @@ const oldDbIdMap = {
files: {}, // key is fullpath files: {}, // key is fullpath
podcastEpisodes: {}, podcastEpisodes: {},
books: {}, // key is library item id books: {}, // key is library item id
podcasts: {}, // key is library item id
devices: {} // key is a json stringify of the old DeviceInfo data devices: {} // key is a json stringify of the old DeviceInfo data
} }
const newRecords = { const newRecords = {
User: [], user: [],
UserPermission: [], userPermission: [],
Library: [], library: [],
LibraryFolder: [], libraryFolder: [],
LibrarySetting: [], librarySetting: [],
FileMetadata: [], fileMetadata: [],
Person: [], person: [],
LibraryItem: [], eBookFile: [],
LibraryFile: [], book: [],
EBookFile: [], podcast: [],
Book: [], libraryItem: [],
BookAuthor: [], libraryFile: [],
BookNarrator: [], bookAuthor: [],
BookChapter: [], bookNarrator: [],
Tag: [], bookChapter: [],
BookTag: [], tag: [],
Genre: [], bookTag: [],
BookGenre: [], genre: [],
Series: [], bookGenre: [],
BookSeries: [], series: [],
Podcast: [], bookSeries: [],
PodcastTag: [], podcastTag: [],
PodcastGenre: [], podcastGenre: [],
PodcastEpisode: [], podcastEpisode: [],
MediaProgress: [], mediaProgress: [],
AudioBookmark: [], audioBookmark: [],
MediaFile: [], mediaFile: [],
MediaStream: [], mediaStream: [],
AudioTrack: [], audioTrack: [],
Device: [], device: [],
PlaybackSession: [], playbackSession: [],
PlaybackSessionListenTime: [], playbackSessionListenTime: [],
Collection: [], collection: [],
CollectionBook: [], collectionBook: [],
Playlist: [], playlist: [],
PlaylistMediaItem: [], playlistMediaItem: [],
Feed: [], feed: [],
FeedEpisode: [], feedEpisode: [],
Setting: [], setting: [],
Notification: [] notification: []
} }
function getDeviceInfoString(deviceInfo, UserId) { function getDeviceInfoString(deviceInfo, UserId) {
@ -102,10 +102,10 @@ function migrateBook(oldLibraryItem, LibraryItem) {
// //
// Migrate ImageFile // Migrate ImageFile
// //
let ImageFileId = null let imageFileId = null
if (oldBook.coverPath) { if (oldBook.coverPath) {
ImageFileId = oldDbIdMap.files[oldBook.coverPath] || null imageFileId = oldDbIdMap.files[oldBook.coverPath] || null
if (!ImageFileId) { if (!imageFileId) {
const FileMetadata = { const FileMetadata = {
id: uuidv4(), id: uuidv4(),
filename: Path.basename(oldBook.coverPath), filename: Path.basename(oldBook.coverPath),
@ -114,26 +114,28 @@ function migrateBook(oldLibraryItem, LibraryItem) {
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt updatedAt: LibraryItem.updatedAt
} }
newRecords.FileMetadata.push(FileMetadata) newRecords.fileMetadata.push(FileMetadata)
oldDbIdMap.files[oldBook.coverPath] = FileMetadata.id oldDbIdMap.files[oldBook.coverPath] = FileMetadata.id
ImageFileId = FileMetadata.id imageFileId = FileMetadata.id
} }
} }
// //
// Migrate EBookFile // Migrate EBookFile
// //
let EBookFileId = null let eBookFileId = null
if (oldBook.ebookFile) { if (oldBook.ebookFile) {
if (oldDbIdMap.files[oldBook.ebookFile.metadata?.path]) { if (oldDbIdMap.files[oldBook.ebookFile.metadata?.path]) {
const ext = oldBook.ebookFile.metadata.ext || ''
const EBookFile = { const EBookFile = {
id: uuidv4(), id: uuidv4(),
FileMetadataId: oldDbIdMap.files[oldBook.ebookFile.metadata?.path] format: ext.toLowerCase().slice(1),
fileMetadataId: oldDbIdMap.files[oldBook.ebookFile.metadata.path]
} }
newRecords.EBookFile.push(EBookFile) newRecords.eBookFile.push(EBookFile)
EBookFileId = EBookFile.id eBookFileId = EBookFile.id
} else { } else {
Logger.warn(`[dbMigration] migrateBook: `) Logger.warn(`[dbMigration] migrateBook: Unable to find ebook file`)
} }
} }
@ -156,11 +158,10 @@ function migrateBook(oldLibraryItem, LibraryItem) {
lastCoverSearch: oldBook.lastCoverSearch, lastCoverSearch: oldBook.lastCoverSearch,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
ImageFileId, imageFileId,
EBookFileId, eBookFileId
LibraryItemId: LibraryItem.id,
} }
newRecords.Book.push(Book) newRecords.book.push(Book)
oldDbIdMap.books[oldLibraryItem.id] = Book.id oldDbIdMap.books[oldLibraryItem.id] = Book.id
// //
@ -169,8 +170,8 @@ function migrateBook(oldLibraryItem, LibraryItem) {
const oldAudioFiles = oldBook.audioFiles const oldAudioFiles = oldBook.audioFiles
let startOffset = 0 let startOffset = 0
for (const oldAudioFile of oldAudioFiles) { for (const oldAudioFile of oldAudioFiles) {
const FileMetadataId = oldDbIdMap.files[oldAudioFile.metadata.path] const fileMetadataId = oldDbIdMap.files[oldAudioFile.metadata.path]
if (!FileMetadataId) { if (!fileMetadataId) {
Logger.warn(`[dbMigration] migrateBook: File metadata not found for audio file "${oldAudioFile.metadata.path}"`) Logger.warn(`[dbMigration] migrateBook: File metadata not found for audio file "${oldAudioFile.metadata.path}"`)
continue continue
} }
@ -187,9 +188,9 @@ function migrateBook(oldLibraryItem, LibraryItem) {
tags: cleanAudioFileMetaTags(oldAudioFile.metaTags), tags: cleanAudioFileMetaTags(oldAudioFile.metaTags),
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
FileMetadataId fileMetadataId
} }
newRecords.MediaFile.push(MediaFile) newRecords.mediaFile.push(MediaFile)
const MediaStream = { const MediaStream = {
id: uuidv4(), id: uuidv4(),
@ -207,9 +208,9 @@ function migrateBook(oldLibraryItem, LibraryItem) {
chapters: oldAudioFile.chapters, chapters: oldAudioFile.chapters,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
MediaFileId: MediaFile.id mediaFileId: MediaFile.id
} }
newRecords.MediaStream.push(MediaStream) newRecords.mediaStream.push(MediaStream)
if (oldAudioFile.embeddedCoverArt) { if (oldAudioFile.embeddedCoverArt) {
const CoverMediaStream = { const CoverMediaStream = {
@ -220,17 +221,17 @@ function migrateBook(oldLibraryItem, LibraryItem) {
default: true, default: true,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
MediaFileId: MediaFile.id mediaFileId: MediaFile.id
} }
newRecords.MediaStream.push(CoverMediaStream) newRecords.mediaStream.push(CoverMediaStream)
} }
const include = !oldAudioFile.exclude && !oldAudioFile.invalid const include = !oldAudioFile.exclude && !oldAudioFile.invalid
const AudioTrack = { const AudioTrack = {
id: uuidv4(), id: uuidv4(),
MediaItemId: Book.id, mediaItemId: Book.id,
mediaItemType: 'Book', mediaItemType: 'book',
index: oldAudioFile.index, index: oldAudioFile.index,
startOffset: include ? startOffset : null, startOffset: include ? startOffset : null,
duration: oldAudioFile.duration, duration: oldAudioFile.duration,
@ -241,9 +242,9 @@ function migrateBook(oldLibraryItem, LibraryItem) {
discNumber: oldAudioFile.discNumFromMeta || oldAudioFile.discNumFromFilename, discNumber: oldAudioFile.discNumFromMeta || oldAudioFile.discNumFromFilename,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
MediaFileId: MediaFile.id mediaFileId: MediaFile.id
} }
newRecords.AudioTrack.push(AudioTrack) newRecords.audioTrack.push(AudioTrack)
if (include) { if (include) {
startOffset += AudioTrack.duration startOffset += AudioTrack.duration
@ -267,13 +268,13 @@ function migrateBook(oldLibraryItem, LibraryItem) {
updatedAt: LibraryItem.updatedAt updatedAt: LibraryItem.updatedAt
} }
tagId = Tag.id tagId = Tag.id
newRecords.Tag.push(Tag) newRecords.tag.push(Tag)
} }
newRecords.BookTag.push({ newRecords.bookTag.push({
id: uuidv4(), id: uuidv4(),
BookId: Book.id, bookId: Book.id,
TagId: tagId tagId
}) })
} }
@ -281,7 +282,7 @@ function migrateBook(oldLibraryItem, LibraryItem) {
// Migrate BookChapters // Migrate BookChapters
// //
for (const oldChapter of oldBook.chapters) { for (const oldChapter of oldBook.chapters) {
newRecords.BookChapter.push({ newRecords.bookChapter.push({
id: uuidv4(), id: uuidv4(),
index: oldChapter.id, index: oldChapter.id,
start: oldChapter.start, start: oldChapter.start,
@ -289,7 +290,7 @@ function migrateBook(oldLibraryItem, LibraryItem) {
title: oldChapter.title, title: oldChapter.title,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
BookId: Book.id bookId: Book.id
}) })
} }
@ -311,13 +312,13 @@ function migrateBook(oldLibraryItem, LibraryItem) {
updatedAt: LibraryItem.updatedAt updatedAt: LibraryItem.updatedAt
} }
genreId = Genre.id genreId = Genre.id
newRecords.Genre.push(Genre) newRecords.genre.push(Genre)
} }
newRecords.BookGenre.push({ newRecords.bookGenre.push({
id: uuidv4(), id: uuidv4(),
BookId: Book.id, bookId: Book.id,
GenreId: genreId genreId
}) })
} }
@ -326,10 +327,10 @@ function migrateBook(oldLibraryItem, LibraryItem) {
// //
for (const oldBookAuthor of oldBook.metadata.authors) { for (const oldBookAuthor of oldBook.metadata.authors) {
if (oldDbIdMap.people[oldBookAuthor.id]) { if (oldDbIdMap.people[oldBookAuthor.id]) {
newRecords.BookAuthor.push({ newRecords.bookAuthor.push({
id: uuidv4(), id: uuidv4(),
PersonId: oldDbIdMap.people[oldBookAuthor.id], authorId: oldDbIdMap.people[oldBookAuthor.id],
BookId: Book.id bookId: Book.id
}) })
} else { } else {
Logger.warn(`[dbMigration] migrateBook: Book author not found "${oldBookAuthor.name}"`) Logger.warn(`[dbMigration] migrateBook: Book author not found "${oldBookAuthor.name}"`)
@ -340,23 +341,23 @@ function migrateBook(oldLibraryItem, LibraryItem) {
// Migrate BookNarrators // Migrate BookNarrators
// //
for (const oldBookNarrator of oldBook.metadata.narrators) { for (const oldBookNarrator of oldBook.metadata.narrators) {
let PersonId = oldDbIdMap.people[oldBookNarrator] let personId = oldDbIdMap.people[oldBookNarrator]
if (!PersonId) { if (!personId) {
const Person = { const Person = {
id: uuidv4(), id: uuidv4(),
type: 'Narrator', type: 'narrator',
name: oldBookNarrator, name: oldBookNarrator,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt updatedAt: LibraryItem.updatedAt
} }
newRecords.Person.push(Person) newRecords.person.push(Person)
PersonId = Person.id personId = Person.id
} }
newRecords.BookNarrator.push({ newRecords.bookNarrator.push({
id: uuidv4(), id: uuidv4(),
PersonId, narratorId: personId,
BookId: Book.id bookId: Book.id
}) })
} }
@ -368,10 +369,10 @@ function migrateBook(oldLibraryItem, LibraryItem) {
const BookSeries = { const BookSeries = {
id: uuidv4(), id: uuidv4(),
sequence: oldBookSeries.sequence, sequence: oldBookSeries.sequence,
SeriesId: oldDbIdMap.series[oldBookSeries.id], seriesId: oldDbIdMap.series[oldBookSeries.id],
BookId: Book.id bookId: Book.id
} }
newRecords.BookSeries.push(BookSeries) newRecords.bookSeries.push(BookSeries)
} else { } else {
Logger.warn(`[dbMigration] migrateBook: Series not found "${oldBookSeries.name}"`) Logger.warn(`[dbMigration] migrateBook: Series not found "${oldBookSeries.name}"`)
} }
@ -385,10 +386,10 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
// //
// Migrate ImageFile // Migrate ImageFile
// //
let ImageFileId = null let imageFileId = null
if (oldPodcast.coverPath) { if (oldPodcast.coverPath) {
ImageFileId = oldDbIdMap.files[oldPodcast.coverPath] || null imageFileId = oldDbIdMap.files[oldPodcast.coverPath] || null
if (!ImageFileId) { if (!imageFileId) {
const FileMetadata = { const FileMetadata = {
id: uuidv4(), id: uuidv4(),
filename: Path.basename(oldPodcast.coverPath), filename: Path.basename(oldPodcast.coverPath),
@ -397,9 +398,9 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt updatedAt: LibraryItem.updatedAt
} }
newRecords.FileMetadata.push(FileMetadata) newRecords.fileMetadata.push(FileMetadata)
oldDbIdMap.files[oldPodcast.coverPath] = FileMetadata.id oldDbIdMap.files[oldPodcast.coverPath] = FileMetadata.id
ImageFileId = FileMetadata.id imageFileId = FileMetadata.id
} }
} }
@ -429,10 +430,10 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
lastCoverSearch: oldPodcast.lastCoverSearch, lastCoverSearch: oldPodcast.lastCoverSearch,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
ImageFileId, imageFileId,
LibraryItemId: LibraryItem.id
} }
newRecords.Podcast.push(Podcast) newRecords.podcast.push(Podcast)
oldDbIdMap.podcasts[oldLibraryItem.id] = Podcast.id
// //
// Migrate Tags // Migrate Tags
@ -451,13 +452,13 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
updatedAt: LibraryItem.updatedAt updatedAt: LibraryItem.updatedAt
} }
tagId = Tag.id tagId = Tag.id
newRecords.Tag.push(Tag) newRecords.tag.push(Tag)
} }
newRecords.PodcastTag.push({ newRecords.podcastTag.push({
id: uuidv4(), id: uuidv4(),
PodcastId: Podcast.id, podcastId: Podcast.id,
TagId: tagId tagId
}) })
} }
@ -478,13 +479,13 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
updatedAt: LibraryItem.updatedAt updatedAt: LibraryItem.updatedAt
} }
genreId = Genre.id genreId = Genre.id
newRecords.Genre.push(Genre) newRecords.genre.push(Genre)
} }
newRecords.PodcastGenre.push({ newRecords.podcastGenre.push({
id: uuidv4(), id: uuidv4(),
PodcastId: Podcast.id, podcastId: Podcast.id,
GenreId: genreId genreId
}) })
} }
@ -494,8 +495,8 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
const oldEpisodes = oldPodcast.episodes || [] const oldEpisodes = oldPodcast.episodes || []
for (const oldEpisode of oldEpisodes) { for (const oldEpisode of oldEpisodes) {
const oldAudioFile = oldEpisode.audioFile const oldAudioFile = oldEpisode.audioFile
const FileMetadataId = oldDbIdMap.files[oldAudioFile.metadata.path] const fileMetadataId = oldDbIdMap.files[oldAudioFile.metadata.path]
if (!FileMetadataId) { if (!fileMetadataId) {
Logger.warn(`[dbMigration] migratePodcast: File metadata not found for audio file "${oldAudioFile.metadata.path}"`) Logger.warn(`[dbMigration] migratePodcast: File metadata not found for audio file "${oldAudioFile.metadata.path}"`)
continue continue
} }
@ -516,9 +517,9 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
publishedAt: oldEpisode.publishedAt, publishedAt: oldEpisode.publishedAt,
createdAt: oldEpisode.addedAt, createdAt: oldEpisode.addedAt,
updatedAt: oldEpisode.updatedAt, updatedAt: oldEpisode.updatedAt,
PodcastId: Podcast.id podcastId: Podcast.id
} }
newRecords.PodcastEpisode.push(PodcastEpisode) newRecords.podcastEpisode.push(PodcastEpisode)
oldDbIdMap.podcastEpisodes[oldEpisode.id] = PodcastEpisode.id oldDbIdMap.podcastEpisodes[oldEpisode.id] = PodcastEpisode.id
// //
@ -535,9 +536,9 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
tags: cleanAudioFileMetaTags(oldAudioFile.metaTags), tags: cleanAudioFileMetaTags(oldAudioFile.metaTags),
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
FileMetadataId fileMetadataId
} }
newRecords.MediaFile.push(MediaFile) newRecords.mediaFile.push(MediaFile)
const MediaStream = { const MediaStream = {
id: uuidv4(), id: uuidv4(),
@ -555,9 +556,9 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
chapters: oldAudioFile.chapters, chapters: oldAudioFile.chapters,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
MediaFileId: MediaFile.id mediaFileId: MediaFile.id
} }
newRecords.MediaStream.push(MediaStream) newRecords.mediaStream.push(MediaStream)
if (oldAudioFile.embeddedCoverArt) { if (oldAudioFile.embeddedCoverArt) {
const CoverMediaStream = { const CoverMediaStream = {
@ -568,15 +569,15 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
default: true, default: true,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
MediaFileId: MediaFile.id mediaFileId: MediaFile.id
} }
newRecords.MediaStream.push(CoverMediaStream) newRecords.mediaStream.push(CoverMediaStream)
} }
const AudioTrack = { const AudioTrack = {
id: uuidv4(), id: uuidv4(),
MediaItemId: Podcast.id, mediaItemId: PodcastEpisode.id,
mediaItemType: 'Podcast', mediaItemType: 'podcastEpisode',
index: oldAudioFile.index, index: oldAudioFile.index,
startOffset: 0, startOffset: 0,
duration: oldAudioFile.duration, duration: oldAudioFile.duration,
@ -587,16 +588,16 @@ function migratePodcast(oldLibraryItem, LibraryItem) {
discNumber: oldAudioFile.discNumFromMeta || oldAudioFile.discNumFromFilename, discNumber: oldAudioFile.discNumFromMeta || oldAudioFile.discNumFromFilename,
createdAt: LibraryItem.createdAt, createdAt: LibraryItem.createdAt,
updatedAt: LibraryItem.updatedAt, updatedAt: LibraryItem.updatedAt,
MediaFileId: MediaFile.id mediaFileId: MediaFile.id
} }
newRecords.AudioTrack.push(AudioTrack) newRecords.audioTrack.push(AudioTrack)
} }
} }
function migrateLibraryItems(oldLibraryItems) { function migrateLibraryItems(oldLibraryItems) {
for (const oldLibraryItem of oldLibraryItems) { for (const oldLibraryItem of oldLibraryItems) {
const LibraryId = oldDbIdMap.libraryFolders[oldLibraryItem.folderId] const libraryFolderId = oldDbIdMap.libraryFolders[oldLibraryItem.folderId]
if (!LibraryId) { if (!libraryFolderId) {
Logger.error(`[dbMigration] migrateLibraryItems: Old library folder id not found "${oldLibraryItem.folderId}"`) Logger.error(`[dbMigration] migrateLibraryItems: Old library folder id not found "${oldLibraryItem.folderId}"`)
continue continue
} }
@ -609,6 +610,7 @@ function migrateLibraryItems(oldLibraryItems) {
ino: oldLibraryItem.ino, ino: oldLibraryItem.ino,
path: oldLibraryItem.path, path: oldLibraryItem.path,
relPath: oldLibraryItem.relPath, relPath: oldLibraryItem.relPath,
mediaId: null, // set below
mediaType: oldLibraryItem.mediaType, mediaType: oldLibraryItem.mediaType,
isFile: !!oldLibraryItem.isFile, isFile: !!oldLibraryItem.isFile,
isMissing: !!oldLibraryItem.isMissing, isMissing: !!oldLibraryItem.isMissing,
@ -620,10 +622,10 @@ function migrateLibraryItems(oldLibraryItems) {
lastScanVersion: oldLibraryItem.scanVersion, lastScanVersion: oldLibraryItem.scanVersion,
createdAt: oldLibraryItem.addedAt, createdAt: oldLibraryItem.addedAt,
updatedAt: oldLibraryItem.updatedAt, updatedAt: oldLibraryItem.updatedAt,
LibraryId libraryFolderId
} }
oldDbIdMap.libraryItems[oldLibraryItem.id] = LibraryItem.id oldDbIdMap.libraryItems[oldLibraryItem.id] = LibraryItem.id
newRecords.LibraryItem.push(LibraryItem) newRecords.libraryItem.push(LibraryItem)
// //
// Migrate LibraryFiles // Migrate LibraryFiles
@ -642,17 +644,17 @@ function migrateLibraryItems(oldLibraryItems) {
createdAt: oldLibraryFile.addedAt || Date.now(), createdAt: oldLibraryFile.addedAt || Date.now(),
updatedAt: oldLibraryFile.updatedAt || Date.now() updatedAt: oldLibraryFile.updatedAt || Date.now()
} }
newRecords.FileMetadata.push(FileMetadata) newRecords.fileMetadata.push(FileMetadata)
oldDbIdMap.files[FileMetadata.path] = FileMetadata.id oldDbIdMap.files[FileMetadata.path] = FileMetadata.id
const LibraryFile = { const LibraryFile = {
id: uuidv4(), id: uuidv4(),
createdAt: FileMetadata.createdAt, createdAt: FileMetadata.createdAt,
updatedAt: FileMetadata.updatedAt, updatedAt: FileMetadata.updatedAt,
FileMetadataId: FileMetadata.id, fileMetadataId: FileMetadata.id,
LibraryItemId: LibraryItem.id libraryItemId: LibraryItem.id
} }
newRecords.LibraryFile.push(LibraryFile) newRecords.libraryFile.push(LibraryFile)
} }
// //
@ -660,8 +662,10 @@ function migrateLibraryItems(oldLibraryItems) {
// //
if (oldLibraryItem.mediaType === 'book') { if (oldLibraryItem.mediaType === 'book') {
migrateBook(oldLibraryItem, LibraryItem) migrateBook(oldLibraryItem, LibraryItem)
LibraryItem.mediaId = oldDbIdMap.books[oldLibraryItem.id]
} else if (oldLibraryItem.mediaType === 'podcast') { } else if (oldLibraryItem.mediaType === 'podcast') {
migratePodcast(oldLibraryItem, LibraryItem) migratePodcast(oldLibraryItem, LibraryItem)
LibraryItem.mediaId = oldDbIdMap.podcasts[oldLibraryItem.id]
} }
} }
} }
@ -682,20 +686,20 @@ function migrateLibraries(oldLibraries) {
updatedAt: oldLibrary.lastUpdate updatedAt: oldLibrary.lastUpdate
} }
oldDbIdMap.libraries[oldLibrary.id] = Library.id oldDbIdMap.libraries[oldLibrary.id] = Library.id
newRecords.Library.push(Library) newRecords.library.push(Library)
// //
// Migrate LibrarySettings // Migrate LibrarySettings
// //
const oldLibrarySettings = oldLibrary.settings || {} const oldLibrarySettings = oldLibrary.settings || {}
for (const oldSettingsKey in oldLibrarySettings) { for (const oldSettingsKey in oldLibrarySettings) {
newRecords.LibrarySetting.push({ newRecords.librarySetting.push({
id: uuidv4(), id: uuidv4(),
key: oldSettingsKey, key: oldSettingsKey,
value: oldLibrarySettings[oldSettingsKey], value: oldLibrarySettings[oldSettingsKey],
createdAt: oldLibrary.createdAt, createdAt: oldLibrary.createdAt,
updatedAt: oldLibrary.lastUpdate, updatedAt: oldLibrary.lastUpdate,
LibraryId: Library.id libraryId: Library.id
}) })
} }
@ -708,10 +712,10 @@ function migrateLibraries(oldLibraries) {
path: oldFolder.fullPath, path: oldFolder.fullPath,
createdAt: oldFolder.addedAt, createdAt: oldFolder.addedAt,
updatedAt: oldLibrary.lastUpdate, updatedAt: oldLibrary.lastUpdate,
LibraryId: Library.id libraryId: Library.id
} }
oldDbIdMap.libraryFolders[oldFolder.id] = LibraryFolder.id oldDbIdMap.libraryFolders[oldFolder.id] = LibraryFolder.id
newRecords.LibraryFolder.push(LibraryFolder) newRecords.libraryFolder.push(LibraryFolder)
} }
} }
} }
@ -728,22 +732,22 @@ function migrateAuthors(oldAuthors) {
createdAt: oldAuthor.addedAt || Date.now(), createdAt: oldAuthor.addedAt || Date.now(),
updatedAt: oldAuthor.updatedAt || Date.now() updatedAt: oldAuthor.updatedAt || Date.now()
} }
newRecords.FileMetadata.push(FileMetadata) newRecords.fileMetadata.push(FileMetadata)
imageFileId = FileMetadata.id imageFileId = FileMetadata.id
} }
const Person = { const Person = {
id: uuidv4(), id: uuidv4(),
type: 'Author', type: 'author',
name: oldAuthor.name, name: oldAuthor.name,
asin: oldAuthor.asin || null, asin: oldAuthor.asin || null,
description: oldAuthor.description, description: oldAuthor.description,
createdAt: oldAuthor.addedAt || Date.now(), createdAt: oldAuthor.addedAt || Date.now(),
updatedAt: oldAuthor.updatedAt || Date.now(), updatedAt: oldAuthor.updatedAt || Date.now(),
ImageFileId: imageFileId imageFileId
} }
oldDbIdMap.people[oldAuthor.id] = Person.id oldDbIdMap.people[oldAuthor.id] = Person.id
newRecords.Person.push(Person) newRecords.person.push(Person)
} }
} }
@ -757,7 +761,7 @@ function migrateSeries(oldSerieses) {
updatedAt: oldSeries.updatedAt || Date.now() updatedAt: oldSeries.updatedAt || Date.now()
} }
oldDbIdMap.series[oldSeries.id] = Series.id oldDbIdMap.series[oldSeries.id] = Series.id
newRecords.Series.push(Series) newRecords.series.push(Series)
} }
} }
@ -780,7 +784,7 @@ function migrateUsers(oldUsers) {
createdAt: oldUser.createdAt || Date.now() createdAt: oldUser.createdAt || Date.now()
} }
oldDbIdMap.users[oldUser.id] = User.id oldDbIdMap.users[oldUser.id] = User.id
newRecords.User.push(User) newRecords.user.push(User)
// //
// Migrate UserPermissions // Migrate UserPermissions
@ -792,9 +796,9 @@ function migrateUsers(oldUsers) {
key: oldUserPermission, key: oldUserPermission,
value: !!oldUser.permissions[oldUserPermission], value: !!oldUser.permissions[oldUserPermission],
createdAt: User.createdAt, createdAt: User.createdAt,
UserId: User.id userId: User.id
} }
newRecords.UserPermission.push(UserPermission) newRecords.userPermission.push(UserPermission)
} }
} }
if (oldUser.librariesAccessible?.length) { if (oldUser.librariesAccessible?.length) {
@ -803,9 +807,9 @@ function migrateUsers(oldUsers) {
key: 'librariesAccessible', key: 'librariesAccessible',
value: JSON.stringify(oldUser.librariesAccessible), value: JSON.stringify(oldUser.librariesAccessible),
createdAt: User.createdAt, createdAt: User.createdAt,
UserId: User.id userId: User.id
} }
newRecords.UserPermission.push(UserPermission) newRecords.userPermission.push(UserPermission)
} }
if (oldUser.itemTagsAccessible?.length) { if (oldUser.itemTagsAccessible?.length) {
const UserPermission = { const UserPermission = {
@ -813,32 +817,32 @@ function migrateUsers(oldUsers) {
key: 'itemTagsAccessible', key: 'itemTagsAccessible',
value: JSON.stringify(oldUser.itemTagsAccessible), value: JSON.stringify(oldUser.itemTagsAccessible),
createdAt: User.createdAt, createdAt: User.createdAt,
UserId: User.id userId: User.id
} }
newRecords.UserPermission.push(UserPermission) newRecords.userPermission.push(UserPermission)
} }
// //
// Migrate MediaProgress // Migrate MediaProgress
// //
for (const oldMediaProgress of oldUser.mediaProgress) { for (const oldMediaProgress of oldUser.mediaProgress) {
let mediaItemType = 'Book' let mediaItemType = 'book'
let MediaItemId = null let mediaItemId = null
if (oldMediaProgress.episodeId) { if (oldMediaProgress.episodeId) {
mediaItemType = 'PodcastEpisode' mediaItemType = 'podcastEpisode'
MediaItemId = oldDbIdMap.podcastEpisodes[oldMediaProgress.episodeId] mediaItemId = oldDbIdMap.podcastEpisodes[oldMediaProgress.episodeId]
} else { } else {
MediaItemId = oldDbIdMap.books[oldMediaProgress.libraryItemId] mediaItemId = oldDbIdMap.books[oldMediaProgress.libraryItemId]
} }
if (!MediaItemId) { if (!mediaItemId) {
Logger.warn(`[dbMigration] migrateUsers: Unable to find media item for media progress "${oldMediaProgress.id}"`) Logger.warn(`[dbMigration] migrateUsers: Unable to find media item for media progress "${oldMediaProgress.id}"`)
continue continue
} }
const MediaProgress = { const MediaProgress = {
id: uuidv4(), id: uuidv4(),
MediaItemId, mediaItemId,
mediaItemType, mediaItemType,
duration: oldMediaProgress.duration, duration: oldMediaProgress.duration,
currentTime: oldMediaProgress.currentTime, currentTime: oldMediaProgress.currentTime,
@ -847,49 +851,49 @@ function migrateUsers(oldUsers) {
finishedAt: oldMediaProgress.finishedAt, finishedAt: oldMediaProgress.finishedAt,
createdAt: oldMediaProgress.startedAt || oldMediaProgress.lastUpdate, createdAt: oldMediaProgress.startedAt || oldMediaProgress.lastUpdate,
updatedAt: oldMediaProgress.lastUpdate, updatedAt: oldMediaProgress.lastUpdate,
UserId: User.id userId: User.id
} }
newRecords.MediaProgress.push(MediaProgress) newRecords.mediaProgress.push(MediaProgress)
} }
// //
// Migrate AudioBookmarks // Migrate AudioBookmarks
// //
for (const oldBookmark of oldUser.bookmarks) { for (const oldBookmark of oldUser.bookmarks) {
const MediaItemId = oldDbIdMap.books[oldBookmark.libraryItemId] const mediaItemId = oldDbIdMap.books[oldBookmark.libraryItemId]
if (!MediaItemId) { if (!mediaItemId) {
Logger.warn(`[dbMigration] migrateUsers: Unable to find media item for audio bookmark "${oldBookmark.id}"`) Logger.warn(`[dbMigration] migrateUsers: Unable to find media item for audio bookmark "${oldBookmark.id}"`)
continue continue
} }
const AudioBookmark = { const AudioBookmark = {
id: uuidv4(), id: uuidv4(),
MediaItemId, mediaItemId,
mediaItemType: 'Book', mediaItemType: 'book',
title: oldBookmark.title, title: oldBookmark.title,
time: oldBookmark.time, time: oldBookmark.time,
createdAt: oldBookmark.createdAt, createdAt: oldBookmark.createdAt,
updatedAt: oldBookmark.createdAt, updatedAt: oldBookmark.createdAt,
UserId: User.id userId: User.id
} }
newRecords.AudioBookmark.push(AudioBookmark) newRecords.audioBookmark.push(AudioBookmark)
} }
} }
} }
function migrateSessions(oldSessions) { function migrateSessions(oldSessions) {
for (const oldSession of oldSessions) { for (const oldSession of oldSessions) {
const UserId = oldDbIdMap.users[oldSession.userId] || null // Can be null const userId = oldDbIdMap.users[oldSession.userId] || null // Can be null
// //
// Migrate Device // Migrate Device
// //
let DeviceId = null let deviceId = null
if (oldSession.deviceInfo) { if (oldSession.deviceInfo) {
const oldDeviceInfo = oldSession.deviceInfo const oldDeviceInfo = oldSession.deviceInfo
const deviceInfoStr = getDeviceInfoString(oldDeviceInfo, UserId) const deviceInfoStr = getDeviceInfoString(oldDeviceInfo, userId)
DeviceId = oldDbIdMap.devices[deviceInfoStr] deviceId = oldDbIdMap.devices[deviceInfoStr]
if (!DeviceId) { if (!deviceId) {
let clientName = 'Unknown' let clientName = 'Unknown'
let clientVersion = null let clientVersion = null
let deviceName = null let deviceName = null
@ -918,9 +922,9 @@ function migrateSessions(oldSessions) {
ipAddress: oldDeviceInfo.ipAddress, ipAddress: oldDeviceInfo.ipAddress,
deviceName, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3 deviceName, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3
deviceVersion, deviceVersion,
UserId userId
} }
newRecords.Device.push(Device) newRecords.device.push(Device)
oldDbIdMap.devices[deviceInfoStr] = Device.id oldDbIdMap.devices[deviceInfoStr] = Device.id
} }
} }
@ -929,18 +933,18 @@ function migrateSessions(oldSessions) {
// //
// Migrate PlaybackSession // Migrate PlaybackSession
// //
let MediaItemId = null let mediaItemId = null
let mediaItemType = 'Book' let mediaItemType = 'book'
if (oldSession.mediaType === 'podcast') { if (oldSession.mediaType === 'podcast') {
MediaItemId = oldDbIdMap.podcastEpisodes[oldSession.episodeId] || null mediaItemId = oldDbIdMap.podcastEpisodes[oldSession.episodeId] || null
mediaItemType = 'PodcastEpisode' mediaItemType = 'podcastEpisode'
} else { } else {
MediaItemId = oldDbIdMap.books[oldSession.libraryItemId] || null mediaItemId = oldDbIdMap.books[oldSession.libraryItemId] || null
} }
const PlaybackSession = { const PlaybackSession = {
id: uuidv4(), id: uuidv4(),
MediaItemId, // Can be null mediaItemId, // Can be null
mediaItemType, mediaItemType,
displayTitle: oldSession.displayTitle, displayTitle: oldSession.displayTitle,
displayAuthor: oldSession.displayAuthor, displayAuthor: oldSession.displayAuthor,
@ -952,10 +956,10 @@ function migrateSessions(oldSessions) {
serverVersion: oldSession.deviceInfo?.serverVersion || null, serverVersion: oldSession.deviceInfo?.serverVersion || null,
createdAt: oldSession.startedAt, createdAt: oldSession.startedAt,
updatedAt: oldSession.updatedAt, updatedAt: oldSession.updatedAt,
UserId, // Can be null userId, // Can be null
DeviceId deviceId
} }
newRecords.PlaybackSession.push(PlaybackSession) newRecords.playbackSession.push(PlaybackSession)
if (oldSession.timeListening) { if (oldSession.timeListening) {
const PlaybackSessionListenTime = { const PlaybackSessionListenTime = {
@ -964,17 +968,17 @@ function migrateSessions(oldSessions) {
date: oldSession.date || dateAndTime.format(new Date(PlaybackSession.createdAt), 'YYYY-MM-DD'), date: oldSession.date || dateAndTime.format(new Date(PlaybackSession.createdAt), 'YYYY-MM-DD'),
createdAt: PlaybackSession.createdAt, createdAt: PlaybackSession.createdAt,
updatedAt: PlaybackSession.updatedAt, updatedAt: PlaybackSession.updatedAt,
PlaybackSessionId: PlaybackSession.id playbackSessionId: PlaybackSession.id
} }
newRecords.PlaybackSessionListenTime.push(PlaybackSessionListenTime) newRecords.playbackSessionListenTime.push(PlaybackSessionListenTime)
} }
} }
} }
function migrateCollections(oldCollections) { function migrateCollections(oldCollections) {
for (const oldCollection of oldCollections) { for (const oldCollection of oldCollections) {
const LibraryId = oldDbIdMap.libraries[oldCollection.libraryId] const libraryId = oldDbIdMap.libraries[oldCollection.libraryId]
if (!LibraryId) { if (!libraryId) {
Logger.warn(`[dbMigration] migrateCollections: Library not found for collection "${oldCollection.name}" (id:${oldCollection.libraryId})`) Logger.warn(`[dbMigration] migrateCollections: Library not found for collection "${oldCollection.name}" (id:${oldCollection.libraryId})`)
continue continue
} }
@ -991,42 +995,42 @@ function migrateCollections(oldCollections) {
description: oldCollection.description, description: oldCollection.description,
createdAt: oldCollection.createdAt, createdAt: oldCollection.createdAt,
updatedAt: oldCollection.lastUpdate, updatedAt: oldCollection.lastUpdate,
LibraryId libraryId
} }
oldDbIdMap.collections[oldCollection.id] = Collection.id oldDbIdMap.collections[oldCollection.id] = Collection.id
newRecords.Collection.push(Collection) newRecords.collection.push(Collection)
BookIds.forEach((BookId) => { BookIds.forEach((bookId) => {
const CollectionBook = { const CollectionBook = {
id: uuidv4(), id: uuidv4(),
createdAt: Collection.createdAt, createdAt: Collection.createdAt,
BookId, bookId,
CollectionId: Collection.id collectionId: Collection.id
} }
newRecords.CollectionBook.push(CollectionBook) newRecords.collectionBook.push(CollectionBook)
}) })
} }
} }
function migratePlaylists(oldPlaylists) { function migratePlaylists(oldPlaylists) {
for (const oldPlaylist of oldPlaylists) { for (const oldPlaylist of oldPlaylists) {
const LibraryId = oldDbIdMap.libraries[oldPlaylist.libraryId] const libraryId = oldDbIdMap.libraries[oldPlaylist.libraryId]
if (!LibraryId) { if (!libraryId) {
Logger.warn(`[dbMigration] migratePlaylists: Library not found for playlist "${oldPlaylist.name}" (id:${oldPlaylist.libraryId})`) Logger.warn(`[dbMigration] migratePlaylists: Library not found for playlist "${oldPlaylist.name}" (id:${oldPlaylist.libraryId})`)
continue continue
} }
const UserId = oldDbIdMap.users[oldPlaylist.userId] const userId = oldDbIdMap.users[oldPlaylist.userId]
if (!UserId) { if (!userId) {
Logger.warn(`[dbMigration] migratePlaylists: User not found for playlist "${oldPlaylist.name}" (id:${oldPlaylist.userId})`) Logger.warn(`[dbMigration] migratePlaylists: User not found for playlist "${oldPlaylist.name}" (id:${oldPlaylist.userId})`)
continue continue
} }
let mediaItemType = 'Book' let mediaItemType = 'book'
let MediaItemIds = [] let MediaItemIds = []
oldPlaylist.items.forEach((itemObj) => { oldPlaylist.items.forEach((itemObj) => {
if (itemObj.episodeId) { if (itemObj.episodeId) {
mediaItemType = 'PodcastEpisode' mediaItemType = 'podcastEpisode'
if (oldDbIdMap.podcastEpisodes[itemObj.episodeId]) { if (oldDbIdMap.podcastEpisodes[itemObj.episodeId]) {
MediaItemIds.push(oldDbIdMap.podcastEpisodes[itemObj.episodeId]) MediaItemIds.push(oldDbIdMap.podcastEpisodes[itemObj.episodeId])
} }
@ -1045,20 +1049,20 @@ function migratePlaylists(oldPlaylists) {
description: oldPlaylist.description, description: oldPlaylist.description,
createdAt: oldPlaylist.createdAt, createdAt: oldPlaylist.createdAt,
updatedAt: oldPlaylist.lastUpdate, updatedAt: oldPlaylist.lastUpdate,
UserId, userId,
LibraryId libraryId
} }
newRecords.Playlist.push(Playlist) newRecords.playlist.push(Playlist)
MediaItemIds.forEach((MediaItemId) => { MediaItemIds.forEach((mediaItemId) => {
const PlaylistMediaItem = { const PlaylistMediaItem = {
id: uuidv4(), id: uuidv4(),
MediaItemId, mediaItemId,
mediaItemType, mediaItemType,
createdAt: Playlist.createdAt, createdAt: Playlist.createdAt,
PlaylistId: Playlist.id playlistId: Playlist.id
} }
newRecords.PlaylistMediaItem.push(PlaylistMediaItem) newRecords.playlistMediaItem.push(PlaylistMediaItem)
}) })
} }
} }
@ -1069,27 +1073,23 @@ function migrateFeeds(oldFeeds) {
continue continue
} }
let entityType = null let entityId = null
let EntityId = null
if (oldFeed.entityType === 'collection') { if (oldFeed.entityType === 'collection') {
entityType = 'Collection' entityId = oldDbIdMap.collections[oldFeed.entityId]
EntityId = oldDbIdMap.collections[oldFeed.entityId]
} else if (oldFeed.entityType === 'libraryItem') { } else if (oldFeed.entityType === 'libraryItem') {
entityType = 'LibraryItem' entityId = oldDbIdMap.libraryItems[oldFeed.entityId]
EntityId = oldDbIdMap.libraryItems[oldFeed.entityId]
} else if (oldFeed.entityType === 'series') { } else if (oldFeed.entityType === 'series') {
entityType = 'Series' entityId = oldDbIdMap.series[oldFeed.entityId]
EntityId = oldDbIdMap.series[oldFeed.entityId]
} }
if (!EntityId) { if (!entityId) {
Logger.warn(`[dbMigration] migrateFeeds: Entity not found for feed "${entityType}" (id:${oldFeed.entityId})`) Logger.warn(`[dbMigration] migrateFeeds: Entity not found for feed "${oldFeed.entityType}" (id:${oldFeed.entityId})`)
continue continue
} }
const UserId = oldDbIdMap.users[oldFeed.userId] const userId = oldDbIdMap.users[oldFeed.userId]
if (!UserId) { if (!userId) {
Logger.warn(`[dbMigration] migrateFeeds: User not found for feed (id:${oldFeed.userId})`) Logger.warn(`[dbMigration] migrateFeeds: User not found for feed (id:${oldFeed.userId})`)
continue continue
} }
@ -1099,8 +1099,8 @@ function migrateFeeds(oldFeeds) {
const Feed = { const Feed = {
id: uuidv4(), id: uuidv4(),
slug: oldFeed.slug, slug: oldFeed.slug,
entityType, entityType: oldFeed.entityType,
EntityId, entityId,
entityUpdatedAt: oldFeed.entityUpdatedAt, entityUpdatedAt: oldFeed.entityUpdatedAt,
serverAddress: oldFeed.serverAddress, serverAddress: oldFeed.serverAddress,
feedURL: oldFeed.feedUrl, feedURL: oldFeed.feedUrl,
@ -1117,9 +1117,9 @@ function migrateFeeds(oldFeeds) {
preventIndexing: !!oldFeedMeta.preventIndexing, preventIndexing: !!oldFeedMeta.preventIndexing,
createdAt: oldFeed.createdAt, createdAt: oldFeed.createdAt,
updatedAt: oldFeed.updatedAt, updatedAt: oldFeed.updatedAt,
UserId userId
} }
newRecords.Feed.push(Feed) newRecords.feed.push(Feed)
// //
// Migrate FeedEpisodes // Migrate FeedEpisodes
@ -1143,9 +1143,9 @@ function migrateFeeds(oldFeeds) {
explicit: !!oldFeedEpisode.explicit, explicit: !!oldFeedEpisode.explicit,
createdAt: oldFeed.createdAt, createdAt: oldFeed.createdAt,
updatedAt: oldFeed.updatedAt, updatedAt: oldFeed.updatedAt,
FeedId: Feed.id feedId: Feed.id
} }
newRecords.FeedEpisode.push(FeedEpisode) newRecords.feedEpisode.push(FeedEpisode)
} }
} }
} }
@ -1162,7 +1162,7 @@ function migrateSettings(oldSettings) {
if (value === undefined) value = null if (value === undefined) value = null
else if (serverSettingsKey === 'sortingPrefixes') value = JSON.stringify(value) else if (serverSettingsKey === 'sortingPrefixes') value = JSON.stringify(value)
newRecords.Setting.push({ newRecords.setting.push({
key: serverSettingsKey, key: serverSettingsKey,
value, value,
type: 0 type: 0
@ -1178,7 +1178,7 @@ function migrateSettings(oldSettings) {
notificationDelay: notificationSettings.notificationDelay ?? 1000 // ms delay between firing notifications notificationDelay: notificationSettings.notificationDelay ?? 1000 // ms delay between firing notifications
} }
for (const notificationSettingKey in cleanedCopy) { for (const notificationSettingKey in cleanedCopy) {
newRecords.Setting.push({ newRecords.setting.push({
key: notificationSettingKey, key: notificationSettingKey,
value: cleanedCopy[notificationSettingKey], value: cleanedCopy[notificationSettingKey],
type: 1 type: 1
@ -1206,7 +1206,7 @@ function migrateSettings(oldSettings) {
createdAt: oldNotification.createdAt, createdAt: oldNotification.createdAt,
updatedAt: oldNotification.createdAt updatedAt: oldNotification.createdAt
} }
newRecords.Notification.push(Notification) newRecords.notification.push(Notification)
} }
} }
} }