mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-03 17:54:54 +02:00
Init sqlite take 2
This commit is contained in:
parent
d86a3b3dc2
commit
cf7fd315b6
88 changed files with 7017 additions and 692 deletions
78
server/models/Author.js
Normal file
78
server/models/Author.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
const oldAuthor = require('../objects/entities/Author')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Author extends Model {
|
||||
static async getOldAuthors() {
|
||||
const authors = await this.findAll()
|
||||
return authors.map(au => au.getOldAuthor())
|
||||
}
|
||||
|
||||
getOldAuthor() {
|
||||
return new oldAuthor({
|
||||
id: this.id,
|
||||
asin: this.asin,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
imagePath: this.imagePath,
|
||||
addedAt: this.createdAt.valueOf(),
|
||||
updatedAt: this.updatedAt.valueOf()
|
||||
})
|
||||
}
|
||||
|
||||
static updateFromOld(oldAuthor) {
|
||||
const author = this.getFromOld(oldAuthor)
|
||||
return this.update(author, {
|
||||
where: {
|
||||
id: author.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static createFromOld(oldAuthor) {
|
||||
const author = this.getFromOld(oldAuthor)
|
||||
return this.create(author)
|
||||
}
|
||||
|
||||
static createBulkFromOld(oldAuthors) {
|
||||
const authors = oldAuthors.map(this.getFromOld)
|
||||
return this.bulkCreate(authors)
|
||||
}
|
||||
|
||||
static getFromOld(oldAuthor) {
|
||||
return {
|
||||
id: oldAuthor.id,
|
||||
name: oldAuthor.name,
|
||||
asin: oldAuthor.asin,
|
||||
description: oldAuthor.description,
|
||||
imagePath: oldAuthor.imagePath
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(authorId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: authorId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Author.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: DataTypes.STRING,
|
||||
asin: DataTypes.STRING,
|
||||
description: DataTypes.TEXT,
|
||||
imagePath: DataTypes.STRING
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'author'
|
||||
})
|
||||
|
||||
return Author
|
||||
}
|
121
server/models/Book.js
Normal file
121
server/models/Book.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
const Logger = require('../Logger')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Book extends Model {
|
||||
static getOldBook(libraryItemExpanded) {
|
||||
const bookExpanded = libraryItemExpanded.media
|
||||
const authors = bookExpanded.authors.map(au => {
|
||||
return {
|
||||
id: au.id,
|
||||
name: au.name
|
||||
}
|
||||
})
|
||||
const series = bookExpanded.series.map(se => {
|
||||
return {
|
||||
id: se.id,
|
||||
name: se.name,
|
||||
sequence: se.bookSeries.sequence
|
||||
}
|
||||
})
|
||||
return {
|
||||
id: bookExpanded.id,
|
||||
libraryItemId: libraryItemExpanded.id,
|
||||
coverPath: bookExpanded.coverPath,
|
||||
tags: bookExpanded.tags,
|
||||
audioFiles: bookExpanded.audioFiles,
|
||||
chapters: bookExpanded.chapters,
|
||||
ebookFile: bookExpanded.ebookFile,
|
||||
metadata: {
|
||||
title: bookExpanded.title,
|
||||
subtitle: bookExpanded.subtitle,
|
||||
authors: authors,
|
||||
narrators: bookExpanded.narrators,
|
||||
series: series,
|
||||
genres: bookExpanded.genres,
|
||||
publishedYear: bookExpanded.publishedYear,
|
||||
publishedDate: bookExpanded.publishedDate,
|
||||
publisher: bookExpanded.publisher,
|
||||
description: bookExpanded.description,
|
||||
isbn: bookExpanded.isbn,
|
||||
asin: bookExpanded.asin,
|
||||
language: bookExpanded.language,
|
||||
explicit: bookExpanded.explicit,
|
||||
abridged: bookExpanded.abridged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} oldBook
|
||||
* @returns {boolean} true if updated
|
||||
*/
|
||||
static saveFromOld(oldBook) {
|
||||
const book = this.getFromOld(oldBook)
|
||||
return this.update(book, {
|
||||
where: {
|
||||
id: book.id
|
||||
}
|
||||
}).then(result => result[0] > 0).catch((error) => {
|
||||
Logger.error(`[Book] Failed to save book ${book.id}`, error)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
static getFromOld(oldBook) {
|
||||
return {
|
||||
id: oldBook.id,
|
||||
title: oldBook.metadata.title,
|
||||
subtitle: oldBook.metadata.subtitle,
|
||||
publishedYear: oldBook.metadata.publishedYear,
|
||||
publishedDate: oldBook.metadata.publishedDate,
|
||||
publisher: oldBook.metadata.publisher,
|
||||
description: oldBook.metadata.description,
|
||||
isbn: oldBook.metadata.isbn,
|
||||
asin: oldBook.metadata.asin,
|
||||
language: oldBook.metadata.language,
|
||||
explicit: !!oldBook.metadata.explicit,
|
||||
abridged: !!oldBook.metadata.abridged,
|
||||
narrators: oldBook.metadata.narrators,
|
||||
ebookFile: oldBook.ebookFile?.toJSON() || null,
|
||||
coverPath: oldBook.coverPath,
|
||||
audioFiles: oldBook.audioFiles?.map(af => af.toJSON()) || [],
|
||||
chapters: oldBook.chapters,
|
||||
tags: oldBook.tags,
|
||||
genres: oldBook.metadata.genres
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Book.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
title: DataTypes.STRING,
|
||||
subtitle: DataTypes.STRING,
|
||||
publishedYear: DataTypes.STRING,
|
||||
publishedDate: DataTypes.STRING,
|
||||
publisher: DataTypes.STRING,
|
||||
description: DataTypes.TEXT,
|
||||
isbn: DataTypes.STRING,
|
||||
asin: DataTypes.STRING,
|
||||
language: DataTypes.STRING,
|
||||
explicit: DataTypes.BOOLEAN,
|
||||
abridged: DataTypes.BOOLEAN,
|
||||
coverPath: DataTypes.STRING,
|
||||
|
||||
narrators: DataTypes.JSON,
|
||||
audioFiles: DataTypes.JSON,
|
||||
ebookFile: DataTypes.JSON,
|
||||
chapters: DataTypes.JSON,
|
||||
tags: DataTypes.JSON,
|
||||
genres: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'book'
|
||||
})
|
||||
|
||||
return Book
|
||||
}
|
40
server/models/BookAuthor.js
Normal file
40
server/models/BookAuthor.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class BookAuthor extends Model {
|
||||
static removeByIds(authorId = null, bookId = null) {
|
||||
const where = {}
|
||||
if (authorId) where.authorId = authorId
|
||||
if (bookId) where.bookId = bookId
|
||||
return this.destroy({
|
||||
where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
BookAuthor.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
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, author } = sequelize.models
|
||||
book.belongsToMany(author, { through: BookAuthor })
|
||||
author.belongsToMany(book, { through: BookAuthor })
|
||||
|
||||
book.hasMany(BookAuthor)
|
||||
BookAuthor.belongsTo(book)
|
||||
|
||||
author.hasMany(BookAuthor)
|
||||
BookAuthor.belongsTo(author)
|
||||
|
||||
return BookAuthor
|
||||
}
|
41
server/models/BookSeries.js
Normal file
41
server/models/BookSeries.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class BookSeries extends Model {
|
||||
static removeByIds(seriesId = null, bookId = null) {
|
||||
const where = {}
|
||||
if (seriesId) where.seriesId = seriesId
|
||||
if (bookId) where.bookId = bookId
|
||||
return this.destroy({
|
||||
where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
BookSeries.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
sequence: DataTypes.STRING
|
||||
}, {
|
||||
sequelize,
|
||||
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 })
|
||||
|
||||
book.hasMany(BookSeries)
|
||||
BookSeries.belongsTo(book)
|
||||
|
||||
series.hasMany(BookSeries)
|
||||
BookSeries.belongsTo(series)
|
||||
|
||||
return BookSeries
|
||||
}
|
117
server/models/Collection.js
Normal file
117
server/models/Collection.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
const oldCollection = require('../objects/Collection')
|
||||
const { areEquivalent } = require('../utils/index')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Collection extends Model {
|
||||
static async getOldCollections() {
|
||||
const collections = await this.findAll({
|
||||
include: {
|
||||
model: sequelize.models.book,
|
||||
include: sequelize.models.libraryItem
|
||||
}
|
||||
})
|
||||
return collections.map(c => this.getOldCollection(c))
|
||||
}
|
||||
|
||||
static getOldCollection(collectionExpanded) {
|
||||
const libraryItemIds = collectionExpanded.books?.map(b => b.libraryItem?.id || null).filter(lid => lid) || []
|
||||
return new oldCollection({
|
||||
id: collectionExpanded.id,
|
||||
libraryId: collectionExpanded.libraryId,
|
||||
name: collectionExpanded.name,
|
||||
description: collectionExpanded.description,
|
||||
books: libraryItemIds,
|
||||
lastUpdate: collectionExpanded.updatedAt.valueOf(),
|
||||
createdAt: collectionExpanded.createdAt.valueOf()
|
||||
})
|
||||
}
|
||||
|
||||
static createFromOld(oldCollection) {
|
||||
const collection = this.getFromOld(oldCollection)
|
||||
return this.create(collection)
|
||||
}
|
||||
|
||||
static async fullUpdateFromOld(oldCollection, collectionBooks) {
|
||||
const existingCollection = await this.findByPk(oldCollection.id, {
|
||||
include: sequelize.models.collectionBook
|
||||
})
|
||||
if (!existingCollection) return false
|
||||
|
||||
let hasUpdates = false
|
||||
const collection = this.getFromOld(oldCollection)
|
||||
|
||||
for (const cb of collectionBooks) {
|
||||
const existingCb = existingCollection.collectionBooks.find(i => i.bookId === cb.bookId)
|
||||
if (!existingCb) {
|
||||
await sequelize.models.collectionBook.create(cb)
|
||||
hasUpdates = true
|
||||
} else if (existingCb.order != cb.order) {
|
||||
await existingCb.update({ order: cb.order })
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
for (const cb of existingCollection.collectionBooks) {
|
||||
// collectionBook was removed
|
||||
if (!collectionBooks.some(i => i.bookId === cb.bookId)) {
|
||||
await cb.destroy()
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
let hasCollectionUpdates = false
|
||||
for (const key in collection) {
|
||||
let existingValue = existingCollection[key]
|
||||
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
||||
if (!areEquivalent(collection[key], existingValue)) {
|
||||
hasCollectionUpdates = true
|
||||
}
|
||||
}
|
||||
if (hasCollectionUpdates) {
|
||||
existingCollection.update(collection)
|
||||
hasUpdates = true
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
static getFromOld(oldCollection) {
|
||||
return {
|
||||
id: oldCollection.id,
|
||||
name: oldCollection.name,
|
||||
description: oldCollection.description,
|
||||
createdAt: oldCollection.createdAt,
|
||||
updatedAt: oldCollection.lastUpdate,
|
||||
libraryId: oldCollection.libraryId
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(collectionId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: collectionId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Collection.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: DataTypes.STRING,
|
||||
description: DataTypes.TEXT
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'collection'
|
||||
})
|
||||
|
||||
const { library } = sequelize.models
|
||||
|
||||
library.hasMany(Collection)
|
||||
Collection.belongsTo(library)
|
||||
|
||||
return Collection
|
||||
}
|
42
server/models/CollectionBook.js
Normal file
42
server/models/CollectionBook.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class CollectionBook extends Model {
|
||||
static removeByIds(collectionId, bookId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
bookId,
|
||||
collectionId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
CollectionBook.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
order: DataTypes.INTEGER
|
||||
}, {
|
||||
sequelize,
|
||||
timestamps: true,
|
||||
updatedAt: false,
|
||||
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 })
|
||||
|
||||
book.hasMany(CollectionBook)
|
||||
CollectionBook.belongsTo(book)
|
||||
|
||||
collection.hasMany(CollectionBook)
|
||||
CollectionBook.belongsTo(collection)
|
||||
|
||||
return CollectionBook
|
||||
}
|
112
server/models/Device.js
Normal file
112
server/models/Device.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
const oldDevice = require('../objects/DeviceInfo')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Device extends Model {
|
||||
getOldDevice() {
|
||||
let browserVersion = null
|
||||
let sdkVersion = null
|
||||
if (this.clientName === 'Abs Android') {
|
||||
sdkVersion = this.deviceVersion || null
|
||||
} else {
|
||||
browserVersion = this.deviceVersion || null
|
||||
}
|
||||
|
||||
return new oldDevice({
|
||||
id: this.id,
|
||||
deviceId: this.deviceId,
|
||||
userId: this.userId,
|
||||
ipAddress: this.ipAddress,
|
||||
browserName: this.extraData.browserName || null,
|
||||
browserVersion,
|
||||
osName: this.extraData.osName || null,
|
||||
osVersion: this.extraData.osVersion || null,
|
||||
clientVersion: this.clientVersion || null,
|
||||
manufacturer: this.extraData.manufacturer || null,
|
||||
model: this.extraData.model || null,
|
||||
sdkVersion,
|
||||
deviceName: this.deviceName,
|
||||
clientName: this.clientName
|
||||
})
|
||||
}
|
||||
|
||||
static async getOldDeviceByDeviceId(deviceId) {
|
||||
const device = await this.findOne({
|
||||
deviceId
|
||||
})
|
||||
if (!device) return null
|
||||
return device.getOldDevice()
|
||||
}
|
||||
|
||||
static createFromOld(oldDevice) {
|
||||
const device = this.getFromOld(oldDevice)
|
||||
return this.create(device)
|
||||
}
|
||||
|
||||
static updateFromOld(oldDevice) {
|
||||
const device = this.getFromOld(oldDevice)
|
||||
return this.update(device, {
|
||||
where: {
|
||||
id: device.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static getFromOld(oldDeviceInfo) {
|
||||
let extraData = {}
|
||||
|
||||
if (oldDeviceInfo.manufacturer) {
|
||||
extraData.manufacturer = oldDeviceInfo.manufacturer
|
||||
}
|
||||
if (oldDeviceInfo.model) {
|
||||
extraData.model = oldDeviceInfo.model
|
||||
}
|
||||
if (oldDeviceInfo.osName) {
|
||||
extraData.osName = oldDeviceInfo.osName
|
||||
}
|
||||
if (oldDeviceInfo.osVersion) {
|
||||
extraData.osVersion = oldDeviceInfo.osVersion
|
||||
}
|
||||
if (oldDeviceInfo.browserName) {
|
||||
extraData.browserName = oldDeviceInfo.browserName
|
||||
}
|
||||
|
||||
return {
|
||||
id: oldDeviceInfo.id,
|
||||
deviceId: oldDeviceInfo.deviceId,
|
||||
clientName: oldDeviceInfo.clientName || null,
|
||||
clientVersion: oldDeviceInfo.clientVersion || null,
|
||||
ipAddress: oldDeviceInfo.ipAddress,
|
||||
deviceName: oldDeviceInfo.deviceName || null,
|
||||
deviceVersion: oldDeviceInfo.sdkVersion || oldDeviceInfo.browserVersion || null,
|
||||
userId: oldDeviceInfo.userId,
|
||||
extraData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Device.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
deviceId: DataTypes.STRING,
|
||||
clientName: DataTypes.STRING, // e.g. Abs Web, Abs Android
|
||||
clientVersion: DataTypes.STRING, // e.g. Server version or mobile version
|
||||
ipAddress: DataTypes.STRING,
|
||||
deviceName: DataTypes.STRING, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3
|
||||
deviceVersion: DataTypes.STRING, // e.g. Browser version or Android SDK
|
||||
extraData: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'device'
|
||||
})
|
||||
|
||||
const { user } = sequelize.models
|
||||
|
||||
user.hasMany(Device)
|
||||
Device.belongsTo(user)
|
||||
|
||||
return Device
|
||||
}
|
165
server/models/Feed.js
Normal file
165
server/models/Feed.js
Normal file
|
@ -0,0 +1,165 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
const oldFeed = require('../objects/Feed')
|
||||
/*
|
||||
* Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
|
||||
* Feeds can be created from LibraryItem, Collection, Playlist or Series
|
||||
*/
|
||||
module.exports = (sequelize) => {
|
||||
class Feed extends Model {
|
||||
static async getOldFeeds() {
|
||||
const feeds = await this.findAll({
|
||||
include: {
|
||||
model: sequelize.models.feedEpisode
|
||||
}
|
||||
})
|
||||
return feeds.map(f => this.getOldFeed(f))
|
||||
}
|
||||
|
||||
static getOldFeed(feedExpanded) {
|
||||
const episodes = feedExpanded.feedEpisodes.map((feedEpisode) => feedEpisode.getOldEpisode())
|
||||
|
||||
return new oldFeed({
|
||||
id: feedExpanded.id,
|
||||
slug: feedExpanded.slug,
|
||||
userId: feedExpanded.userId,
|
||||
entityType: feedExpanded.entityType,
|
||||
entityId: feedExpanded.entityId,
|
||||
meta: {
|
||||
title: feedExpanded.title,
|
||||
description: feedExpanded.description,
|
||||
author: feedExpanded.author,
|
||||
imageUrl: feedExpanded.imageURL,
|
||||
feedUrl: feedExpanded.feedURL,
|
||||
link: feedExpanded.siteURL,
|
||||
explicit: feedExpanded.explicit,
|
||||
type: feedExpanded.podcastType,
|
||||
language: feedExpanded.language,
|
||||
preventIndexing: feedExpanded.preventIndexing,
|
||||
ownerName: feedExpanded.ownerName,
|
||||
ownerEmail: feedExpanded.ownerEmail
|
||||
},
|
||||
serverAddress: feedExpanded.serverAddress,
|
||||
feedUrl: feedExpanded.feedURL,
|
||||
episodes,
|
||||
createdAt: feedExpanded.createdAt.valueOf(),
|
||||
updatedAt: feedExpanded.updatedAt.valueOf()
|
||||
})
|
||||
}
|
||||
|
||||
static removeById(feedId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: feedId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getEntity(options) {
|
||||
if (!this.entityType) return Promise.resolve(null)
|
||||
const mixinMethodName = `get${sequelize.uppercaseFirst(this.entityType)}`
|
||||
return this[mixinMethodName](options)
|
||||
}
|
||||
}
|
||||
|
||||
Feed.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
slug: DataTypes.STRING,
|
||||
entityType: DataTypes.STRING,
|
||||
entityId: DataTypes.UUIDV4,
|
||||
entityUpdatedAt: DataTypes.DATE,
|
||||
serverAddress: DataTypes.STRING,
|
||||
feedURL: DataTypes.STRING,
|
||||
imageURL: DataTypes.STRING,
|
||||
siteURL: DataTypes.STRING,
|
||||
title: DataTypes.STRING,
|
||||
description: DataTypes.TEXT,
|
||||
author: DataTypes.STRING,
|
||||
podcastType: DataTypes.STRING,
|
||||
language: DataTypes.STRING,
|
||||
ownerName: DataTypes.STRING,
|
||||
ownerEmail: DataTypes.STRING,
|
||||
explicit: DataTypes.BOOLEAN,
|
||||
preventIndexing: DataTypes.BOOLEAN
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'feed'
|
||||
})
|
||||
|
||||
const { user, libraryItem, collection, series, playlist } = sequelize.models
|
||||
|
||||
user.hasMany(Feed)
|
||||
Feed.belongsTo(user)
|
||||
|
||||
libraryItem.hasMany(Feed, {
|
||||
foreignKey: 'entityId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
entityType: 'libraryItem'
|
||||
}
|
||||
})
|
||||
Feed.belongsTo(libraryItem, { foreignKey: 'entityId', constraints: false })
|
||||
|
||||
collection.hasMany(Feed, {
|
||||
foreignKey: 'entityId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
entityType: 'collection'
|
||||
}
|
||||
})
|
||||
Feed.belongsTo(collection, { foreignKey: 'entityId', constraints: false })
|
||||
|
||||
series.hasMany(Feed, {
|
||||
foreignKey: 'entityId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
entityType: 'series'
|
||||
}
|
||||
})
|
||||
Feed.belongsTo(series, { foreignKey: 'entityId', constraints: false })
|
||||
|
||||
playlist.hasMany(Feed, {
|
||||
foreignKey: 'entityId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
entityType: 'playlist'
|
||||
}
|
||||
})
|
||||
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
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
return Feed
|
||||
}
|
60
server/models/FeedEpisode.js
Normal file
60
server/models/FeedEpisode.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class FeedEpisode extends Model {
|
||||
getOldEpisode() {
|
||||
const enclosure = {
|
||||
url: this.enclosureURL,
|
||||
size: this.enclosureSize,
|
||||
type: this.enclosureType
|
||||
}
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
enclosure,
|
||||
pubDate: this.pubDate,
|
||||
link: this.siteURL,
|
||||
author: this.author,
|
||||
explicit: this.explicit,
|
||||
duration: this.duration,
|
||||
season: this.season,
|
||||
episode: this.episode,
|
||||
episodeType: this.episodeType,
|
||||
fullPath: this.filePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FeedEpisode.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
title: DataTypes.STRING,
|
||||
author: DataTypes.STRING,
|
||||
description: DataTypes.TEXT,
|
||||
siteURL: DataTypes.STRING,
|
||||
enclosureURL: DataTypes.STRING,
|
||||
enclosureType: DataTypes.STRING,
|
||||
enclosureSize: DataTypes.BIGINT,
|
||||
pubDate: DataTypes.STRING,
|
||||
season: DataTypes.STRING,
|
||||
episode: DataTypes.STRING,
|
||||
episodeType: DataTypes.STRING,
|
||||
duration: DataTypes.FLOAT,
|
||||
filePath: DataTypes.STRING,
|
||||
explicit: DataTypes.BOOLEAN
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'feedEpisode'
|
||||
})
|
||||
|
||||
const { feed } = sequelize.models
|
||||
|
||||
feed.hasMany(FeedEpisode)
|
||||
FeedEpisode.belongsTo(feed)
|
||||
|
||||
return FeedEpisode
|
||||
}
|
137
server/models/Library.js
Normal file
137
server/models/Library.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
const Logger = require('../Logger')
|
||||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
const oldLibrary = require('../objects/Library')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Library extends Model {
|
||||
static async getAllOldLibraries() {
|
||||
const libraries = await this.findAll({
|
||||
include: sequelize.models.libraryFolder
|
||||
})
|
||||
return libraries.map(lib => this.getOldLibrary(lib))
|
||||
}
|
||||
|
||||
static getOldLibrary(libraryExpanded) {
|
||||
const folders = libraryExpanded.libraryFolders.map(folder => {
|
||||
return {
|
||||
id: folder.id,
|
||||
fullPath: folder.path,
|
||||
libraryId: folder.libraryId,
|
||||
addedAt: folder.createdAt.valueOf()
|
||||
}
|
||||
})
|
||||
return new oldLibrary({
|
||||
id: libraryExpanded.id,
|
||||
name: libraryExpanded.name,
|
||||
folders,
|
||||
displayOrder: libraryExpanded.displayOrder,
|
||||
icon: libraryExpanded.icon,
|
||||
mediaType: libraryExpanded.mediaType,
|
||||
provider: libraryExpanded.provider,
|
||||
settings: libraryExpanded.settings,
|
||||
createdAt: libraryExpanded.createdAt.valueOf(),
|
||||
lastUpdate: libraryExpanded.updatedAt.valueOf()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} oldLibrary
|
||||
* @returns {Library|null}
|
||||
*/
|
||||
static createFromOld(oldLibrary) {
|
||||
const library = this.getFromOld(oldLibrary)
|
||||
|
||||
library.libraryFolders = oldLibrary.folders.map(folder => {
|
||||
return {
|
||||
id: folder.id,
|
||||
path: folder.fullPath,
|
||||
libraryId: library.id
|
||||
}
|
||||
})
|
||||
|
||||
return this.create(library).catch((error) => {
|
||||
Logger.error(`[Library] Failed to create library ${library.id}`, error)
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
static async updateFromOld(oldLibrary) {
|
||||
const existingLibrary = await this.findByPk(oldLibrary.id, {
|
||||
include: sequelize.models.libraryFolder
|
||||
})
|
||||
if (!existingLibrary) {
|
||||
Logger.error(`[Library] Failed to update library ${oldLibrary.id} - not found`)
|
||||
return null
|
||||
}
|
||||
|
||||
const library = this.getFromOld(oldLibrary)
|
||||
|
||||
const libraryFolders = oldLibrary.folders.map(folder => {
|
||||
return {
|
||||
id: folder.id,
|
||||
path: folder.fullPath,
|
||||
libraryId: library.id
|
||||
}
|
||||
})
|
||||
for (const libraryFolder of libraryFolders) {
|
||||
const existingLibraryFolder = existingLibrary.libraryFolders.find(lf => lf.id === libraryFolder.id)
|
||||
if (!existingLibraryFolder) {
|
||||
await sequelize.models.libraryFolder.create(libraryFolder)
|
||||
} else if (existingLibraryFolder.path !== libraryFolder.path) {
|
||||
await existingLibraryFolder.update({ path: libraryFolder.path })
|
||||
}
|
||||
}
|
||||
|
||||
const libraryFoldersRemoved = existingLibrary.libraryFolders.filter(lf => !libraryFolders.some(_lf => _lf.id === lf.id))
|
||||
for (const existingLibraryFolder of libraryFoldersRemoved) {
|
||||
await existingLibraryFolder.destroy()
|
||||
}
|
||||
|
||||
return existingLibrary.update(library)
|
||||
}
|
||||
|
||||
static getFromOld(oldLibrary) {
|
||||
return {
|
||||
id: oldLibrary.id,
|
||||
name: oldLibrary.name,
|
||||
displayOrder: oldLibrary.displayOrder,
|
||||
icon: oldLibrary.icon || null,
|
||||
mediaType: oldLibrary.mediaType || null,
|
||||
provider: oldLibrary.provider,
|
||||
settings: oldLibrary.settings?.toJSON() || {},
|
||||
createdAt: oldLibrary.createdAt,
|
||||
updatedAt: oldLibrary.lastUpdate
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(libraryId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: libraryId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Library.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: DataTypes.STRING,
|
||||
displayOrder: DataTypes.INTEGER,
|
||||
icon: DataTypes.STRING,
|
||||
mediaType: DataTypes.STRING,
|
||||
provider: DataTypes.STRING,
|
||||
lastScan: DataTypes.DATE,
|
||||
lastScanVersion: DataTypes.STRING,
|
||||
settings: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'library'
|
||||
})
|
||||
|
||||
return Library
|
||||
}
|
23
server/models/LibraryFolder.js
Normal file
23
server/models/LibraryFolder.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class LibraryFolder extends Model { }
|
||||
|
||||
LibraryFolder.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
path: DataTypes.STRING
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'libraryFolder'
|
||||
})
|
||||
|
||||
const { library } = sequelize.models
|
||||
library.hasMany(LibraryFolder)
|
||||
LibraryFolder.belongsTo(library)
|
||||
|
||||
return LibraryFolder
|
||||
}
|
393
server/models/LibraryItem.js
Normal file
393
server/models/LibraryItem.js
Normal file
|
@ -0,0 +1,393 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
const Logger = require('../Logger')
|
||||
const oldLibraryItem = require('../objects/LibraryItem')
|
||||
const { areEquivalent } = require('../utils/index')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class LibraryItem extends Model {
|
||||
static async getAllOldLibraryItems() {
|
||||
let libraryItems = await this.findAll({
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.book,
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.author,
|
||||
through: {
|
||||
attributes: []
|
||||
}
|
||||
},
|
||||
{
|
||||
model: sequelize.models.series,
|
||||
through: {
|
||||
attributes: ['sequence']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: sequelize.models.podcast,
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.podcastEpisode
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
return libraryItems.map(ti => this.getOldLibraryItem(ti))
|
||||
}
|
||||
|
||||
static getOldLibraryItem(libraryItemExpanded) {
|
||||
let media = null
|
||||
if (libraryItemExpanded.mediaType === 'book') {
|
||||
media = sequelize.models.book.getOldBook(libraryItemExpanded)
|
||||
} else if (libraryItemExpanded.mediaType === 'podcast') {
|
||||
media = sequelize.models.podcast.getOldPodcast(libraryItemExpanded)
|
||||
}
|
||||
|
||||
return new oldLibraryItem({
|
||||
id: libraryItemExpanded.id,
|
||||
ino: libraryItemExpanded.ino,
|
||||
libraryId: libraryItemExpanded.libraryId,
|
||||
folderId: libraryItemExpanded.libraryFolderId,
|
||||
path: libraryItemExpanded.path,
|
||||
relPath: libraryItemExpanded.relPath,
|
||||
isFile: libraryItemExpanded.isFile,
|
||||
mtimeMs: libraryItemExpanded.mtime?.valueOf(),
|
||||
ctimeMs: libraryItemExpanded.ctime?.valueOf(),
|
||||
birthtimeMs: libraryItemExpanded.birthtime?.valueOf(),
|
||||
addedAt: libraryItemExpanded.createdAt.valueOf(),
|
||||
updatedAt: libraryItemExpanded.updatedAt.valueOf(),
|
||||
lastScan: libraryItemExpanded.lastScan?.valueOf(),
|
||||
scanVersion: libraryItemExpanded.lastScanVersion,
|
||||
isMissing: !!libraryItemExpanded.isMissing,
|
||||
isInvalid: !!libraryItemExpanded.isInvalid,
|
||||
mediaType: libraryItemExpanded.mediaType,
|
||||
media,
|
||||
libraryFiles: libraryItemExpanded.libraryFiles
|
||||
})
|
||||
}
|
||||
|
||||
static async fullCreateFromOld(oldLibraryItem) {
|
||||
const newLibraryItem = await this.create(this.getFromOld(oldLibraryItem))
|
||||
|
||||
if (oldLibraryItem.mediaType === 'book') {
|
||||
const bookObj = sequelize.models.book.getFromOld(oldLibraryItem.media)
|
||||
bookObj.libraryItemId = newLibraryItem.id
|
||||
const newBook = await sequelize.models.book.create(bookObj)
|
||||
|
||||
const oldBookAuthors = oldLibraryItem.media.metadata.authors || []
|
||||
const oldBookSeriesAll = oldLibraryItem.media.metadata.series || []
|
||||
|
||||
for (const oldBookAuthor of oldBookAuthors) {
|
||||
await sequelize.models.bookAuthor.create({ authorId: oldBookAuthor.id, bookId: newBook.id })
|
||||
}
|
||||
for (const oldSeries of oldBookSeriesAll) {
|
||||
await sequelize.models.bookSeries.create({ seriesId: oldSeries.id, bookId: newBook.id, sequence: oldSeries.sequence })
|
||||
}
|
||||
} else if (oldLibraryItem.mediaType === 'podcast') {
|
||||
const podcastObj = sequelize.models.podcast.getFromOld(oldLibraryItem.media)
|
||||
podcastObj.libraryItemId = newLibraryItem.id
|
||||
const newPodcast = await sequelize.models.podcast.create(podcastObj)
|
||||
|
||||
const oldEpisodes = oldLibraryItem.media.episodes || []
|
||||
for (const oldEpisode of oldEpisodes) {
|
||||
const episodeObj = sequelize.models.podcastEpisode.getFromOld(oldEpisode)
|
||||
episodeObj.libraryItemId = newLibraryItem.id
|
||||
episodeObj.podcastId = newPodcast.id
|
||||
await sequelize.models.podcastEpisode.create(episodeObj)
|
||||
}
|
||||
}
|
||||
|
||||
return newLibraryItem
|
||||
}
|
||||
|
||||
static async fullUpdateFromOld(oldLibraryItem) {
|
||||
const libraryItemExpanded = await this.findByPk(oldLibraryItem.id, {
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.book,
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.author,
|
||||
through: {
|
||||
attributes: []
|
||||
}
|
||||
},
|
||||
{
|
||||
model: sequelize.models.series,
|
||||
through: {
|
||||
attributes: ['sequence']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: sequelize.models.podcast,
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.podcastEpisode
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
if (!libraryItemExpanded) return false
|
||||
|
||||
let hasUpdates = false
|
||||
|
||||
// Check update Book/Podcast
|
||||
if (libraryItemExpanded.media) {
|
||||
let updatedMedia = null
|
||||
if (libraryItemExpanded.mediaType === 'podcast') {
|
||||
updatedMedia = sequelize.models.podcast.getFromOld(oldLibraryItem.media)
|
||||
|
||||
const existingPodcastEpisodes = libraryItemExpanded.media.podcastEpisodes || []
|
||||
const updatedPodcastEpisodes = oldLibraryItem.media.episodes || []
|
||||
|
||||
for (const existingPodcastEpisode of existingPodcastEpisodes) {
|
||||
// Episode was removed
|
||||
if (!updatedPodcastEpisodes.some(ep => ep.id === existingPodcastEpisode.id)) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${existingPodcastEpisode.title}" was removed`)
|
||||
await existingPodcastEpisode.destroy()
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
for (const updatedPodcastEpisode of updatedPodcastEpisodes) {
|
||||
const existingEpisodeMatch = existingPodcastEpisodes.find(ep => ep.id === updatedPodcastEpisode.id)
|
||||
if (!existingEpisodeMatch) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${updatedPodcastEpisode.title}" was added`)
|
||||
await sequelize.models.podcastEpisode.createFromOld(updatedPodcastEpisode)
|
||||
hasUpdates = true
|
||||
} else {
|
||||
const updatedEpisodeCleaned = sequelize.models.podcastEpisode.getFromOld(updatedPodcastEpisode)
|
||||
let episodeHasUpdates = false
|
||||
for (const key in updatedEpisodeCleaned) {
|
||||
let existingValue = existingEpisodeMatch[key]
|
||||
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
||||
|
||||
if (!areEquivalent(updatedEpisodeCleaned[key], existingValue, true)) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${existingEpisodeMatch.title}" ${key} was updated from "${existingValue}" to "${updatedEpisodeCleaned[key]}"`)
|
||||
episodeHasUpdates = true
|
||||
}
|
||||
}
|
||||
if (episodeHasUpdates) {
|
||||
await existingEpisodeMatch.update(updatedEpisodeCleaned)
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (libraryItemExpanded.mediaType === 'book') {
|
||||
updatedMedia = sequelize.models.book.getFromOld(oldLibraryItem.media)
|
||||
|
||||
const existingAuthors = libraryItemExpanded.media.authors || []
|
||||
const existingSeriesAll = libraryItemExpanded.media.series || []
|
||||
const updatedAuthors = oldLibraryItem.media.metadata.authors || []
|
||||
const updatedSeriesAll = oldLibraryItem.media.metadata.series || []
|
||||
|
||||
for (const existingAuthor of existingAuthors) {
|
||||
// Author was removed from Book
|
||||
if (!updatedAuthors.some(au => au.id === existingAuthor.id)) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${existingAuthor.name}" was removed`)
|
||||
await sequelize.models.bookAuthor.removeByIds(existingAuthor.id, libraryItemExpanded.media.id)
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
for (const updatedAuthor of updatedAuthors) {
|
||||
// Author was added
|
||||
if (!existingAuthors.some(au => au.id === updatedAuthor.id)) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${updatedAuthor.name}" was added`)
|
||||
await sequelize.models.bookAuthor.create({ authorId: updatedAuthor.id, bookId: libraryItemExpanded.media.id })
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
for (const existingSeries of existingSeriesAll) {
|
||||
// Series was removed
|
||||
if (!updatedSeriesAll.some(se => se.id === existingSeries.id)) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${existingSeries.name}" was removed`)
|
||||
await sequelize.models.bookSeries.removeByIds(existingSeries.id, libraryItemExpanded.media.id)
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
for (const updatedSeries of updatedSeriesAll) {
|
||||
// Series was added/updated
|
||||
const existingSeriesMatch = existingSeriesAll.find(se => se.id === updatedSeries.id)
|
||||
if (!existingSeriesMatch) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${updatedSeries.name}" was added`)
|
||||
await sequelize.models.bookSeries.create({ seriesId: updatedSeries.id, bookId: libraryItemExpanded.media.id, sequence: updatedSeries.sequence })
|
||||
hasUpdates = true
|
||||
} else if (existingSeriesMatch.bookSeries.sequence !== updatedSeries.sequence) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${updatedSeries.name}" sequence was updated from "${existingSeriesMatch.bookSeries.sequence}" to "${updatedSeries.sequence}"`)
|
||||
await existingSeriesMatch.bookSeries.update({ sequence: updatedSeries.sequence })
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hasMediaUpdates = false
|
||||
for (const key in updatedMedia) {
|
||||
let existingValue = libraryItemExpanded.media[key]
|
||||
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
||||
|
||||
if (!areEquivalent(updatedMedia[key], existingValue, true)) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" ${libraryItemExpanded.mediaType}.${key} updated from ${existingValue} to ${updatedMedia[key]}`)
|
||||
hasMediaUpdates = true
|
||||
}
|
||||
}
|
||||
if (hasMediaUpdates && updatedMedia) {
|
||||
await libraryItemExpanded.media.update(updatedMedia)
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
const updatedLibraryItem = this.getFromOld(oldLibraryItem)
|
||||
let hasLibraryItemUpdates = false
|
||||
for (const key in updatedLibraryItem) {
|
||||
let existingValue = libraryItemExpanded[key]
|
||||
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
||||
|
||||
if (!areEquivalent(updatedLibraryItem[key], existingValue, true)) {
|
||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" ${key} updated from ${existingValue} to ${updatedLibraryItem[key]}`)
|
||||
hasLibraryItemUpdates = true
|
||||
}
|
||||
}
|
||||
if (hasLibraryItemUpdates) {
|
||||
await libraryItemExpanded.update(updatedLibraryItem)
|
||||
hasUpdates = true
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
static updateFromOld(oldLibraryItem) {
|
||||
const libraryItem = this.getFromOld(oldLibraryItem)
|
||||
return this.update(libraryItem, {
|
||||
where: {
|
||||
id: libraryItem.id
|
||||
}
|
||||
}).then((result) => result[0] > 0).catch((error) => {
|
||||
Logger.error(`[LibraryItem] Failed to update libraryItem ${libraryItem.id}`, error)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
static getFromOld(oldLibraryItem) {
|
||||
return {
|
||||
id: oldLibraryItem.id,
|
||||
ino: oldLibraryItem.ino,
|
||||
path: oldLibraryItem.path,
|
||||
relPath: oldLibraryItem.relPath,
|
||||
mediaId: oldLibraryItem.media.id,
|
||||
mediaType: oldLibraryItem.mediaType,
|
||||
isFile: !!oldLibraryItem.isFile,
|
||||
isMissing: !!oldLibraryItem.isMissing,
|
||||
isInvalid: !!oldLibraryItem.isInvalid,
|
||||
mtime: oldLibraryItem.mtimeMs,
|
||||
ctime: oldLibraryItem.ctimeMs,
|
||||
birthtime: oldLibraryItem.birthtimeMs,
|
||||
lastScan: oldLibraryItem.lastScan,
|
||||
lastScanVersion: oldLibraryItem.scanVersion,
|
||||
createdAt: oldLibraryItem.addedAt,
|
||||
updatedAt: oldLibraryItem.updatedAt,
|
||||
libraryId: oldLibraryItem.libraryId,
|
||||
libraryFolderId: oldLibraryItem.folderId,
|
||||
libraryFiles: oldLibraryItem.libraryFiles?.map(lf => lf.toJSON()) || []
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(libraryItemId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: libraryItemId
|
||||
},
|
||||
individualHooks: true
|
||||
})
|
||||
}
|
||||
|
||||
getMedia(options) {
|
||||
if (!this.mediaType) return Promise.resolve(null)
|
||||
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaType)}`
|
||||
return this[mixinMethodName](options)
|
||||
}
|
||||
}
|
||||
|
||||
LibraryItem.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
ino: DataTypes.STRING,
|
||||
path: DataTypes.STRING,
|
||||
relPath: DataTypes.STRING,
|
||||
mediaId: DataTypes.UUIDV4,
|
||||
mediaType: DataTypes.STRING,
|
||||
isFile: DataTypes.BOOLEAN,
|
||||
isMissing: DataTypes.BOOLEAN,
|
||||
isInvalid: DataTypes.BOOLEAN,
|
||||
mtime: DataTypes.DATE(6),
|
||||
ctime: DataTypes.DATE(6),
|
||||
birthtime: DataTypes.DATE(6),
|
||||
lastScan: DataTypes.DATE,
|
||||
lastScanVersion: DataTypes.STRING,
|
||||
libraryFiles: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'libraryItem'
|
||||
})
|
||||
|
||||
const { library, libraryFolder, book, podcast } = sequelize.models
|
||||
library.hasMany(LibraryItem)
|
||||
LibraryItem.belongsTo(library)
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
LibraryItem.addHook('afterDestroy', async instance => {
|
||||
if (!instance) return
|
||||
const media = await instance.getMedia()
|
||||
if (media) {
|
||||
media.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
return LibraryItem
|
||||
}
|
141
server/models/MediaProgress.js
Normal file
141
server/models/MediaProgress.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
/*
|
||||
* Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
|
||||
* Book has many MediaProgress. PodcastEpisode has many MediaProgress.
|
||||
*/
|
||||
module.exports = (sequelize) => {
|
||||
class MediaProgress extends Model {
|
||||
getOldMediaProgress() {
|
||||
const isPodcastEpisode = this.mediaItemType === 'podcastEpisode'
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
userId: this.userId,
|
||||
libraryItemId: this.extraData?.libraryItemId || null,
|
||||
episodeId: isPodcastEpisode ? this.mediaItemId : null,
|
||||
mediaItemId: this.mediaItemId,
|
||||
mediaItemType: this.mediaItemType,
|
||||
duration: this.duration,
|
||||
progress: this.extraData?.progress || null,
|
||||
currentTime: this.currentTime,
|
||||
isFinished: !!this.isFinished,
|
||||
hideFromContinueListening: !!this.hideFromContinueListening,
|
||||
ebookLocation: this.ebookLocation,
|
||||
ebookProgress: this.ebookProgress,
|
||||
lastUpdate: this.updatedAt.valueOf(),
|
||||
startedAt: this.createdAt.valueOf(),
|
||||
finishedAt: this.finishedAt?.valueOf() || null
|
||||
}
|
||||
}
|
||||
|
||||
static upsertFromOld(oldMediaProgress) {
|
||||
const mediaProgress = this.getFromOld(oldMediaProgress)
|
||||
return this.upsert(mediaProgress)
|
||||
}
|
||||
|
||||
static getFromOld(oldMediaProgress) {
|
||||
return {
|
||||
id: oldMediaProgress.id,
|
||||
userId: oldMediaProgress.userId,
|
||||
mediaItemId: oldMediaProgress.mediaItemId,
|
||||
mediaItemType: oldMediaProgress.mediaItemType,
|
||||
duration: oldMediaProgress.duration,
|
||||
currentTime: oldMediaProgress.currentTime,
|
||||
ebookLocation: oldMediaProgress.ebookLocation || null,
|
||||
ebookProgress: oldMediaProgress.ebookProgress || null,
|
||||
isFinished: !!oldMediaProgress.isFinished,
|
||||
hideFromContinueListening: !!oldMediaProgress.hideFromContinueListening,
|
||||
finishedAt: oldMediaProgress.finishedAt,
|
||||
createdAt: oldMediaProgress.startedAt || oldMediaProgress.lastUpdate,
|
||||
updatedAt: oldMediaProgress.lastUpdate,
|
||||
extraData: {
|
||||
libraryItemId: oldMediaProgress.libraryItemId,
|
||||
progress: oldMediaProgress.progress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(mediaProgressId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: mediaProgressId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getMediaItem(options) {
|
||||
if (!this.mediaItemType) return Promise.resolve(null)
|
||||
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
|
||||
return this[mixinMethodName](options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MediaProgress.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
mediaItemId: DataTypes.UUIDV4,
|
||||
mediaItemType: DataTypes.STRING,
|
||||
duration: DataTypes.FLOAT,
|
||||
currentTime: DataTypes.FLOAT,
|
||||
isFinished: DataTypes.BOOLEAN,
|
||||
hideFromContinueListening: DataTypes.BOOLEAN,
|
||||
ebookLocation: DataTypes.STRING,
|
||||
ebookProgress: DataTypes.FLOAT,
|
||||
finishedAt: DataTypes.DATE,
|
||||
extraData: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'mediaProgress'
|
||||
})
|
||||
|
||||
const { book, podcastEpisode, user } = sequelize.models
|
||||
|
||||
book.hasMany(MediaProgress, {
|
||||
foreignKey: 'mediaItemId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
mediaItemType: 'book'
|
||||
}
|
||||
})
|
||||
MediaProgress.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
|
||||
|
||||
podcastEpisode.hasMany(MediaProgress, {
|
||||
foreignKey: 'mediaItemId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
mediaItemType: 'podcastEpisode'
|
||||
}
|
||||
})
|
||||
MediaProgress.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
|
||||
|
||||
MediaProgress.addHook('afterFind', findResult => {
|
||||
if (!findResult) return
|
||||
|
||||
if (!Array.isArray(findResult)) findResult = [findResult]
|
||||
|
||||
for (const instance of findResult) {
|
||||
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
|
||||
instance.mediaItem = instance.book
|
||||
instance.dataValues.mediaItem = instance.dataValues.book
|
||||
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
|
||||
instance.mediaItem = instance.podcastEpisode
|
||||
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
|
||||
}
|
||||
// To prevent mistakes:
|
||||
delete instance.book
|
||||
delete instance.dataValues.book
|
||||
delete instance.podcastEpisode
|
||||
delete instance.dataValues.podcastEpisode
|
||||
}
|
||||
})
|
||||
|
||||
user.hasMany(MediaProgress)
|
||||
MediaProgress.belongsTo(user)
|
||||
|
||||
return MediaProgress
|
||||
}
|
198
server/models/PlaybackSession.js
Normal file
198
server/models/PlaybackSession.js
Normal file
|
@ -0,0 +1,198 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
const oldPlaybackSession = require('../objects/PlaybackSession')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class PlaybackSession extends Model {
|
||||
static async getOldPlaybackSessions(where = null) {
|
||||
const playbackSessions = await this.findAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.device
|
||||
}
|
||||
]
|
||||
})
|
||||
return playbackSessions.map(session => this.getOldPlaybackSession(session))
|
||||
}
|
||||
|
||||
static async getById(sessionId) {
|
||||
const playbackSession = await this.findByPk(sessionId, {
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.device
|
||||
}
|
||||
]
|
||||
})
|
||||
if (!playbackSession) return null
|
||||
return this.getOldPlaybackSession(playbackSession)
|
||||
}
|
||||
|
||||
static getOldPlaybackSession(playbackSessionExpanded) {
|
||||
const isPodcastEpisode = playbackSessionExpanded.mediaItemType === 'podcastEpisode'
|
||||
|
||||
return new oldPlaybackSession({
|
||||
id: playbackSessionExpanded.id,
|
||||
userId: playbackSessionExpanded.userId,
|
||||
libraryId: playbackSessionExpanded.libraryId,
|
||||
libraryItemId: playbackSessionExpanded.extraData?.libraryItemId || null,
|
||||
bookId: isPodcastEpisode ? null : playbackSessionExpanded.mediaItemId,
|
||||
episodeId: isPodcastEpisode ? playbackSessionExpanded.mediaItemId : null,
|
||||
mediaType: isPodcastEpisode ? 'podcast' : 'book',
|
||||
mediaMetadata: playbackSessionExpanded.mediaMetadata,
|
||||
chapters: null,
|
||||
displayTitle: playbackSessionExpanded.displayTitle,
|
||||
displayAuthor: playbackSessionExpanded.displayAuthor,
|
||||
coverPath: playbackSessionExpanded.coverPath,
|
||||
duration: playbackSessionExpanded.duration,
|
||||
playMethod: playbackSessionExpanded.playMethod,
|
||||
mediaPlayer: playbackSessionExpanded.mediaPlayer,
|
||||
deviceInfo: playbackSessionExpanded.device?.getOldDevice() || null,
|
||||
serverVersion: playbackSessionExpanded.serverVersion,
|
||||
date: playbackSessionExpanded.date,
|
||||
dayOfWeek: playbackSessionExpanded.dayOfWeek,
|
||||
timeListening: playbackSessionExpanded.timeListening,
|
||||
startTime: playbackSessionExpanded.startTime,
|
||||
currentTime: playbackSessionExpanded.currentTime,
|
||||
startedAt: playbackSessionExpanded.createdAt.valueOf(),
|
||||
updatedAt: playbackSessionExpanded.updatedAt.valueOf()
|
||||
})
|
||||
}
|
||||
|
||||
static removeById(sessionId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: sessionId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static createFromOld(oldPlaybackSession) {
|
||||
const playbackSession = this.getFromOld(oldPlaybackSession)
|
||||
return this.create(playbackSession)
|
||||
}
|
||||
|
||||
static updateFromOld(oldPlaybackSession) {
|
||||
const playbackSession = this.getFromOld(oldPlaybackSession)
|
||||
return this.update(playbackSession, {
|
||||
where: {
|
||||
id: playbackSession.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static getFromOld(oldPlaybackSession) {
|
||||
return {
|
||||
id: oldPlaybackSession.id,
|
||||
mediaItemId: oldPlaybackSession.episodeId || oldPlaybackSession.bookId,
|
||||
mediaItemType: oldPlaybackSession.episodeId ? 'podcastEpisode' : 'book',
|
||||
libraryId: oldPlaybackSession.libraryId,
|
||||
displayTitle: oldPlaybackSession.displayTitle,
|
||||
displayAuthor: oldPlaybackSession.displayAuthor,
|
||||
duration: oldPlaybackSession.duration,
|
||||
playMethod: oldPlaybackSession.playMethod,
|
||||
mediaPlayer: oldPlaybackSession.mediaPlayer,
|
||||
startTime: oldPlaybackSession.startTime,
|
||||
currentTime: oldPlaybackSession.currentTime,
|
||||
serverVersion: oldPlaybackSession.serverVersion || null,
|
||||
createdAt: oldPlaybackSession.startedAt,
|
||||
updatedAt: oldPlaybackSession.updatedAt,
|
||||
userId: oldPlaybackSession.userId,
|
||||
deviceId: oldPlaybackSession.deviceInfo?.id || null,
|
||||
timeListening: oldPlaybackSession.timeListening,
|
||||
coverPath: oldPlaybackSession.coverPath,
|
||||
mediaMetadata: oldPlaybackSession.mediaMetadata,
|
||||
date: oldPlaybackSession.date,
|
||||
dayOfWeek: oldPlaybackSession.dayOfWeek,
|
||||
extraData: {
|
||||
libraryItemId: oldPlaybackSession.libraryItemId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getMediaItem(options) {
|
||||
if (!this.mediaItemType) return Promise.resolve(null)
|
||||
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
|
||||
return this[mixinMethodName](options)
|
||||
}
|
||||
}
|
||||
|
||||
PlaybackSession.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
mediaItemId: DataTypes.UUIDV4,
|
||||
mediaItemType: DataTypes.STRING,
|
||||
displayTitle: DataTypes.STRING,
|
||||
displayAuthor: DataTypes.STRING,
|
||||
duration: DataTypes.FLOAT,
|
||||
playMethod: DataTypes.INTEGER,
|
||||
mediaPlayer: DataTypes.STRING,
|
||||
startTime: DataTypes.FLOAT,
|
||||
currentTime: DataTypes.FLOAT,
|
||||
serverVersion: DataTypes.STRING,
|
||||
coverPath: DataTypes.STRING,
|
||||
timeListening: DataTypes.INTEGER,
|
||||
mediaMetadata: DataTypes.JSON,
|
||||
date: DataTypes.STRING,
|
||||
dayOfWeek: DataTypes.STRING,
|
||||
extraData: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'playbackSession'
|
||||
})
|
||||
|
||||
const { book, podcastEpisode, user, device, library } = sequelize.models
|
||||
|
||||
user.hasMany(PlaybackSession)
|
||||
PlaybackSession.belongsTo(user)
|
||||
|
||||
device.hasMany(PlaybackSession)
|
||||
PlaybackSession.belongsTo(device)
|
||||
|
||||
library.hasMany(PlaybackSession)
|
||||
PlaybackSession.belongsTo(library)
|
||||
|
||||
book.hasMany(PlaybackSession, {
|
||||
foreignKey: 'mediaItemId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
mediaItemType: 'book'
|
||||
}
|
||||
})
|
||||
PlaybackSession.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
|
||||
|
||||
podcastEpisode.hasOne(PlaybackSession, {
|
||||
foreignKey: 'mediaItemId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
mediaItemType: 'podcastEpisode'
|
||||
}
|
||||
})
|
||||
PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
|
||||
|
||||
PlaybackSession.addHook('afterFind', findResult => {
|
||||
if (!findResult) return
|
||||
|
||||
if (!Array.isArray(findResult)) findResult = [findResult]
|
||||
|
||||
for (const instance of findResult) {
|
||||
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
|
||||
instance.mediaItem = instance.book
|
||||
instance.dataValues.mediaItem = instance.dataValues.book
|
||||
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
|
||||
instance.mediaItem = instance.podcastEpisode
|
||||
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
|
||||
}
|
||||
// To prevent mistakes:
|
||||
delete instance.book
|
||||
delete instance.dataValues.book
|
||||
delete instance.podcastEpisode
|
||||
delete instance.dataValues.podcastEpisode
|
||||
}
|
||||
})
|
||||
|
||||
return PlaybackSession
|
||||
}
|
171
server/models/Playlist.js
Normal file
171
server/models/Playlist.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
const oldPlaylist = require('../objects/Playlist')
|
||||
const { areEquivalent } = require('../utils/index')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Playlist extends Model {
|
||||
static async getOldPlaylists() {
|
||||
const playlists = await this.findAll({
|
||||
include: {
|
||||
model: sequelize.models.playlistMediaItem,
|
||||
include: [
|
||||
{
|
||||
model: sequelize.models.book,
|
||||
include: sequelize.models.libraryItem
|
||||
},
|
||||
{
|
||||
model: sequelize.models.podcastEpisode,
|
||||
include: {
|
||||
model: sequelize.models.podcast,
|
||||
include: sequelize.models.libraryItem
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
return playlists.map(p => this.getOldPlaylist(p))
|
||||
}
|
||||
|
||||
static getOldPlaylist(playlistExpanded) {
|
||||
const items = playlistExpanded.playlistMediaItems.map(pmi => {
|
||||
const libraryItemId = pmi.mediaItem?.podcast?.libraryItem?.id || pmi.mediaItem?.libraryItem?.id || null
|
||||
if (!libraryItemId) {
|
||||
console.log(JSON.stringify(pmi, null, 2))
|
||||
throw new Error('No library item id')
|
||||
}
|
||||
return {
|
||||
episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '',
|
||||
libraryItemId: libraryItemId
|
||||
}
|
||||
})
|
||||
return new oldPlaylist({
|
||||
id: playlistExpanded.id,
|
||||
libraryId: playlistExpanded.libraryId,
|
||||
userId: playlistExpanded.userId,
|
||||
name: playlistExpanded.name,
|
||||
description: playlistExpanded.description,
|
||||
items,
|
||||
lastUpdate: playlistExpanded.updatedAt.valueOf(),
|
||||
createdAt: playlistExpanded.createdAt.valueOf()
|
||||
})
|
||||
}
|
||||
|
||||
static createFromOld(oldPlaylist) {
|
||||
const playlist = this.getFromOld(oldPlaylist)
|
||||
return this.create(playlist)
|
||||
}
|
||||
|
||||
static async fullUpdateFromOld(oldPlaylist, playlistMediaItems) {
|
||||
const existingPlaylist = await this.findByPk(oldPlaylist.id, {
|
||||
include: sequelize.models.playlistMediaItem
|
||||
})
|
||||
if (!existingPlaylist) return false
|
||||
|
||||
let hasUpdates = false
|
||||
const playlist = this.getFromOld(oldPlaylist)
|
||||
|
||||
for (const pmi of playlistMediaItems) {
|
||||
const existingPmi = existingPlaylist.playlistMediaItems.find(i => i.mediaItemId === pmi.mediaItemId)
|
||||
if (!existingPmi) {
|
||||
await sequelize.models.playlistMediaItem.create(pmi)
|
||||
hasUpdates = true
|
||||
} else if (existingPmi.order != pmi.order) {
|
||||
await existingPmi.update({ order: pmi.order })
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
for (const pmi of existingPlaylist.playlistMediaItems) {
|
||||
// Pmi was removed
|
||||
if (!playlistMediaItems.some(i => i.mediaItemId === pmi.mediaItemId)) {
|
||||
await pmi.destroy()
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
let hasPlaylistUpdates = false
|
||||
for (const key in playlist) {
|
||||
let existingValue = existingPlaylist[key]
|
||||
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
||||
|
||||
if (!areEquivalent(playlist[key], existingValue)) {
|
||||
hasPlaylistUpdates = true
|
||||
}
|
||||
}
|
||||
if (hasPlaylistUpdates) {
|
||||
existingPlaylist.update(playlist)
|
||||
hasUpdates = true
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
static getFromOld(oldPlaylist) {
|
||||
return {
|
||||
id: oldPlaylist.id,
|
||||
name: oldPlaylist.name,
|
||||
description: oldPlaylist.description,
|
||||
createdAt: oldPlaylist.createdAt,
|
||||
updatedAt: oldPlaylist.lastUpdate,
|
||||
userId: oldPlaylist.userId,
|
||||
libraryId: oldPlaylist.libraryId
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(playlistId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: playlistId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Playlist.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: DataTypes.STRING,
|
||||
description: DataTypes.TEXT
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'playlist'
|
||||
})
|
||||
|
||||
const { library, user } = sequelize.models
|
||||
library.hasMany(Playlist)
|
||||
Playlist.belongsTo(library)
|
||||
|
||||
user.hasMany(Playlist)
|
||||
Playlist.belongsTo(user)
|
||||
|
||||
Playlist.addHook('afterFind', findResult => {
|
||||
if (!findResult) return
|
||||
|
||||
if (!Array.isArray(findResult)) findResult = [findResult]
|
||||
|
||||
for (const instance of findResult) {
|
||||
if (instance.playlistMediaItems?.length) {
|
||||
instance.playlistMediaItems = instance.playlistMediaItems.map(pmi => {
|
||||
if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
|
||||
pmi.mediaItem = pmi.book
|
||||
pmi.dataValues.mediaItem = pmi.dataValues.book
|
||||
} else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
|
||||
pmi.mediaItem = pmi.podcastEpisode
|
||||
pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
|
||||
}
|
||||
// To prevent mistakes:
|
||||
delete pmi.book
|
||||
delete pmi.dataValues.book
|
||||
delete pmi.podcastEpisode
|
||||
delete pmi.dataValues.podcastEpisode
|
||||
return pmi
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
return Playlist
|
||||
}
|
82
server/models/PlaylistMediaItem.js
Normal file
82
server/models/PlaylistMediaItem.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class PlaylistMediaItem extends Model {
|
||||
static removeByIds(playlistId, mediaItemId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
playlistId,
|
||||
mediaItemId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getMediaItem(options) {
|
||||
if (!this.mediaItemType) return Promise.resolve(null)
|
||||
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaItemType)}`
|
||||
return this[mixinMethodName](options)
|
||||
}
|
||||
}
|
||||
|
||||
PlaylistMediaItem.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
mediaItemId: DataTypes.UUIDV4,
|
||||
mediaItemType: DataTypes.STRING,
|
||||
order: DataTypes.INTEGER
|
||||
}, {
|
||||
sequelize,
|
||||
timestamps: true,
|
||||
updatedAt: false,
|
||||
modelName: 'playlistMediaItem'
|
||||
})
|
||||
|
||||
const { book, podcastEpisode, playlist } = sequelize.models
|
||||
|
||||
book.hasMany(PlaylistMediaItem, {
|
||||
foreignKey: 'mediaItemId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
mediaItemType: 'book'
|
||||
}
|
||||
})
|
||||
PlaylistMediaItem.belongsTo(book, { foreignKey: 'mediaItemId', constraints: false })
|
||||
|
||||
podcastEpisode.hasOne(PlaylistMediaItem, {
|
||||
foreignKey: 'mediaItemId',
|
||||
constraints: false,
|
||||
scope: {
|
||||
mediaItemType: 'podcastEpisode'
|
||||
}
|
||||
})
|
||||
PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
|
||||
|
||||
PlaylistMediaItem.addHook('afterFind', findResult => {
|
||||
if (!findResult) return
|
||||
|
||||
if (!Array.isArray(findResult)) findResult = [findResult]
|
||||
|
||||
for (const instance of findResult) {
|
||||
if (instance.mediaItemType === 'book' && instance.book !== undefined) {
|
||||
instance.mediaItem = instance.book
|
||||
instance.dataValues.mediaItem = instance.dataValues.book
|
||||
} else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) {
|
||||
instance.mediaItem = instance.podcastEpisode
|
||||
instance.dataValues.mediaItem = instance.dataValues.podcastEpisode
|
||||
}
|
||||
// To prevent mistakes:
|
||||
delete instance.book
|
||||
delete instance.dataValues.book
|
||||
delete instance.podcastEpisode
|
||||
delete instance.dataValues.podcastEpisode
|
||||
}
|
||||
})
|
||||
|
||||
playlist.hasMany(PlaylistMediaItem)
|
||||
PlaylistMediaItem.belongsTo(playlist)
|
||||
|
||||
return PlaylistMediaItem
|
||||
}
|
98
server/models/Podcast.js
Normal file
98
server/models/Podcast.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Podcast extends Model {
|
||||
static getOldPodcast(libraryItemExpanded) {
|
||||
const podcastExpanded = libraryItemExpanded.media
|
||||
const podcastEpisodes = podcastExpanded.podcastEpisodes.map(ep => ep.getOldPodcastEpisode(libraryItemExpanded.id)).sort((a, b) => a.index - b.index)
|
||||
return {
|
||||
id: podcastExpanded.id,
|
||||
libraryItemId: libraryItemExpanded.id,
|
||||
metadata: {
|
||||
title: podcastExpanded.title,
|
||||
author: podcastExpanded.author,
|
||||
description: podcastExpanded.description,
|
||||
releaseDate: podcastExpanded.releaseDate,
|
||||
genres: podcastExpanded.genres,
|
||||
feedUrl: podcastExpanded.feedURL,
|
||||
imageUrl: podcastExpanded.imageURL,
|
||||
itunesPageUrl: podcastExpanded.itunesPageURL,
|
||||
itunesId: podcastExpanded.itunesId,
|
||||
itunesArtistId: podcastExpanded.itunesArtistId,
|
||||
explicit: podcastExpanded.explicit,
|
||||
language: podcastExpanded.language,
|
||||
type: podcastExpanded.podcastType
|
||||
},
|
||||
coverPath: podcastExpanded.coverPath,
|
||||
tags: podcastExpanded.tags,
|
||||
episodes: podcastEpisodes,
|
||||
autoDownloadEpisodes: podcastExpanded.autoDownloadEpisodes,
|
||||
autoDownloadSchedule: podcastExpanded.autoDownloadSchedule,
|
||||
lastEpisodeCheck: podcastExpanded.lastEpisodeCheck?.valueOf() || null,
|
||||
maxEpisodesToKeep: podcastExpanded.maxEpisodesToKeep,
|
||||
maxNewEpisodesToDownload: podcastExpanded.maxNewEpisodesToDownload
|
||||
}
|
||||
}
|
||||
|
||||
static getFromOld(oldPodcast) {
|
||||
const oldPodcastMetadata = oldPodcast.metadata
|
||||
return {
|
||||
id: oldPodcast.id,
|
||||
title: oldPodcastMetadata.title,
|
||||
author: oldPodcastMetadata.author,
|
||||
releaseDate: oldPodcastMetadata.releaseDate,
|
||||
feedURL: oldPodcastMetadata.feedUrl,
|
||||
imageURL: oldPodcastMetadata.imageUrl,
|
||||
description: oldPodcastMetadata.description,
|
||||
itunesPageURL: oldPodcastMetadata.itunesPageUrl,
|
||||
itunesId: oldPodcastMetadata.itunesId,
|
||||
itunesArtistId: oldPodcastMetadata.itunesArtistId,
|
||||
language: oldPodcastMetadata.language,
|
||||
podcastType: oldPodcastMetadata.type,
|
||||
explicit: !!oldPodcastMetadata.explicit,
|
||||
autoDownloadEpisodes: !!oldPodcast.autoDownloadEpisodes,
|
||||
autoDownloadSchedule: oldPodcast.autoDownloadSchedule,
|
||||
lastEpisodeCheck: oldPodcast.lastEpisodeCheck,
|
||||
maxEpisodesToKeep: oldPodcast.maxEpisodesToKeep,
|
||||
maxNewEpisodesToDownload: oldPodcast.maxNewEpisodesToDownload,
|
||||
coverPath: oldPodcast.coverPath,
|
||||
tags: oldPodcast.tags,
|
||||
genres: oldPodcastMetadata.genres
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Podcast.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
title: DataTypes.STRING,
|
||||
author: DataTypes.STRING,
|
||||
releaseDate: DataTypes.STRING,
|
||||
feedURL: DataTypes.STRING,
|
||||
imageURL: DataTypes.STRING,
|
||||
description: DataTypes.TEXT,
|
||||
itunesPageURL: DataTypes.STRING,
|
||||
itunesId: DataTypes.STRING,
|
||||
itunesArtistId: DataTypes.STRING,
|
||||
language: DataTypes.STRING,
|
||||
podcastType: DataTypes.STRING,
|
||||
explicit: DataTypes.BOOLEAN,
|
||||
|
||||
autoDownloadEpisodes: DataTypes.BOOLEAN,
|
||||
autoDownloadSchedule: DataTypes.STRING,
|
||||
lastEpisodeCheck: DataTypes.DATE,
|
||||
maxEpisodesToKeep: DataTypes.INTEGER,
|
||||
maxNewEpisodesToDownload: DataTypes.INTEGER,
|
||||
coverPath: DataTypes.STRING,
|
||||
tags: DataTypes.JSON,
|
||||
genres: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'podcast'
|
||||
})
|
||||
|
||||
return Podcast
|
||||
}
|
95
server/models/PodcastEpisode.js
Normal file
95
server/models/PodcastEpisode.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class PodcastEpisode extends Model {
|
||||
getOldPodcastEpisode(libraryItemId = null) {
|
||||
let enclosure = null
|
||||
if (this.enclosureURL) {
|
||||
enclosure = {
|
||||
url: this.enclosureURL,
|
||||
type: this.enclosureType,
|
||||
length: this.enclosureSize !== null ? String(this.enclosureSize) : null
|
||||
}
|
||||
}
|
||||
return {
|
||||
libraryItemId: libraryItemId || null,
|
||||
podcastId: this.podcastId,
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
season: this.season,
|
||||
episode: this.episode,
|
||||
episodeType: this.episodeType,
|
||||
title: this.title,
|
||||
subtitle: this.subtitle,
|
||||
description: this.description,
|
||||
enclosure,
|
||||
pubDate: this.pubDate,
|
||||
chapters: this.chapters,
|
||||
audioFile: this.audioFile,
|
||||
publishedAt: this.publishedAt?.valueOf() || null,
|
||||
addedAt: this.createdAt.valueOf(),
|
||||
updatedAt: this.updatedAt.valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
static createFromOld(oldEpisode) {
|
||||
const podcastEpisode = this.getFromOld(oldEpisode)
|
||||
return this.create(podcastEpisode)
|
||||
}
|
||||
|
||||
static getFromOld(oldEpisode) {
|
||||
return {
|
||||
id: oldEpisode.id,
|
||||
index: oldEpisode.index,
|
||||
season: oldEpisode.season,
|
||||
episode: oldEpisode.episode,
|
||||
episodeType: oldEpisode.episodeType,
|
||||
title: oldEpisode.title,
|
||||
subtitle: oldEpisode.subtitle,
|
||||
description: oldEpisode.description,
|
||||
pubDate: oldEpisode.pubDate,
|
||||
enclosureURL: oldEpisode.enclosure?.url || null,
|
||||
enclosureSize: oldEpisode.enclosure?.length || null,
|
||||
enclosureType: oldEpisode.enclosure?.type || null,
|
||||
publishedAt: oldEpisode.publishedAt,
|
||||
createdAt: oldEpisode.addedAt,
|
||||
updatedAt: oldEpisode.updatedAt,
|
||||
podcastId: oldEpisode.podcastId,
|
||||
audioFile: oldEpisode.audioFile?.toJSON() || null,
|
||||
chapters: oldEpisode.chapters
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PodcastEpisode.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
index: DataTypes.INTEGER,
|
||||
season: DataTypes.STRING,
|
||||
episode: DataTypes.STRING,
|
||||
episodeType: DataTypes.STRING,
|
||||
title: DataTypes.STRING,
|
||||
subtitle: DataTypes.STRING(1000),
|
||||
description: DataTypes.TEXT,
|
||||
pubDate: DataTypes.STRING,
|
||||
enclosureURL: DataTypes.STRING,
|
||||
enclosureSize: DataTypes.BIGINT,
|
||||
enclosureType: DataTypes.STRING,
|
||||
publishedAt: DataTypes.DATE,
|
||||
|
||||
audioFile: DataTypes.JSON,
|
||||
chapters: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'podcastEpisode'
|
||||
})
|
||||
|
||||
const { podcast } = sequelize.models
|
||||
podcast.hasMany(PodcastEpisode)
|
||||
PodcastEpisode.belongsTo(podcast)
|
||||
|
||||
return PodcastEpisode
|
||||
}
|
72
server/models/Series.js
Normal file
72
server/models/Series.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
const oldSeries = require('../objects/entities/Series')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Series extends Model {
|
||||
static async getAllOldSeries() {
|
||||
const series = await this.findAll()
|
||||
return series.map(se => se.getOldSeries())
|
||||
}
|
||||
|
||||
getOldSeries() {
|
||||
return new oldSeries({
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
addedAt: this.createdAt.valueOf(),
|
||||
updatedAt: this.updatedAt.valueOf()
|
||||
})
|
||||
}
|
||||
|
||||
static updateFromOld(oldSeries) {
|
||||
const series = this.getFromOld(oldSeries)
|
||||
return this.update(series, {
|
||||
where: {
|
||||
id: series.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static createFromOld(oldSeries) {
|
||||
const series = this.getFromOld(oldSeries)
|
||||
return this.create(series)
|
||||
}
|
||||
|
||||
static createBulkFromOld(oldSeriesObjs) {
|
||||
const series = oldSeriesObjs.map(this.getFromOld)
|
||||
return this.bulkCreate(series)
|
||||
}
|
||||
|
||||
static getFromOld(oldSeries) {
|
||||
return {
|
||||
id: oldSeries.id,
|
||||
name: oldSeries.name,
|
||||
description: oldSeries.description
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(seriesId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: seriesId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Series.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
name: DataTypes.STRING,
|
||||
description: DataTypes.TEXT
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'series'
|
||||
})
|
||||
|
||||
return Series
|
||||
}
|
45
server/models/Setting.js
Normal file
45
server/models/Setting.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
const { DataTypes, Model } = require('sequelize')
|
||||
|
||||
const oldEmailSettings = require('../objects/settings/EmailSettings')
|
||||
const oldServerSettings = require('../objects/settings/ServerSettings')
|
||||
const oldNotificationSettings = require('../objects/settings/NotificationSettings')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class Setting extends Model {
|
||||
static async getOldSettings() {
|
||||
const settings = (await this.findAll()).map(se => se.value)
|
||||
|
||||
|
||||
const emailSettingsJson = settings.find(se => se.id === 'email-settings')
|
||||
const serverSettingsJson = settings.find(se => se.id === 'server-settings')
|
||||
const notificationSettingsJson = settings.find(se => se.id === 'notification-settings')
|
||||
|
||||
return {
|
||||
settings,
|
||||
emailSettings: new oldEmailSettings(emailSettingsJson),
|
||||
serverSettings: new oldServerSettings(serverSettingsJson),
|
||||
notificationSettings: new oldNotificationSettings(notificationSettingsJson)
|
||||
}
|
||||
}
|
||||
|
||||
static updateSettingObj(setting) {
|
||||
return this.upsert({
|
||||
key: setting.id,
|
||||
value: setting
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Setting.init({
|
||||
key: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true
|
||||
},
|
||||
value: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'setting'
|
||||
})
|
||||
|
||||
return Setting
|
||||
}
|
136
server/models/User.js
Normal file
136
server/models/User.js
Normal file
|
@ -0,0 +1,136 @@
|
|||
const uuidv4 = require("uuid").v4
|
||||
const { DataTypes, Model } = require('sequelize')
|
||||
const Logger = require('../Logger')
|
||||
const oldUser = require('../objects/user/User')
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
class User extends Model {
|
||||
static async getOldUsers() {
|
||||
const users = await this.findAll({
|
||||
include: sequelize.models.mediaProgress
|
||||
})
|
||||
return users.map(u => this.getOldUser(u))
|
||||
}
|
||||
|
||||
static getOldUser(userExpanded) {
|
||||
const mediaProgress = userExpanded.mediaProgresses.map(mp => mp.getOldMediaProgress())
|
||||
|
||||
const librariesAccessible = userExpanded.permissions?.librariesAccessible || []
|
||||
const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || []
|
||||
const permissions = userExpanded.permissions || {}
|
||||
delete permissions.librariesAccessible
|
||||
delete permissions.itemTagsSelected
|
||||
|
||||
return new oldUser({
|
||||
id: userExpanded.id,
|
||||
oldUserId: userExpanded.extraData?.oldUserId || null,
|
||||
username: userExpanded.username,
|
||||
pash: userExpanded.pash,
|
||||
type: userExpanded.type,
|
||||
token: userExpanded.token,
|
||||
mediaProgress,
|
||||
seriesHideFromContinueListening: userExpanded.extraData?.seriesHideFromContinueListening || [],
|
||||
bookmarks: userExpanded.bookmarks,
|
||||
isActive: userExpanded.isActive,
|
||||
isLocked: userExpanded.isLocked,
|
||||
lastSeen: userExpanded.lastSeen?.valueOf() || null,
|
||||
createdAt: userExpanded.createdAt.valueOf(),
|
||||
permissions,
|
||||
librariesAccessible,
|
||||
itemTagsSelected
|
||||
})
|
||||
}
|
||||
|
||||
static createFromOld(oldUser) {
|
||||
const user = this.getFromOld(oldUser)
|
||||
return this.create(user)
|
||||
}
|
||||
|
||||
static updateFromOld(oldUser) {
|
||||
const user = this.getFromOld(oldUser)
|
||||
return this.update(user, {
|
||||
where: {
|
||||
id: user.id
|
||||
}
|
||||
}).then((result) => result[0] > 0).catch((error) => {
|
||||
Logger.error(`[User] Failed to save user ${oldUser.id}`, error)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
static getFromOld(oldUser) {
|
||||
return {
|
||||
id: oldUser.id,
|
||||
username: oldUser.username,
|
||||
pash: oldUser.pash || null,
|
||||
type: oldUser.type || null,
|
||||
token: oldUser.token || null,
|
||||
isActive: !!oldUser.isActive,
|
||||
lastSeen: oldUser.lastSeen || null,
|
||||
extraData: {
|
||||
seriesHideFromContinueListening: oldUser.seriesHideFromContinueListening || [],
|
||||
oldUserId: oldUser.oldUserId
|
||||
},
|
||||
createdAt: oldUser.createdAt || Date.now(),
|
||||
permissions: {
|
||||
...oldUser.permissions,
|
||||
librariesAccessible: oldUser.librariesAccessible || [],
|
||||
itemTagsSelected: oldUser.itemTagsSelected || []
|
||||
},
|
||||
bookmarks: oldUser.bookmarks
|
||||
}
|
||||
}
|
||||
|
||||
static removeById(userId) {
|
||||
return this.destroy({
|
||||
where: {
|
||||
id: userId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static async createRootUser(username, pash, token) {
|
||||
const newRoot = new oldUser({
|
||||
id: uuidv4(),
|
||||
type: 'root',
|
||||
username,
|
||||
pash,
|
||||
token,
|
||||
isActive: true,
|
||||
createdAt: Date.now()
|
||||
})
|
||||
await this.createFromOld(newRoot)
|
||||
return newRoot
|
||||
}
|
||||
}
|
||||
|
||||
User.init({
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
username: DataTypes.STRING,
|
||||
email: DataTypes.STRING,
|
||||
pash: DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
token: DataTypes.STRING,
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
isLocked: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
lastSeen: DataTypes.DATE,
|
||||
permissions: DataTypes.JSON,
|
||||
bookmarks: DataTypes.JSON,
|
||||
extraData: DataTypes.JSON
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'user'
|
||||
})
|
||||
|
||||
return User
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue