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 || {}
}
async init() {
async init(force = false) {
if (!await this.connect()) {
throw new Error('Database connection failed')
}
await this.buildModels()
await this.buildModels(force)
Logger.info(`[Database] Db initialized`, Object.keys(this.sequelize.models))
}
@ -30,6 +30,9 @@ class Database {
logging: false
})
// Helper function
this.sequelize.uppercaseFirst = str => str ? `${str[0].toUpperCase()}${str.substr(1)}` : ''
try {
await this.sequelize.authenticate()
Logger.info(`[Database] Db connection was successful`)
@ -40,15 +43,15 @@ class Database {
}
}
buildModels() {
buildModels(force = false) {
require('./models/User')(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/Book')(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/MediaProgress')(this.sequelize)
require('./models/LibraryFile')(this.sequelize)
@ -82,24 +85,7 @@ class Database {
require('./models/Notification')(this.sequelize)
require('./models/UserPermission')(this.sequelize)
return this.sequelize.sync({ force: true })
}
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())
}
return this.sequelize.sync({ force })
}
}

View File

@ -22,6 +22,7 @@ const Database = require('./Database')
const SocketAuthority = require('./SocketAuthority')
const ApiRouter = require('./routers/ApiRouter')
const ApiRouter2 = require('./routers/ApiRouter2')
const HlsRouter = require('./routers/HlsRouter')
const StaticRouter = require('./routers/StaticRouter')
@ -84,6 +85,7 @@ class Server {
// Routers
this.apiRouter = new ApiRouter(this)
this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager)
this.staticRouter = new StaticRouter(this.db)
@ -102,9 +104,9 @@ class Server {
await this.playbackSessionManager.removeOrphanStreams()
// TODO: Test new db connection
await Database.init()
// await Database.createTestUser()
await dbMigration3.migrate()
const force = true
await Database.init(force)
if (force) await dbMigration3.migrate()
const previousVersion = await this.db.checkPreviousVersion() // Returns null if same server version
if (previousVersion) {
@ -169,6 +171,7 @@ class Server {
// Static folder
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('/hls', this.authMiddleware.bind(this), this.hlsRouter.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 {
getMediaItem(options) {
if (!this.mediaItemType) return Promise.resolve(null)
const mixinMethodName = `get${this.mediaItemType}`
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
return this[mixinMethodName](options)
}
}
@ -19,52 +19,57 @@ module.exports = (sequelize) => {
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
MediaItemId: DataTypes.UUIDV4,
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
title: DataTypes.STRING,
time: DataTypes.INTEGER
}, {
sequelize,
modelName: 'AudioBookmark'
modelName: 'audioBookmark'
})
const { User, Book, PodcastEpisode } = sequelize.models
Book.hasMany(AudioBookmark, {
foreignKey: 'MediaItemId',
const { user, book, podcastEpisode } = sequelize.models
book.hasMany(AudioBookmark, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'Book'
mediaItemType: 'book'
}
})
AudioBookmark.belongsTo(Book, { foreignKey: 'MediaItemId', constraints: false })
AudioBookmark.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
PodcastEpisode.hasMany(AudioBookmark, {
foreignKey: 'MediaItemId',
podcastEpisode.hasMany(AudioBookmark, {
foreignKey: 'mediaItemId',
constraints: false,
scope: {
mediaItemType: 'PodcastEpisode'
mediaItemType: 'podcastEpisode'
}
})
AudioBookmark.belongsTo(PodcastEpisode, { foreignKey: 'MediaItemId', constraints: false })
AudioBookmark.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
AudioBookmark.addHook('afterFind', findResult => {
if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult]
for (const instance of findResult) {
if (instance.mediaItemType === 'Book' && instance.Book !== undefined) {
instance.MediaItem = instance.Book
} else if (instance.mediaItemType === 'PodcastEpisode' && instance.PodcastEpisode !== undefined) {
instance.MediaItem = instance.PodcastEpisode
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
instance.mediaItem = instance.book
instance.dataValues.mediaItem = instance.dataValues.book
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
instance.mediaItem = instance.podcastEpisode
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
}
// To prevent mistakes:
delete instance.Book
delete instance.dataValues.Book
delete instance.PodcastEpisode
delete instance.dataValues.PodcastEpisode
delete instance.book
delete instance.dataValues.book
delete instance.podcastEpisode
delete instance.dataValues.podcastEpisode
}
})
User.hasMany(AudioBookmark)
AudioBookmark.belongsTo(User)
user.hasMany(AudioBookmark)
AudioBookmark.belongsTo(user)
return AudioBookmark
}

View File

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

View File

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

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
}
}, {
sequelize,
modelName: 'BookAuthor',
modelName: 'bookAuthor',
timestamps: false
})
// 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
const { Book, Person } = sequelize.models
Book.belongsToMany(Person, { through: BookAuthor })
Person.belongsToMany(Book, { through: BookAuthor })
const { book, person } = sequelize.models
book.belongsToMany(person, { through: BookAuthor, as: 'authors', otherKey: 'authorId' })
person.belongsToMany(book, { through: BookAuthor, foreignKey: 'authorId' })
Book.hasMany(BookAuthor)
BookAuthor.belongsTo(Book)
book.hasMany(BookAuthor)
BookAuthor.belongsTo(book)
Person.hasMany(BookAuthor)
BookAuthor.belongsTo(Person)
person.hasMany(BookAuthor, { foreignKey: 'authorId' })
BookAuthor.belongsTo(person, { as: 'author', foreignKey: 'authorId' })
return BookAuthor
}

View File

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

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
}
}, {
sequelize,
modelName: 'BookGenre',
modelName: 'bookGenre',
timestamps: false
})
// 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
const { Book, Genre } = sequelize.models
Book.belongsToMany(Genre, { through: BookGenre })
Genre.belongsToMany(Book, { through: BookGenre })
const { book, genre } = sequelize.models
book.belongsToMany(genre, { through: BookGenre })
genre.belongsToMany(book, { through: BookGenre })
Book.hasMany(BookGenre)
BookGenre.belongsTo(Book)
book.hasMany(BookGenre)
BookGenre.belongsTo(book)
Genre.hasMany(BookGenre)
BookGenre.belongsTo(Genre)
genre.hasMany(BookGenre)
BookGenre.belongsTo(genre)
return BookGenre
}

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
}
}, {
sequelize,
modelName: 'BookNarrator',
modelName: 'bookNarrator',
timestamps: false
})
// 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
const { Book, Person } = sequelize.models
Book.belongsToMany(Person, { through: BookNarrator })
Person.belongsToMany(Book, { through: BookNarrator })
const { book, person } = sequelize.models
book.belongsToMany(person, { through: BookNarrator, as: 'narrators', otherKey: 'narratorId' })
person.belongsToMany(book, { through: BookNarrator, foreignKey: 'narratorId' })
Book.hasMany(BookNarrator)
BookNarrator.belongsTo(Book)
book.hasMany(BookNarrator)
BookNarrator.belongsTo(book)
Person.hasMany(BookNarrator)
BookNarrator.belongsTo(Person)
person.hasMany(BookNarrator, { foreignKey: 'narratorId' })
BookNarrator.belongsTo(person, { as: 'narrator', foreignKey: 'narratorId' })
return BookNarrator
}

View File

@ -12,21 +12,21 @@ module.exports = (sequelize) => {
sequence: DataTypes.STRING
}, {
sequelize,
modelName: 'BookSeries',
modelName: 'bookSeries',
timestamps: false
})
// 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
const { Book, Series } = sequelize.models
Book.belongsToMany(Series, { through: BookSeries })
Series.belongsToMany(Book, { through: BookSeries })
const { book, series } = sequelize.models
book.belongsToMany(series, { through: BookSeries })
series.belongsToMany(book, { through: BookSeries })
Book.hasMany(BookSeries)
BookSeries.belongsTo(Book)
book.hasMany(BookSeries)
BookSeries.belongsTo(book)
Series.hasMany(BookSeries)
BookSeries.belongsTo(Series)
series.hasMany(BookSeries)
BookSeries.belongsTo(series)
return BookSeries
}

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
}
}, {
sequelize,
modelName: 'BookTag',
modelName: 'bookTag',
timestamps: false
})
// 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
const { Book, Tag } = sequelize.models
Book.belongsToMany(Tag, { through: BookTag })
Tag.belongsToMany(Book, { through: BookTag })
const { book, tag } = sequelize.models
book.belongsToMany(tag, { through: BookTag })
tag.belongsToMany(book, { through: BookTag })
Book.hasMany(BookTag)
BookTag.belongsTo(Book)
book.hasMany(BookTag)
BookTag.belongsTo(book)
Tag.hasMany(BookTag)
BookTag.belongsTo(Tag)
tag.hasMany(BookTag)
BookTag.belongsTo(tag)
return BookTag
}

View File

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

View File

@ -13,20 +13,20 @@ module.exports = (sequelize) => {
sequelize,
timestamps: true,
updatedAt: false,
modelName: 'CollectionBook'
modelName: 'collectionBook'
})
// 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
const { Book, Collection } = sequelize.models
Book.belongsToMany(Collection, { through: CollectionBook })
Collection.belongsToMany(Book, { through: CollectionBook })
const { book, collection } = sequelize.models
book.belongsToMany(collection, { through: CollectionBook })
collection.belongsToMany(book, { through: CollectionBook })
Book.hasMany(CollectionBook)
CollectionBook.belongsTo(Book)
book.hasMany(CollectionBook)
CollectionBook.belongsTo(book)
Collection.hasMany(CollectionBook)
CollectionBook.belongsTo(Collection)
collection.hasMany(CollectionBook)
CollectionBook.belongsTo(collection)
return CollectionBook
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,13 @@
const { DataTypes, Model } = require('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({
id: {
@ -12,6 +18,7 @@ module.exports = (sequelize) => {
ino: DataTypes.STRING,
path: DataTypes.STRING,
relPath: DataTypes.STRING,
mediaId: DataTypes.UUIDV4,
mediaType: DataTypes.STRING,
isFile: DataTypes.BOOLEAN,
isMissing: DataTypes.BOOLEAN,
@ -23,12 +30,50 @@ module.exports = (sequelize) => {
lastScanVersion: DataTypes.STRING
}, {
sequelize,
modelName: 'LibraryItem'
modelName: 'libraryItem'
})
const { LibraryFolder } = sequelize.models
LibraryFolder.hasMany(LibraryItem)
LibraryItem.belongsTo(LibraryFolder)
const { libraryFolder, book, podcast } = sequelize.models
libraryFolder.hasMany(LibraryItem)
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
}
}, {
sequelize,
modelName: 'PodcastGenre',
modelName: 'podcastGenre',
timestamps: false
})
// 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
const { Podcast, Genre } = sequelize.models
Podcast.belongsToMany(Genre, { through: PodcastGenre })
Genre.belongsToMany(Podcast, { through: PodcastGenre })
const { podcast, genre } = sequelize.models
podcast.belongsToMany(genre, { through: PodcastGenre })
genre.belongsToMany(podcast, { through: PodcastGenre })
Podcast.hasMany(PodcastGenre)
PodcastGenre.belongsTo(Podcast)
podcast.hasMany(PodcastGenre)
PodcastGenre.belongsTo(podcast)
Genre.hasMany(PodcastGenre)
PodcastGenre.belongsTo(Genre)
genre.hasMany(PodcastGenre)
PodcastGenre.belongsTo(genre)
return PodcastGenre
}

View File

@ -11,21 +11,21 @@ module.exports = (sequelize) => {
}
}, {
sequelize,
modelName: 'PodcastTag',
modelName: 'podcastTag',
timestamps: false
})
// 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
const { Podcast, Tag } = sequelize.models
Podcast.belongsToMany(Tag, { through: PodcastTag })
Tag.belongsToMany(Podcast, { through: PodcastTag })
const { podcast, tag } = sequelize.models
podcast.belongsToMany(tag, { through: PodcastTag })
tag.belongsToMany(podcast, { through: PodcastTag })
Podcast.hasMany(PodcastTag)
PodcastTag.belongsTo(Podcast)
podcast.hasMany(PodcastTag)
PodcastTag.belongsTo(podcast)
Tag.hasMany(PodcastTag)
PodcastTag.belongsTo(Tag)
tag.hasMany(PodcastTag)
PodcastTag.belongsTo(tag)
return PodcastTag
}

View File

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

View File

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

View File

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

View File

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

View File

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