mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-25 16:18:54 +01:00
Add jsdoc types for models
This commit is contained in:
parent
7afda1295b
commit
c707bcf0f6
@ -93,25 +93,25 @@ class Database {
|
|||||||
|
|
||||||
buildModels(force = false) {
|
buildModels(force = false) {
|
||||||
require('./models/User')(this.sequelize)
|
require('./models/User')(this.sequelize)
|
||||||
require('./models/Library')(this.sequelize)
|
require('./models/Library').init(this.sequelize)
|
||||||
require('./models/LibraryFolder')(this.sequelize)
|
require('./models/LibraryFolder').init(this.sequelize)
|
||||||
require('./models/Book')(this.sequelize)
|
require('./models/Book').init(this.sequelize)
|
||||||
require('./models/Podcast')(this.sequelize)
|
require('./models/Podcast')(this.sequelize)
|
||||||
require('./models/PodcastEpisode')(this.sequelize)
|
require('./models/PodcastEpisode')(this.sequelize)
|
||||||
require('./models/LibraryItem')(this.sequelize)
|
require('./models/LibraryItem')(this.sequelize)
|
||||||
require('./models/MediaProgress')(this.sequelize)
|
require('./models/MediaProgress')(this.sequelize)
|
||||||
require('./models/Series')(this.sequelize)
|
require('./models/Series')(this.sequelize)
|
||||||
require('./models/BookSeries')(this.sequelize)
|
require('./models/BookSeries').init(this.sequelize)
|
||||||
require('./models/Author').init(this.sequelize)
|
require('./models/Author').init(this.sequelize)
|
||||||
require('./models/BookAuthor')(this.sequelize)
|
require('./models/BookAuthor').init(this.sequelize)
|
||||||
require('./models/Collection')(this.sequelize)
|
require('./models/Collection').init(this.sequelize)
|
||||||
require('./models/CollectionBook')(this.sequelize)
|
require('./models/CollectionBook').init(this.sequelize)
|
||||||
require('./models/Playlist')(this.sequelize)
|
require('./models/Playlist')(this.sequelize)
|
||||||
require('./models/PlaylistMediaItem')(this.sequelize)
|
require('./models/PlaylistMediaItem')(this.sequelize)
|
||||||
require('./models/Device')(this.sequelize)
|
require('./models/Device').init(this.sequelize)
|
||||||
require('./models/PlaybackSession')(this.sequelize)
|
require('./models/PlaybackSession')(this.sequelize)
|
||||||
require('./models/Feed')(this.sequelize)
|
require('./models/Feed').init(this.sequelize)
|
||||||
require('./models/FeedEpisode')(this.sequelize)
|
require('./models/FeedEpisode').init(this.sequelize)
|
||||||
require('./models/Setting')(this.sequelize)
|
require('./models/Setting')(this.sequelize)
|
||||||
|
|
||||||
return this.sequelize.sync({ force, alter: false })
|
return this.sequelize.sync({ force, alter: false })
|
||||||
|
@ -1,178 +1,231 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
class Book extends Model {
|
||||||
class Book extends Model {
|
constructor(values, options) {
|
||||||
static getOldBook(libraryItemExpanded) {
|
super(values, options)
|
||||||
const bookExpanded = libraryItemExpanded.media
|
|
||||||
let authors = []
|
|
||||||
if (bookExpanded.authors?.length) {
|
|
||||||
authors = bookExpanded.authors.map(au => {
|
|
||||||
return {
|
|
||||||
id: au.id,
|
|
||||||
name: au.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (bookExpanded.bookAuthors?.length) {
|
|
||||||
authors = bookExpanded.bookAuthors.map(ba => {
|
|
||||||
if (ba.author) {
|
|
||||||
return {
|
|
||||||
id: ba.author.id,
|
|
||||||
name: ba.author.name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}).filter(a => a)
|
|
||||||
}
|
|
||||||
|
|
||||||
let series = []
|
/** @type {UUIDV4} */
|
||||||
if (bookExpanded.series?.length) {
|
this.id
|
||||||
series = bookExpanded.series.map(se => {
|
/** @type {string} */
|
||||||
return {
|
this.title
|
||||||
id: se.id,
|
/** @type {string} */
|
||||||
name: se.name,
|
this.titleIgnorePrefix
|
||||||
sequence: se.bookSeries.sequence
|
/** @type {string} */
|
||||||
}
|
this.publishedYear
|
||||||
})
|
/** @type {string} */
|
||||||
} else if (bookExpanded.bookSeries?.length) {
|
this.publishedDate
|
||||||
series = bookExpanded.bookSeries.map(bs => {
|
/** @type {string} */
|
||||||
if (bs.series) {
|
this.publisher
|
||||||
return {
|
/** @type {string} */
|
||||||
id: bs.series.id,
|
this.description
|
||||||
name: bs.series.name,
|
/** @type {string} */
|
||||||
sequence: bs.sequence
|
this.isbn
|
||||||
}
|
/** @type {string} */
|
||||||
} else {
|
this.asin
|
||||||
Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs)
|
/** @type {string} */
|
||||||
return null
|
this.language
|
||||||
}
|
/** @type {boolean} */
|
||||||
}).filter(s => s)
|
this.explicit
|
||||||
}
|
/** @type {boolean} */
|
||||||
|
this.abridged
|
||||||
|
/** @type {string} */
|
||||||
|
this.coverPath
|
||||||
|
/** @type {number} */
|
||||||
|
this.duration
|
||||||
|
/** @type {Object} */
|
||||||
|
this.narrators
|
||||||
|
/** @type {Object} */
|
||||||
|
this.audioFiles
|
||||||
|
/** @type {Object} */
|
||||||
|
this.ebookFile
|
||||||
|
/** @type {Object} */
|
||||||
|
this.chapters
|
||||||
|
/** @type {Object} */
|
||||||
|
this.tags
|
||||||
|
/** @type {Object} */
|
||||||
|
this.genres
|
||||||
|
/** @type {Date} */
|
||||||
|
this.updatedAt
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
static getOldBook(libraryItemExpanded) {
|
||||||
id: bookExpanded.id,
|
const bookExpanded = libraryItemExpanded.media
|
||||||
libraryItemId: libraryItemExpanded.id,
|
let authors = []
|
||||||
coverPath: bookExpanded.coverPath,
|
if (bookExpanded.authors?.length) {
|
||||||
tags: bookExpanded.tags,
|
authors = bookExpanded.authors.map(au => {
|
||||||
audioFiles: bookExpanded.audioFiles,
|
return {
|
||||||
chapters: bookExpanded.chapters,
|
id: au.id,
|
||||||
ebookFile: bookExpanded.ebookFile,
|
name: au.name
|
||||||
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
|
|
||||||
})
|
})
|
||||||
|
} else if (bookExpanded.bookAuthors?.length) {
|
||||||
|
authors = bookExpanded.bookAuthors.map(ba => {
|
||||||
|
if (ba.author) {
|
||||||
|
return {
|
||||||
|
id: ba.author.id,
|
||||||
|
name: ba.author.name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}).filter(a => a)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFromOld(oldBook) {
|
let series = []
|
||||||
return {
|
if (bookExpanded.series?.length) {
|
||||||
id: oldBook.id,
|
series = bookExpanded.series.map(se => {
|
||||||
title: oldBook.metadata.title,
|
return {
|
||||||
titleIgnorePrefix: oldBook.metadata.titleIgnorePrefix,
|
id: se.id,
|
||||||
subtitle: oldBook.metadata.subtitle,
|
name: se.name,
|
||||||
publishedYear: oldBook.metadata.publishedYear,
|
sequence: se.bookSeries.sequence
|
||||||
publishedDate: oldBook.metadata.publishedDate,
|
}
|
||||||
publisher: oldBook.metadata.publisher,
|
})
|
||||||
description: oldBook.metadata.description,
|
} else if (bookExpanded.bookSeries?.length) {
|
||||||
isbn: oldBook.metadata.isbn,
|
series = bookExpanded.bookSeries.map(bs => {
|
||||||
asin: oldBook.metadata.asin,
|
if (bs.series) {
|
||||||
language: oldBook.metadata.language,
|
return {
|
||||||
explicit: !!oldBook.metadata.explicit,
|
id: bs.series.id,
|
||||||
abridged: !!oldBook.metadata.abridged,
|
name: bs.series.name,
|
||||||
narrators: oldBook.metadata.narrators,
|
sequence: bs.sequence
|
||||||
ebookFile: oldBook.ebookFile?.toJSON() || null,
|
}
|
||||||
coverPath: oldBook.coverPath,
|
} else {
|
||||||
duration: oldBook.duration,
|
Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs)
|
||||||
audioFiles: oldBook.audioFiles?.map(af => af.toJSON()) || [],
|
return null
|
||||||
chapters: oldBook.chapters,
|
}
|
||||||
tags: oldBook.tags,
|
}).filter(s => s)
|
||||||
genres: oldBook.metadata.genres
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Book.init({
|
/**
|
||||||
id: {
|
* @param {object} oldBook
|
||||||
type: DataTypes.UUID,
|
* @returns {boolean} true if updated
|
||||||
defaultValue: DataTypes.UUIDV4,
|
*/
|
||||||
primaryKey: true
|
static saveFromOld(oldBook) {
|
||||||
},
|
const book = this.getFromOld(oldBook)
|
||||||
title: DataTypes.STRING,
|
return this.update(book, {
|
||||||
titleIgnorePrefix: DataTypes.STRING,
|
where: {
|
||||||
subtitle: DataTypes.STRING,
|
id: book.id
|
||||||
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,
|
|
||||||
duration: DataTypes.FLOAT,
|
|
||||||
|
|
||||||
narrators: DataTypes.JSON,
|
|
||||||
audioFiles: DataTypes.JSON,
|
|
||||||
ebookFile: DataTypes.JSON,
|
|
||||||
chapters: DataTypes.JSON,
|
|
||||||
tags: DataTypes.JSON,
|
|
||||||
genres: DataTypes.JSON
|
|
||||||
}, {
|
|
||||||
sequelize,
|
|
||||||
modelName: 'book',
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
fields: [{
|
|
||||||
name: 'title',
|
|
||||||
collate: 'NOCASE'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fields: [{
|
|
||||||
name: 'titleIgnorePrefix',
|
|
||||||
collate: 'NOCASE'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fields: ['publishedYear']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fields: ['duration']
|
|
||||||
}
|
}
|
||||||
]
|
}).then(result => result[0] > 0).catch((error) => {
|
||||||
})
|
Logger.error(`[Book] Failed to save book ${book.id}`, error)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return Book
|
static getFromOld(oldBook) {
|
||||||
}
|
return {
|
||||||
|
id: oldBook.id,
|
||||||
|
title: oldBook.metadata.title,
|
||||||
|
titleIgnorePrefix: oldBook.metadata.titleIgnorePrefix,
|
||||||
|
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,
|
||||||
|
duration: oldBook.duration,
|
||||||
|
audioFiles: oldBook.audioFiles?.map(af => af.toJSON()) || [],
|
||||||
|
chapters: oldBook.chapters,
|
||||||
|
tags: oldBook.tags,
|
||||||
|
genres: oldBook.metadata.genres
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize model
|
||||||
|
* @param {import('../Database').sequelize} sequelize
|
||||||
|
*/
|
||||||
|
static init(sequelize) {
|
||||||
|
super.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
title: DataTypes.STRING,
|
||||||
|
titleIgnorePrefix: 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,
|
||||||
|
duration: DataTypes.FLOAT,
|
||||||
|
|
||||||
|
narrators: DataTypes.JSON,
|
||||||
|
audioFiles: DataTypes.JSON,
|
||||||
|
ebookFile: DataTypes.JSON,
|
||||||
|
chapters: DataTypes.JSON,
|
||||||
|
tags: DataTypes.JSON,
|
||||||
|
genres: DataTypes.JSON
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'book',
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
fields: [{
|
||||||
|
name: 'title',
|
||||||
|
collate: 'NOCASE'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: [{
|
||||||
|
name: 'titleIgnorePrefix',
|
||||||
|
collate: 'NOCASE'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: ['publishedYear']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: ['duration']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Book
|
@ -1,41 +1,57 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
class BookAuthor extends Model {
|
||||||
class BookAuthor extends Model {
|
constructor(values, options) {
|
||||||
static removeByIds(authorId = null, bookId = null) {
|
super(values, options)
|
||||||
const where = {}
|
|
||||||
if (authorId) where.authorId = authorId
|
/** @type {UUIDV4} */
|
||||||
if (bookId) where.bookId = bookId
|
this.id
|
||||||
return this.destroy({
|
/** @type {UUIDV4} */
|
||||||
where
|
this.bookId
|
||||||
})
|
/** @type {UUIDV4} */
|
||||||
}
|
this.authorId
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
BookAuthor.init({
|
static removeByIds(authorId = null, bookId = null) {
|
||||||
id: {
|
const where = {}
|
||||||
type: DataTypes.UUID,
|
if (authorId) where.authorId = authorId
|
||||||
defaultValue: DataTypes.UUIDV4,
|
if (bookId) where.bookId = bookId
|
||||||
primaryKey: true
|
return this.destroy({
|
||||||
}
|
where
|
||||||
}, {
|
})
|
||||||
sequelize,
|
}
|
||||||
modelName: 'bookAuthor',
|
|
||||||
timestamps: true,
|
|
||||||
updatedAt: 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
|
* Initialize model
|
||||||
const { book, author } = sequelize.models
|
* @param {import('../Database').sequelize} sequelize
|
||||||
book.belongsToMany(author, { through: BookAuthor })
|
*/
|
||||||
author.belongsToMany(book, { through: BookAuthor })
|
static init(sequelize) {
|
||||||
|
super.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'bookAuthor',
|
||||||
|
timestamps: true,
|
||||||
|
updatedAt: false
|
||||||
|
})
|
||||||
|
|
||||||
book.hasMany(BookAuthor)
|
// Super Many-to-Many
|
||||||
BookAuthor.belongsTo(book)
|
// 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 })
|
||||||
|
|
||||||
author.hasMany(BookAuthor)
|
book.hasMany(BookAuthor)
|
||||||
BookAuthor.belongsTo(author)
|
BookAuthor.belongsTo(book)
|
||||||
|
|
||||||
return BookAuthor
|
author.hasMany(BookAuthor)
|
||||||
}
|
BookAuthor.belongsTo(author)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = BookAuthor
|
@ -1,42 +1,61 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
class BookSeries extends Model {
|
||||||
class BookSeries extends Model {
|
constructor(values, options) {
|
||||||
static removeByIds(seriesId = null, bookId = null) {
|
super(values, options)
|
||||||
const where = {}
|
|
||||||
if (seriesId) where.seriesId = seriesId
|
/** @type {UUIDV4} */
|
||||||
if (bookId) where.bookId = bookId
|
this.id
|
||||||
return this.destroy({
|
/** @type {string} */
|
||||||
where
|
this.sequence
|
||||||
})
|
/** @type {UUIDV4} */
|
||||||
}
|
this.bookId
|
||||||
|
/** @type {UUIDV4} */
|
||||||
|
this.seriesId
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
BookSeries.init({
|
static removeByIds(seriesId = null, bookId = null) {
|
||||||
id: {
|
const where = {}
|
||||||
type: DataTypes.UUID,
|
if (seriesId) where.seriesId = seriesId
|
||||||
defaultValue: DataTypes.UUIDV4,
|
if (bookId) where.bookId = bookId
|
||||||
primaryKey: true
|
return this.destroy({
|
||||||
},
|
where
|
||||||
sequence: DataTypes.STRING
|
})
|
||||||
}, {
|
}
|
||||||
sequelize,
|
|
||||||
modelName: 'bookSeries',
|
|
||||||
timestamps: true,
|
|
||||||
updatedAt: 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
|
* Initialize model
|
||||||
const { book, series } = sequelize.models
|
* @param {import('../Database').sequelize} sequelize
|
||||||
book.belongsToMany(series, { through: BookSeries })
|
*/
|
||||||
series.belongsToMany(book, { through: BookSeries })
|
static init(sequelize) {
|
||||||
|
super.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
sequence: DataTypes.STRING
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'bookSeries',
|
||||||
|
timestamps: true,
|
||||||
|
updatedAt: false
|
||||||
|
})
|
||||||
|
|
||||||
book.hasMany(BookSeries)
|
// Super Many-to-Many
|
||||||
BookSeries.belongsTo(book)
|
// 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 })
|
||||||
|
|
||||||
series.hasMany(BookSeries)
|
book.hasMany(BookSeries)
|
||||||
BookSeries.belongsTo(series)
|
BookSeries.belongsTo(book)
|
||||||
|
|
||||||
return BookSeries
|
series.hasMany(BookSeries)
|
||||||
}
|
BookSeries.belongsTo(series)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BookSeries
|
@ -1,151 +1,97 @@
|
|||||||
const { DataTypes, Model, Sequelize } = require('sequelize')
|
const { DataTypes, Model, Sequelize } = require('sequelize')
|
||||||
|
|
||||||
const oldCollection = require('../objects/Collection')
|
const oldCollection = require('../objects/Collection')
|
||||||
const { areEquivalent } = require('../utils/index')
|
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
|
||||||
class Collection extends Model {
|
class Collection extends Model {
|
||||||
/**
|
constructor(values, options) {
|
||||||
* Get all old collections
|
super(values, options)
|
||||||
* @returns {Promise<oldCollection[]>}
|
|
||||||
*/
|
/** @type {UUIDV4} */
|
||||||
static async getOldCollections() {
|
this.id
|
||||||
const collections = await this.findAll({
|
/** @type {string} */
|
||||||
include: {
|
this.name
|
||||||
model: sequelize.models.book,
|
/** @type {string} */
|
||||||
include: sequelize.models.libraryItem
|
this.description
|
||||||
|
/** @type {UUIDV4} */
|
||||||
|
this.libraryId
|
||||||
|
/** @type {Date} */
|
||||||
|
this.updatedAt
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get all old collections
|
||||||
|
* @returns {Promise<oldCollection[]>}
|
||||||
|
*/
|
||||||
|
static async getOldCollections() {
|
||||||
|
const collections = await this.findAll({
|
||||||
|
include: {
|
||||||
|
model: this.sequelize.models.book,
|
||||||
|
include: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
|
||||||
|
})
|
||||||
|
return collections.map(c => this.getOldCollection(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all old collections toJSONExpanded, items filtered for user permissions
|
||||||
|
* @param {[oldUser]} user
|
||||||
|
* @param {[string]} libraryId
|
||||||
|
* @param {[string[]]} include
|
||||||
|
* @returns {Promise<object[]>} oldCollection.toJSONExpanded
|
||||||
|
*/
|
||||||
|
static async getOldCollectionsJsonExpanded(user, libraryId, include) {
|
||||||
|
let collectionWhere = null
|
||||||
|
if (libraryId) {
|
||||||
|
collectionWhere = {
|
||||||
|
libraryId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally include rssfeed for collection
|
||||||
|
const collectionIncludes = []
|
||||||
|
if (include.includes('rssfeed')) {
|
||||||
|
collectionIncludes.push({
|
||||||
|
model: this.sequelize.models.feed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const collections = await this.findAll({
|
||||||
|
where: collectionWhere,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.book,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.author,
|
||||||
|
through: {
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.series,
|
||||||
|
through: {
|
||||||
|
attributes: ['sequence']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
},
|
},
|
||||||
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
|
...collectionIncludes
|
||||||
})
|
],
|
||||||
return collections.map(c => this.getOldCollection(c))
|
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
|
||||||
}
|
})
|
||||||
|
// TODO: Handle user permission restrictions on initial query
|
||||||
/**
|
return collections.map(c => {
|
||||||
* Get all old collections toJSONExpanded, items filtered for user permissions
|
const oldCollection = this.getOldCollection(c)
|
||||||
* @param {[oldUser]} user
|
|
||||||
* @param {[string]} libraryId
|
|
||||||
* @param {[string[]]} include
|
|
||||||
* @returns {Promise<object[]>} oldCollection.toJSONExpanded
|
|
||||||
*/
|
|
||||||
static async getOldCollectionsJsonExpanded(user, libraryId, include) {
|
|
||||||
let collectionWhere = null
|
|
||||||
if (libraryId) {
|
|
||||||
collectionWhere = {
|
|
||||||
libraryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally include rssfeed for collection
|
|
||||||
const collectionIncludes = []
|
|
||||||
if (include.includes('rssfeed')) {
|
|
||||||
collectionIncludes.push({
|
|
||||||
model: sequelize.models.feed
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const collections = await this.findAll({
|
|
||||||
where: collectionWhere,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: sequelize.models.book,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: sequelize.models.author,
|
|
||||||
through: {
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: sequelize.models.series,
|
|
||||||
through: {
|
|
||||||
attributes: ['sequence']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...collectionIncludes
|
|
||||||
],
|
|
||||||
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
|
|
||||||
})
|
|
||||||
// TODO: Handle user permission restrictions on initial query
|
|
||||||
return collections.map(c => {
|
|
||||||
const oldCollection = this.getOldCollection(c)
|
|
||||||
|
|
||||||
// Filter books using user permissions
|
|
||||||
const books = c.books?.filter(b => {
|
|
||||||
if (user) {
|
|
||||||
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (b.explicit === true && !user.canAccessExplicitContent) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}) || []
|
|
||||||
|
|
||||||
// Map to library items
|
|
||||||
const libraryItems = books.map(b => {
|
|
||||||
const libraryItem = b.libraryItem
|
|
||||||
delete b.libraryItem
|
|
||||||
libraryItem.media = b
|
|
||||||
return sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Users with restricted permissions will not see this collection
|
|
||||||
if (!books.length && oldCollection.books.length) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
|
|
||||||
|
|
||||||
// Map feed if found
|
|
||||||
if (c.feeds?.length) {
|
|
||||||
collectionExpanded.rssFeed = sequelize.models.feed.getOldFeed(c.feeds[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return collectionExpanded
|
|
||||||
}).filter(c => c)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get old collection toJSONExpanded, items filtered for user permissions
|
|
||||||
* @param {[oldUser]} user
|
|
||||||
* @param {[string[]]} include
|
|
||||||
* @returns {Promise<object>} oldCollection.toJSONExpanded
|
|
||||||
*/
|
|
||||||
async getOldJsonExpanded(user, include) {
|
|
||||||
this.books = await this.getBooks({
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: sequelize.models.author,
|
|
||||||
through: {
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: sequelize.models.series,
|
|
||||||
through: {
|
|
||||||
attributes: ['sequence']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
],
|
|
||||||
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
|
||||||
}) || []
|
|
||||||
|
|
||||||
const oldCollection = sequelize.models.collection.getOldCollection(this)
|
|
||||||
|
|
||||||
// Filter books using user permissions
|
// Filter books using user permissions
|
||||||
// TODO: Handle user permission restrictions on initial query
|
const books = c.books?.filter(b => {
|
||||||
const books = this.books?.filter(b => {
|
|
||||||
if (user) {
|
if (user) {
|
||||||
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
||||||
return false
|
return false
|
||||||
@ -162,7 +108,7 @@ module.exports = (sequelize) => {
|
|||||||
const libraryItem = b.libraryItem
|
const libraryItem = b.libraryItem
|
||||||
delete b.libraryItem
|
delete b.libraryItem
|
||||||
libraryItem.media = b
|
libraryItem.media = b
|
||||||
return sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Users with restricted permissions will not see this collection
|
// Users with restricted permissions will not see this collection
|
||||||
@ -172,151 +118,225 @@ module.exports = (sequelize) => {
|
|||||||
|
|
||||||
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
|
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
|
||||||
|
|
||||||
if (include?.includes('rssfeed')) {
|
// Map feed if found
|
||||||
const feeds = await this.getFeeds()
|
if (c.feeds?.length) {
|
||||||
if (feeds?.length) {
|
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(c.feeds[0])
|
||||||
collectionExpanded.rssFeed = sequelize.models.feed.getOldFeed(feeds[0])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return collectionExpanded
|
return collectionExpanded
|
||||||
|
}).filter(c => c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get old collection toJSONExpanded, items filtered for user permissions
|
||||||
|
* @param {[oldUser]} user
|
||||||
|
* @param {[string[]]} include
|
||||||
|
* @returns {Promise<object>} oldCollection.toJSONExpanded
|
||||||
|
*/
|
||||||
|
async getOldJsonExpanded(user, include) {
|
||||||
|
this.books = await this.getBooks({
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.author,
|
||||||
|
through: {
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.series,
|
||||||
|
through: {
|
||||||
|
attributes: ['sequence']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
||||||
|
}) || []
|
||||||
|
|
||||||
|
const oldCollection = this.sequelize.models.collection.getOldCollection(this)
|
||||||
|
|
||||||
|
// Filter books using user permissions
|
||||||
|
// TODO: Handle user permission restrictions on initial query
|
||||||
|
const books = this.books?.filter(b => {
|
||||||
|
if (user) {
|
||||||
|
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (b.explicit === true && !user.canAccessExplicitContent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}) || []
|
||||||
|
|
||||||
|
// Map to library items
|
||||||
|
const libraryItems = books.map(b => {
|
||||||
|
const libraryItem = b.libraryItem
|
||||||
|
delete b.libraryItem
|
||||||
|
libraryItem.media = b
|
||||||
|
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Users with restricted permissions will not see this collection
|
||||||
|
if (!books.length && oldCollection.books.length) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
|
||||||
* Get old collection from Collection
|
|
||||||
* @param {Collection} collectionExpanded
|
|
||||||
* @returns {oldCollection}
|
|
||||||
*/
|
|
||||||
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) {
|
if (include?.includes('rssfeed')) {
|
||||||
const collection = this.getFromOld(oldCollection)
|
const feeds = await this.getFeeds()
|
||||||
return this.create(collection)
|
if (feeds?.length) {
|
||||||
}
|
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
|
||||||
|
|
||||||
static getFromOld(oldCollection) {
|
|
||||||
return {
|
|
||||||
id: oldCollection.id,
|
|
||||||
name: oldCollection.name,
|
|
||||||
description: oldCollection.description,
|
|
||||||
libraryId: oldCollection.libraryId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeById(collectionId) {
|
return collectionExpanded
|
||||||
return this.destroy({
|
}
|
||||||
where: {
|
|
||||||
id: collectionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old collection by id
|
* Get old collection from Collection
|
||||||
* @param {string} collectionId
|
* @param {Collection} collectionExpanded
|
||||||
* @returns {Promise<oldCollection|null>} returns null if not found
|
* @returns {oldCollection}
|
||||||
*/
|
*/
|
||||||
static async getOldById(collectionId) {
|
static getOldCollection(collectionExpanded) {
|
||||||
if (!collectionId) return null
|
const libraryItemIds = collectionExpanded.books?.map(b => b.libraryItem?.id || null).filter(lid => lid) || []
|
||||||
const collection = await this.findByPk(collectionId, {
|
return new oldCollection({
|
||||||
include: {
|
id: collectionExpanded.id,
|
||||||
model: sequelize.models.book,
|
libraryId: collectionExpanded.libraryId,
|
||||||
include: sequelize.models.libraryItem
|
name: collectionExpanded.name,
|
||||||
},
|
description: collectionExpanded.description,
|
||||||
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
|
books: libraryItemIds,
|
||||||
})
|
lastUpdate: collectionExpanded.updatedAt.valueOf(),
|
||||||
if (!collection) return null
|
createdAt: collectionExpanded.createdAt.valueOf()
|
||||||
return this.getOldCollection(collection)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static createFromOld(oldCollection) {
|
||||||
* Get old collection from current
|
const collection = this.getFromOld(oldCollection)
|
||||||
* @returns {Promise<oldCollection>}
|
return this.create(collection)
|
||||||
*/
|
}
|
||||||
async getOld() {
|
|
||||||
this.books = await this.getBooks({
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: sequelize.models.author,
|
|
||||||
through: {
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: sequelize.models.series,
|
|
||||||
through: {
|
|
||||||
attributes: ['sequence']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
],
|
static getFromOld(oldCollection) {
|
||||||
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
return {
|
||||||
}) || []
|
id: oldCollection.id,
|
||||||
|
name: oldCollection.name,
|
||||||
return sequelize.models.collection.getOldCollection(this)
|
description: oldCollection.description,
|
||||||
}
|
libraryId: oldCollection.libraryId
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all collections belonging to library
|
|
||||||
* @param {string} libraryId
|
|
||||||
* @returns {Promise<number>} number of collections destroyed
|
|
||||||
*/
|
|
||||||
static async removeAllForLibrary(libraryId) {
|
|
||||||
if (!libraryId) return 0
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
libraryId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getAllForBook(bookId) {
|
|
||||||
const collections = await this.findAll({
|
|
||||||
include: {
|
|
||||||
model: sequelize.models.book,
|
|
||||||
where: {
|
|
||||||
id: bookId
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
include: sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
|
|
||||||
})
|
|
||||||
return collections.map(c => this.getOldCollection(c))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection.init({
|
static removeById(collectionId) {
|
||||||
id: {
|
return this.destroy({
|
||||||
type: DataTypes.UUID,
|
where: {
|
||||||
defaultValue: DataTypes.UUIDV4,
|
id: collectionId
|
||||||
primaryKey: true
|
}
|
||||||
},
|
})
|
||||||
name: DataTypes.STRING,
|
}
|
||||||
description: DataTypes.TEXT
|
|
||||||
}, {
|
|
||||||
sequelize,
|
|
||||||
modelName: 'collection'
|
|
||||||
})
|
|
||||||
|
|
||||||
const { library } = sequelize.models
|
/**
|
||||||
|
* Get old collection by id
|
||||||
|
* @param {string} collectionId
|
||||||
|
* @returns {Promise<oldCollection|null>} returns null if not found
|
||||||
|
*/
|
||||||
|
static async getOldById(collectionId) {
|
||||||
|
if (!collectionId) return null
|
||||||
|
const collection = await this.findByPk(collectionId, {
|
||||||
|
include: {
|
||||||
|
model: this.sequelize.models.book,
|
||||||
|
include: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
|
||||||
|
})
|
||||||
|
if (!collection) return null
|
||||||
|
return this.getOldCollection(collection)
|
||||||
|
}
|
||||||
|
|
||||||
library.hasMany(Collection)
|
/**
|
||||||
Collection.belongsTo(library)
|
* Get old collection from current
|
||||||
|
* @returns {Promise<oldCollection>}
|
||||||
|
*/
|
||||||
|
async getOld() {
|
||||||
|
this.books = await this.getBooks({
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.author,
|
||||||
|
through: {
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.series,
|
||||||
|
through: {
|
||||||
|
attributes: ['sequence']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
return Collection
|
],
|
||||||
}
|
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
||||||
|
}) || []
|
||||||
|
|
||||||
|
return this.sequelize.models.collection.getOldCollection(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all collections belonging to library
|
||||||
|
* @param {string} libraryId
|
||||||
|
* @returns {Promise<number>} number of collections destroyed
|
||||||
|
*/
|
||||||
|
static async removeAllForLibrary(libraryId) {
|
||||||
|
if (!libraryId) return 0
|
||||||
|
return this.destroy({
|
||||||
|
where: {
|
||||||
|
libraryId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getAllForBook(bookId) {
|
||||||
|
const collections = await this.findAll({
|
||||||
|
include: {
|
||||||
|
model: this.sequelize.models.book,
|
||||||
|
where: {
|
||||||
|
id: bookId
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
include: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
|
||||||
|
})
|
||||||
|
return collections.map(c => this.getOldCollection(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize model
|
||||||
|
* @param {import('../Database').sequelize} sequelize
|
||||||
|
*/
|
||||||
|
static init(sequelize) {
|
||||||
|
super.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
name: DataTypes.STRING,
|
||||||
|
description: DataTypes.TEXT
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'collection'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { library } = sequelize.models
|
||||||
|
|
||||||
|
library.hasMany(Collection)
|
||||||
|
Collection.belongsTo(library)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Collection
|
@ -1,46 +1,61 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
class CollectionBook extends Model {
|
||||||
class CollectionBook extends Model {
|
constructor(values, options) {
|
||||||
static removeByIds(collectionId, bookId) {
|
super(values, options)
|
||||||
return this.destroy({
|
|
||||||
where: {
|
/** @type {UUIDV4} */
|
||||||
bookId,
|
this.id
|
||||||
collectionId
|
/** @type {number} */
|
||||||
}
|
this.order
|
||||||
})
|
/** @type {UUIDV4} */
|
||||||
}
|
this.bookId
|
||||||
|
/** @type {UUIDV4} */
|
||||||
|
this.collectionId
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBook.init({
|
static removeByIds(collectionId, bookId) {
|
||||||
id: {
|
return this.destroy({
|
||||||
type: DataTypes.UUID,
|
where: {
|
||||||
defaultValue: DataTypes.UUIDV4,
|
bookId,
|
||||||
primaryKey: true
|
collectionId
|
||||||
},
|
}
|
||||||
order: DataTypes.INTEGER
|
})
|
||||||
}, {
|
}
|
||||||
sequelize,
|
|
||||||
timestamps: true,
|
|
||||||
updatedAt: false,
|
|
||||||
modelName: 'collectionBook'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Super Many-to-Many
|
static init(sequelize) {
|
||||||
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
|
super.init({
|
||||||
const { book, collection } = sequelize.models
|
id: {
|
||||||
book.belongsToMany(collection, { through: CollectionBook })
|
type: DataTypes.UUID,
|
||||||
collection.belongsToMany(book, { through: CollectionBook })
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
order: DataTypes.INTEGER
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
timestamps: true,
|
||||||
|
updatedAt: false,
|
||||||
|
modelName: 'collectionBook'
|
||||||
|
})
|
||||||
|
|
||||||
book.hasMany(CollectionBook, {
|
// Super Many-to-Many
|
||||||
onDelete: 'CASCADE'
|
// 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
|
||||||
CollectionBook.belongsTo(book)
|
book.belongsToMany(collection, { through: CollectionBook })
|
||||||
|
collection.belongsToMany(book, { through: CollectionBook })
|
||||||
|
|
||||||
collection.hasMany(CollectionBook, {
|
book.hasMany(CollectionBook, {
|
||||||
onDelete: 'CASCADE'
|
onDelete: 'CASCADE'
|
||||||
})
|
})
|
||||||
CollectionBook.belongsTo(collection)
|
CollectionBook.belongsTo(book)
|
||||||
|
|
||||||
return CollectionBook
|
collection.hasMany(CollectionBook, {
|
||||||
}
|
onDelete: 'CASCADE'
|
||||||
|
})
|
||||||
|
CollectionBook.belongsTo(collection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CollectionBook
|
@ -1,116 +1,147 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
const oldDevice = require('../objects/DeviceInfo')
|
const oldDevice = require('../objects/DeviceInfo')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
class Device extends Model {
|
||||||
class Device extends Model {
|
constructor(values, options) {
|
||||||
getOldDevice() {
|
super(values, options)
|
||||||
let browserVersion = null
|
|
||||||
let sdkVersion = null
|
|
||||||
if (this.clientName === 'Abs Android') {
|
|
||||||
sdkVersion = this.deviceVersion || null
|
|
||||||
} else {
|
|
||||||
browserVersion = this.deviceVersion || null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new oldDevice({
|
/** @type {UUIDV4} */
|
||||||
id: this.id,
|
this.id
|
||||||
deviceId: this.deviceId,
|
/** @type {string} */
|
||||||
userId: this.userId,
|
this.deviceId
|
||||||
ipAddress: this.ipAddress,
|
/** @type {string} */
|
||||||
browserName: this.extraData.browserName || null,
|
this.clientName
|
||||||
browserVersion,
|
/** @type {string} */
|
||||||
osName: this.extraData.osName || null,
|
this.clientVersion
|
||||||
osVersion: this.extraData.osVersion || null,
|
/** @type {string} */
|
||||||
clientVersion: this.clientVersion || null,
|
this.ipAddress
|
||||||
manufacturer: this.extraData.manufacturer || null,
|
/** @type {string} */
|
||||||
model: this.extraData.model || null,
|
this.deviceName
|
||||||
sdkVersion,
|
/** @type {string} */
|
||||||
deviceName: this.deviceName,
|
this.deviceVersion
|
||||||
clientName: this.clientName
|
/** @type {object} */
|
||||||
})
|
this.extraData
|
||||||
|
/** @type {UUIDV4} */
|
||||||
|
this.userId
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
|
/** @type {Date} */
|
||||||
|
this.updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
getOldDevice() {
|
||||||
|
let browserVersion = null
|
||||||
|
let sdkVersion = null
|
||||||
|
if (this.clientName === 'Abs Android') {
|
||||||
|
sdkVersion = this.deviceVersion || null
|
||||||
|
} else {
|
||||||
|
browserVersion = this.deviceVersion || null
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getOldDeviceByDeviceId(deviceId) {
|
return new oldDevice({
|
||||||
const device = await this.findOne({
|
id: this.id,
|
||||||
where: {
|
deviceId: this.deviceId,
|
||||||
deviceId
|
userId: this.userId,
|
||||||
}
|
ipAddress: this.ipAddress,
|
||||||
})
|
browserName: this.extraData.browserName || null,
|
||||||
if (!device) return null
|
browserVersion,
|
||||||
return device.getOldDevice()
|
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({
|
||||||
|
where: {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
static createFromOld(oldDevice) {
|
return {
|
||||||
const device = this.getFromOld(oldDevice)
|
id: oldDeviceInfo.id,
|
||||||
return this.create(device)
|
deviceId: oldDeviceInfo.deviceId,
|
||||||
}
|
clientName: oldDeviceInfo.clientName || null,
|
||||||
|
clientVersion: oldDeviceInfo.clientVersion || null,
|
||||||
static updateFromOld(oldDevice) {
|
ipAddress: oldDeviceInfo.ipAddress,
|
||||||
const device = this.getFromOld(oldDevice)
|
deviceName: oldDeviceInfo.deviceName || null,
|
||||||
return this.update(device, {
|
deviceVersion: oldDeviceInfo.sdkVersion || oldDeviceInfo.browserVersion || null,
|
||||||
where: {
|
userId: oldDeviceInfo.userId,
|
||||||
id: device.id
|
extraData
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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: {
|
* Initialize model
|
||||||
type: DataTypes.UUID,
|
* @param {import('../Database').sequelize} sequelize
|
||||||
defaultValue: DataTypes.UUIDV4,
|
*/
|
||||||
primaryKey: true
|
static init(sequelize) {
|
||||||
},
|
super.init({
|
||||||
deviceId: DataTypes.STRING,
|
id: {
|
||||||
clientName: DataTypes.STRING, // e.g. Abs Web, Abs Android
|
type: DataTypes.UUID,
|
||||||
clientVersion: DataTypes.STRING, // e.g. Server version or mobile version
|
defaultValue: DataTypes.UUIDV4,
|
||||||
ipAddress: DataTypes.STRING,
|
primaryKey: true
|
||||||
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
|
deviceId: DataTypes.STRING,
|
||||||
extraData: DataTypes.JSON
|
clientName: DataTypes.STRING, // e.g. Abs Web, Abs Android
|
||||||
}, {
|
clientVersion: DataTypes.STRING, // e.g. Server version or mobile version
|
||||||
sequelize,
|
ipAddress: DataTypes.STRING,
|
||||||
modelName: 'device'
|
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
|
const { user } = sequelize.models
|
||||||
|
|
||||||
user.hasMany(Device, {
|
user.hasMany(Device, {
|
||||||
onDelete: 'CASCADE'
|
onDelete: 'CASCADE'
|
||||||
})
|
})
|
||||||
Device.belongsTo(user)
|
Device.belongsTo(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Device
|
module.exports = Device
|
||||||
}
|
|
@ -1,307 +1,361 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
const oldFeed = require('../objects/Feed')
|
const oldFeed = require('../objects/Feed')
|
||||||
const areEquivalent = require('../utils/areEquivalent')
|
const areEquivalent = require('../utils/areEquivalent')
|
||||||
/*
|
|
||||||
* Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
|
|
||||||
* Feeds can be created from LibraryItem, Collection, Playlist or Series
|
|
||||||
*/
|
|
||||||
module.exports = (sequelize) => {
|
|
||||||
class Feed extends Model {
|
|
||||||
static async getOldFeeds() {
|
|
||||||
const feeds = await this.findAll({
|
|
||||||
include: {
|
|
||||||
model: sequelize.models.feedEpisode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return feeds.map(f => this.getOldFeed(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
class Feed extends Model {
|
||||||
* Get old feed from Feed and optionally Feed with FeedEpisodes
|
constructor(values, options) {
|
||||||
* @param {Feed} feedExpanded
|
super(values, options)
|
||||||
* @returns {oldFeed}
|
|
||||||
*/
|
/** @type {UUIDV4} */
|
||||||
static getOldFeed(feedExpanded) {
|
this.id
|
||||||
const episodes = feedExpanded.feedEpisodes?.map((feedEpisode) => feedEpisode.getOldEpisode())
|
/** @type {string} */
|
||||||
return new oldFeed({
|
this.slug
|
||||||
id: feedExpanded.id,
|
/** @type {string} */
|
||||||
slug: feedExpanded.slug,
|
this.entityType
|
||||||
userId: feedExpanded.userId,
|
/** @type {UUIDV4} */
|
||||||
entityType: feedExpanded.entityType,
|
this.entityId
|
||||||
entityId: feedExpanded.entityId,
|
/** @type {Date} */
|
||||||
entityUpdatedAt: feedExpanded.entityUpdatedAt?.valueOf() || null,
|
this.entityUpdatedAt
|
||||||
coverPath: feedExpanded.coverPath || null,
|
/** @type {string} */
|
||||||
meta: {
|
this.serverAddress
|
||||||
title: feedExpanded.title,
|
/** @type {string} */
|
||||||
description: feedExpanded.description,
|
this.feedURL
|
||||||
author: feedExpanded.author,
|
/** @type {string} */
|
||||||
imageUrl: feedExpanded.imageURL,
|
this.imageURL
|
||||||
feedUrl: feedExpanded.feedURL,
|
/** @type {string} */
|
||||||
link: feedExpanded.siteURL,
|
this.siteURL
|
||||||
explicit: feedExpanded.explicit,
|
/** @type {string} */
|
||||||
type: feedExpanded.podcastType,
|
this.title
|
||||||
language: feedExpanded.language,
|
/** @type {string} */
|
||||||
preventIndexing: feedExpanded.preventIndexing,
|
this.description
|
||||||
ownerName: feedExpanded.ownerName,
|
/** @type {string} */
|
||||||
ownerEmail: feedExpanded.ownerEmail
|
this.author
|
||||||
},
|
/** @type {string} */
|
||||||
serverAddress: feedExpanded.serverAddress,
|
this.podcastType
|
||||||
|
/** @type {string} */
|
||||||
|
this.language
|
||||||
|
/** @type {string} */
|
||||||
|
this.ownerName
|
||||||
|
/** @type {string} */
|
||||||
|
this.ownerEmail
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.explicit
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.preventIndexing
|
||||||
|
/** @type {string} */
|
||||||
|
this.coverPath
|
||||||
|
/** @type {UUIDV4} */
|
||||||
|
this.userId
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
|
/** @type {Date} */
|
||||||
|
this.updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getOldFeeds() {
|
||||||
|
const feeds = await this.findAll({
|
||||||
|
include: {
|
||||||
|
model: this.sequelize.models.feedEpisode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return feeds.map(f => this.getOldFeed(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get old feed from Feed and optionally Feed with FeedEpisodes
|
||||||
|
* @param {Feed} feedExpanded
|
||||||
|
* @returns {oldFeed}
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
entityUpdatedAt: feedExpanded.entityUpdatedAt?.valueOf() || null,
|
||||||
|
coverPath: feedExpanded.coverPath || null,
|
||||||
|
meta: {
|
||||||
|
title: feedExpanded.title,
|
||||||
|
description: feedExpanded.description,
|
||||||
|
author: feedExpanded.author,
|
||||||
|
imageUrl: feedExpanded.imageURL,
|
||||||
feedUrl: feedExpanded.feedURL,
|
feedUrl: feedExpanded.feedURL,
|
||||||
episodes: episodes || [],
|
link: feedExpanded.siteURL,
|
||||||
createdAt: feedExpanded.createdAt.valueOf(),
|
explicit: feedExpanded.explicit,
|
||||||
updatedAt: feedExpanded.updatedAt.valueOf()
|
type: feedExpanded.podcastType,
|
||||||
})
|
language: feedExpanded.language,
|
||||||
}
|
preventIndexing: feedExpanded.preventIndexing,
|
||||||
|
ownerName: feedExpanded.ownerName,
|
||||||
|
ownerEmail: feedExpanded.ownerEmail
|
||||||
|
},
|
||||||
|
serverAddress: feedExpanded.serverAddress,
|
||||||
|
feedUrl: feedExpanded.feedURL,
|
||||||
|
episodes: episodes || [],
|
||||||
|
createdAt: feedExpanded.createdAt.valueOf(),
|
||||||
|
updatedAt: feedExpanded.updatedAt.valueOf()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static removeById(feedId) {
|
static removeById(feedId) {
|
||||||
return this.destroy({
|
return this.destroy({
|
||||||
where: {
|
where: {
|
||||||
id: feedId
|
id: feedId
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all library item ids that have an open feed (used in library filter)
|
|
||||||
* @returns {Promise<Array<String>>} array of library item ids
|
|
||||||
*/
|
|
||||||
static async findAllLibraryItemIds() {
|
|
||||||
const feeds = await this.findAll({
|
|
||||||
attributes: ['entityId'],
|
|
||||||
where: {
|
|
||||||
entityType: 'libraryItem'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return feeds.map(f => f.entityId).filter(f => f) || []
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find feed where and return oldFeed
|
|
||||||
* @param {object} where sequelize where object
|
|
||||||
* @returns {Promise<objects.Feed>} oldFeed
|
|
||||||
*/
|
|
||||||
static async findOneOld(where) {
|
|
||||||
if (!where) return null
|
|
||||||
const feedExpanded = await this.findOne({
|
|
||||||
where,
|
|
||||||
include: {
|
|
||||||
model: sequelize.models.feedEpisode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (!feedExpanded) return null
|
|
||||||
return this.getOldFeed(feedExpanded)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find feed and return oldFeed
|
|
||||||
* @param {string} id
|
|
||||||
* @returns {Promise<objects.Feed>} oldFeed
|
|
||||||
*/
|
|
||||||
static async findByPkOld(id) {
|
|
||||||
if (!id) return null
|
|
||||||
const feedExpanded = await this.findByPk(id, {
|
|
||||||
include: {
|
|
||||||
model: sequelize.models.feedEpisode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (!feedExpanded) return null
|
|
||||||
return this.getOldFeed(feedExpanded)
|
|
||||||
}
|
|
||||||
|
|
||||||
static async fullCreateFromOld(oldFeed) {
|
|
||||||
const feedObj = this.getFromOld(oldFeed)
|
|
||||||
const newFeed = await this.create(feedObj)
|
|
||||||
|
|
||||||
if (oldFeed.episodes?.length) {
|
|
||||||
for (const oldFeedEpisode of oldFeed.episodes) {
|
|
||||||
const feedEpisode = sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
|
|
||||||
feedEpisode.feedId = newFeed.id
|
|
||||||
await sequelize.models.feedEpisode.create(feedEpisode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static async fullUpdateFromOld(oldFeed) {
|
/**
|
||||||
const oldFeedEpisodes = oldFeed.episodes || []
|
* Find all library item ids that have an open feed (used in library filter)
|
||||||
const feedObj = this.getFromOld(oldFeed)
|
* @returns {Promise<Array<String>>} array of library item ids
|
||||||
|
*/
|
||||||
const existingFeed = await this.findByPk(feedObj.id, {
|
static async findAllLibraryItemIds() {
|
||||||
include: sequelize.models.feedEpisode
|
const feeds = await this.findAll({
|
||||||
})
|
attributes: ['entityId'],
|
||||||
if (!existingFeed) return false
|
where: {
|
||||||
|
entityType: 'libraryItem'
|
||||||
let hasUpdates = false
|
|
||||||
for (const feedEpisode of existingFeed.feedEpisodes) {
|
|
||||||
const oldFeedEpisode = oldFeedEpisodes.find(ep => ep.id === feedEpisode.id)
|
|
||||||
// Episode removed
|
|
||||||
if (!oldFeedEpisode) {
|
|
||||||
feedEpisode.destroy()
|
|
||||||
} else {
|
|
||||||
let episodeHasUpdates = false
|
|
||||||
const oldFeedEpisodeCleaned = sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
|
|
||||||
for (const key in oldFeedEpisodeCleaned) {
|
|
||||||
if (!areEquivalent(oldFeedEpisodeCleaned[key], feedEpisode[key])) {
|
|
||||||
episodeHasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (episodeHasUpdates) {
|
|
||||||
await feedEpisode.update(oldFeedEpisodeCleaned)
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return feeds.map(f => f.entityId).filter(f => f) || []
|
||||||
|
}
|
||||||
|
|
||||||
let feedHasUpdates = false
|
/**
|
||||||
for (const key in feedObj) {
|
* Find feed where and return oldFeed
|
||||||
let existingValue = existingFeed[key]
|
* @param {object} where sequelize where object
|
||||||
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
* @returns {Promise<objects.Feed>} oldFeed
|
||||||
|
*/
|
||||||
if (!areEquivalent(existingValue, feedObj[key])) {
|
static async findOneOld(where) {
|
||||||
feedHasUpdates = true
|
if (!where) return null
|
||||||
}
|
const feedExpanded = await this.findOne({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
model: this.sequelize.models.feedEpisode
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
if (!feedExpanded) return null
|
||||||
|
return this.getOldFeed(feedExpanded)
|
||||||
|
}
|
||||||
|
|
||||||
if (feedHasUpdates) {
|
/**
|
||||||
await existingFeed.update(feedObj)
|
* Find feed and return oldFeed
|
||||||
hasUpdates = true
|
* @param {string} id
|
||||||
|
* @returns {Promise<objects.Feed>} oldFeed
|
||||||
|
*/
|
||||||
|
static async findByPkOld(id) {
|
||||||
|
if (!id) return null
|
||||||
|
const feedExpanded = await this.findByPk(id, {
|
||||||
|
include: {
|
||||||
|
model: this.sequelize.models.feedEpisode
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
if (!feedExpanded) return null
|
||||||
|
return this.getOldFeed(feedExpanded)
|
||||||
|
}
|
||||||
|
|
||||||
return hasUpdates
|
static async fullCreateFromOld(oldFeed) {
|
||||||
}
|
const feedObj = this.getFromOld(oldFeed)
|
||||||
|
const newFeed = await this.create(feedObj)
|
||||||
|
|
||||||
static getFromOld(oldFeed) {
|
if (oldFeed.episodes?.length) {
|
||||||
const oldFeedMeta = oldFeed.meta || {}
|
for (const oldFeedEpisode of oldFeed.episodes) {
|
||||||
return {
|
const feedEpisode = this.sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
|
||||||
id: oldFeed.id,
|
feedEpisode.feedId = newFeed.id
|
||||||
slug: oldFeed.slug,
|
await this.sequelize.models.feedEpisode.create(feedEpisode)
|
||||||
entityType: oldFeed.entityType,
|
|
||||||
entityId: oldFeed.entityId,
|
|
||||||
entityUpdatedAt: oldFeed.entityUpdatedAt,
|
|
||||||
serverAddress: oldFeed.serverAddress,
|
|
||||||
feedURL: oldFeed.feedUrl,
|
|
||||||
coverPath: oldFeed.coverPath || null,
|
|
||||||
imageURL: oldFeedMeta.imageUrl,
|
|
||||||
siteURL: oldFeedMeta.link,
|
|
||||||
title: oldFeedMeta.title,
|
|
||||||
description: oldFeedMeta.description,
|
|
||||||
author: oldFeedMeta.author,
|
|
||||||
podcastType: oldFeedMeta.type || null,
|
|
||||||
language: oldFeedMeta.language || null,
|
|
||||||
ownerName: oldFeedMeta.ownerName || null,
|
|
||||||
ownerEmail: oldFeedMeta.ownerEmail || null,
|
|
||||||
explicit: !!oldFeedMeta.explicit,
|
|
||||||
preventIndexing: !!oldFeedMeta.preventIndexing,
|
|
||||||
userId: oldFeed.userId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntity(options) {
|
|
||||||
if (!this.entityType) return Promise.resolve(null)
|
|
||||||
const mixinMethodName = `get${sequelize.uppercaseFirst(this.entityType)}`
|
|
||||||
return this[mixinMethodName](options)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Feed.init({
|
static async fullUpdateFromOld(oldFeed) {
|
||||||
id: {
|
const oldFeedEpisodes = oldFeed.episodes || []
|
||||||
type: DataTypes.UUID,
|
const feedObj = this.getFromOld(oldFeed)
|
||||||
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,
|
|
||||||
coverPath: DataTypes.STRING
|
|
||||||
}, {
|
|
||||||
sequelize,
|
|
||||||
modelName: 'feed'
|
|
||||||
})
|
|
||||||
|
|
||||||
const { user, libraryItem, collection, series, playlist } = sequelize.models
|
const existingFeed = await this.findByPk(feedObj.id, {
|
||||||
|
include: this.sequelize.models.feedEpisode
|
||||||
|
})
|
||||||
|
if (!existingFeed) return false
|
||||||
|
|
||||||
user.hasMany(Feed)
|
let hasUpdates = false
|
||||||
Feed.belongsTo(user)
|
for (const feedEpisode of existingFeed.feedEpisodes) {
|
||||||
|
const oldFeedEpisode = oldFeedEpisodes.find(ep => ep.id === feedEpisode.id)
|
||||||
libraryItem.hasMany(Feed, {
|
// Episode removed
|
||||||
foreignKey: 'entityId',
|
if (!oldFeedEpisode) {
|
||||||
constraints: false,
|
feedEpisode.destroy()
|
||||||
scope: {
|
} else {
|
||||||
entityType: 'libraryItem'
|
let episodeHasUpdates = false
|
||||||
}
|
const oldFeedEpisodeCleaned = this.sequelize.models.feedEpisode.getFromOld(oldFeedEpisode)
|
||||||
})
|
for (const key in oldFeedEpisodeCleaned) {
|
||||||
Feed.belongsTo(libraryItem, { foreignKey: 'entityId', constraints: false })
|
if (!areEquivalent(oldFeedEpisodeCleaned[key], feedEpisode[key])) {
|
||||||
|
episodeHasUpdates = true
|
||||||
collection.hasMany(Feed, {
|
}
|
||||||
foreignKey: 'entityId',
|
}
|
||||||
constraints: false,
|
if (episodeHasUpdates) {
|
||||||
scope: {
|
await feedEpisode.update(oldFeedEpisodeCleaned)
|
||||||
entityType: 'collection'
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
})
|
|
||||||
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
|
let feedHasUpdates = false
|
||||||
}
|
for (const key in feedObj) {
|
||||||
|
let existingValue = existingFeed[key]
|
||||||
|
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
||||||
|
|
||||||
|
if (!areEquivalent(existingValue, feedObj[key])) {
|
||||||
|
feedHasUpdates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feedHasUpdates) {
|
||||||
|
await existingFeed.update(feedObj)
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
static getFromOld(oldFeed) {
|
||||||
|
const oldFeedMeta = oldFeed.meta || {}
|
||||||
|
return {
|
||||||
|
id: oldFeed.id,
|
||||||
|
slug: oldFeed.slug,
|
||||||
|
entityType: oldFeed.entityType,
|
||||||
|
entityId: oldFeed.entityId,
|
||||||
|
entityUpdatedAt: oldFeed.entityUpdatedAt,
|
||||||
|
serverAddress: oldFeed.serverAddress,
|
||||||
|
feedURL: oldFeed.feedUrl,
|
||||||
|
coverPath: oldFeed.coverPath || null,
|
||||||
|
imageURL: oldFeedMeta.imageUrl,
|
||||||
|
siteURL: oldFeedMeta.link,
|
||||||
|
title: oldFeedMeta.title,
|
||||||
|
description: oldFeedMeta.description,
|
||||||
|
author: oldFeedMeta.author,
|
||||||
|
podcastType: oldFeedMeta.type || null,
|
||||||
|
language: oldFeedMeta.language || null,
|
||||||
|
ownerName: oldFeedMeta.ownerName || null,
|
||||||
|
ownerEmail: oldFeedMeta.ownerEmail || null,
|
||||||
|
explicit: !!oldFeedMeta.explicit,
|
||||||
|
preventIndexing: !!oldFeedMeta.preventIndexing,
|
||||||
|
userId: oldFeed.userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntity(options) {
|
||||||
|
if (!this.entityType) return Promise.resolve(null)
|
||||||
|
const mixinMethodName = `get${this.sequelize.uppercaseFirst(this.entityType)}`
|
||||||
|
return this[mixinMethodName](options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize model
|
||||||
|
*
|
||||||
|
* Polymorphic association: Feeds can be created from LibraryItem, Collection, Playlist or Series
|
||||||
|
* @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
|
||||||
|
*
|
||||||
|
* @param {import('../Database').sequelize} sequelize
|
||||||
|
*/
|
||||||
|
static init(sequelize) {
|
||||||
|
super.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
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,
|
||||||
|
coverPath: DataTypes.STRING
|
||||||
|
}, {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Feed
|
@ -1,82 +1,125 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
class FeedEpisode extends Model {
|
||||||
class FeedEpisode extends Model {
|
constructor(values, options) {
|
||||||
getOldEpisode() {
|
super(values, options)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromOld(oldFeedEpisode) {
|
/** @type {UUIDV4} */
|
||||||
return {
|
this.id
|
||||||
id: oldFeedEpisode.id,
|
/** @type {string} */
|
||||||
title: oldFeedEpisode.title,
|
this.title
|
||||||
author: oldFeedEpisode.author,
|
/** @type {string} */
|
||||||
description: oldFeedEpisode.description,
|
this.description
|
||||||
siteURL: oldFeedEpisode.link,
|
/** @type {string} */
|
||||||
enclosureURL: oldFeedEpisode.enclosure?.url || null,
|
this.siteURL
|
||||||
enclosureType: oldFeedEpisode.enclosure?.type || null,
|
/** @type {string} */
|
||||||
enclosureSize: oldFeedEpisode.enclosure?.size || null,
|
this.enclosureURL
|
||||||
pubDate: oldFeedEpisode.pubDate,
|
/** @type {string} */
|
||||||
season: oldFeedEpisode.season || null,
|
this.enclosureType
|
||||||
episode: oldFeedEpisode.episode || null,
|
/** @type {BigInt} */
|
||||||
episodeType: oldFeedEpisode.episodeType || null,
|
this.enclosureSize
|
||||||
duration: oldFeedEpisode.duration,
|
/** @type {string} */
|
||||||
filePath: oldFeedEpisode.fullPath,
|
this.pubDate
|
||||||
explicit: !!oldFeedEpisode.explicit
|
/** @type {string} */
|
||||||
}
|
this.season
|
||||||
|
/** @type {string} */
|
||||||
|
this.episode
|
||||||
|
/** @type {string} */
|
||||||
|
this.episodeType
|
||||||
|
/** @type {number} */
|
||||||
|
this.duration
|
||||||
|
/** @type {string} */
|
||||||
|
this.filePath
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.explicit
|
||||||
|
/** @type {UUIDV4} */
|
||||||
|
this.feedId
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
|
/** @type {Date} */
|
||||||
|
this.updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
getOldEpisode() {
|
||||||
|
const enclosure = {
|
||||||
|
url: this.enclosureURL,
|
||||||
|
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({
|
static getFromOld(oldFeedEpisode) {
|
||||||
id: {
|
return {
|
||||||
type: DataTypes.UUID,
|
id: oldFeedEpisode.id,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
title: oldFeedEpisode.title,
|
||||||
primaryKey: true
|
author: oldFeedEpisode.author,
|
||||||
},
|
description: oldFeedEpisode.description,
|
||||||
title: DataTypes.STRING,
|
siteURL: oldFeedEpisode.link,
|
||||||
author: DataTypes.STRING,
|
enclosureURL: oldFeedEpisode.enclosure?.url || null,
|
||||||
description: DataTypes.TEXT,
|
enclosureType: oldFeedEpisode.enclosure?.type || null,
|
||||||
siteURL: DataTypes.STRING,
|
enclosureSize: oldFeedEpisode.enclosure?.size || null,
|
||||||
enclosureURL: DataTypes.STRING,
|
pubDate: oldFeedEpisode.pubDate,
|
||||||
enclosureType: DataTypes.STRING,
|
season: oldFeedEpisode.season || null,
|
||||||
enclosureSize: DataTypes.BIGINT,
|
episode: oldFeedEpisode.episode || null,
|
||||||
pubDate: DataTypes.STRING,
|
episodeType: oldFeedEpisode.episodeType || null,
|
||||||
season: DataTypes.STRING,
|
duration: oldFeedEpisode.duration,
|
||||||
episode: DataTypes.STRING,
|
filePath: oldFeedEpisode.fullPath,
|
||||||
episodeType: DataTypes.STRING,
|
explicit: !!oldFeedEpisode.explicit
|
||||||
duration: DataTypes.FLOAT,
|
}
|
||||||
filePath: DataTypes.STRING,
|
}
|
||||||
explicit: DataTypes.BOOLEAN
|
|
||||||
}, {
|
|
||||||
sequelize,
|
|
||||||
modelName: 'feedEpisode'
|
|
||||||
})
|
|
||||||
|
|
||||||
const { feed } = sequelize.models
|
/**
|
||||||
|
* Initialize model
|
||||||
|
* @param {import('../Database').sequelize} sequelize
|
||||||
|
*/
|
||||||
|
static init(sequelize) {
|
||||||
|
super.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'
|
||||||
|
})
|
||||||
|
|
||||||
feed.hasMany(FeedEpisode, {
|
const { feed } = sequelize.models
|
||||||
onDelete: 'CASCADE'
|
|
||||||
})
|
|
||||||
FeedEpisode.belongsTo(feed)
|
|
||||||
|
|
||||||
return FeedEpisode
|
feed.hasMany(FeedEpisode, {
|
||||||
}
|
onDelete: 'CASCADE'
|
||||||
|
})
|
||||||
|
FeedEpisode.belongsTo(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FeedEpisode
|
@ -2,217 +2,251 @@ const { DataTypes, Model } = require('sequelize')
|
|||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const oldLibrary = require('../objects/Library')
|
const oldLibrary = require('../objects/Library')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
|
||||||
class Library extends Model {
|
|
||||||
/**
|
|
||||||
* Get all old libraries
|
|
||||||
* @returns {Promise<oldLibrary[]>}
|
|
||||||
*/
|
|
||||||
static async getAllOldLibraries() {
|
|
||||||
const libraries = await this.findAll({
|
|
||||||
include: sequelize.models.libraryFolder,
|
|
||||||
order: [['displayOrder', 'ASC']]
|
|
||||||
})
|
|
||||||
return libraries.map(lib => this.getOldLibrary(lib))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
class Library extends Model {
|
||||||
* Convert expanded Library to oldLibrary
|
constructor(values, options) {
|
||||||
* @param {Library} libraryExpanded
|
super(values, options)
|
||||||
* @returns {Promise<oldLibrary>}
|
|
||||||
*/
|
|
||||||
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,
|
|
||||||
oldLibraryId: libraryExpanded.extraData?.oldLibraryId || null,
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** @type {UUIDV4} */
|
||||||
* @param {object} oldLibrary
|
this.id
|
||||||
* @returns {Library|null}
|
/** @type {string} */
|
||||||
*/
|
this.name
|
||||||
static async createFromOld(oldLibrary) {
|
/** @type {number} */
|
||||||
const library = this.getFromOld(oldLibrary)
|
this.displayOrder
|
||||||
|
/** @type {string} */
|
||||||
|
this.icon
|
||||||
|
/** @type {string} */
|
||||||
|
this.mediaType
|
||||||
|
/** @type {string} */
|
||||||
|
this.provider
|
||||||
|
/** @type {Date} */
|
||||||
|
this.lastScan
|
||||||
|
/** @type {string} */
|
||||||
|
this.lastScanVersion
|
||||||
|
/** @type {Object} */
|
||||||
|
this.settings
|
||||||
|
/** @type {Object} */
|
||||||
|
this.extraData
|
||||||
|
/** @type {Date} */
|
||||||
|
this.createdAt
|
||||||
|
/** @type {Date} */
|
||||||
|
this.updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
library.libraryFolders = oldLibrary.folders.map(folder => {
|
/**
|
||||||
return {
|
* Get all old libraries
|
||||||
id: folder.id,
|
* @returns {Promise<oldLibrary[]>}
|
||||||
path: folder.fullPath
|
*/
|
||||||
}
|
static async getAllOldLibraries() {
|
||||||
})
|
const libraries = await this.findAll({
|
||||||
|
include: this.sequelize.models.libraryFolder,
|
||||||
|
order: [['displayOrder', 'ASC']]
|
||||||
|
})
|
||||||
|
return libraries.map(lib => this.getOldLibrary(lib))
|
||||||
|
}
|
||||||
|
|
||||||
return this.create(library, {
|
/**
|
||||||
include: sequelize.models.libraryFolder
|
* Convert expanded Library to oldLibrary
|
||||||
}).catch((error) => {
|
* @param {Library} libraryExpanded
|
||||||
Logger.error(`[Library] Failed to create library ${library.id}`, error)
|
* @returns {Promise<oldLibrary>}
|
||||||
return null
|
*/
|
||||||
})
|
static getOldLibrary(libraryExpanded) {
|
||||||
}
|
const folders = libraryExpanded.libraryFolders.map(folder => {
|
||||||
|
|
||||||
/**
|
|
||||||
* Update library and library folders
|
|
||||||
* @param {object} oldLibrary
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
const extraData = {}
|
|
||||||
if (oldLibrary.oldLibraryId) {
|
|
||||||
extraData.oldLibraryId = oldLibrary.oldLibraryId
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
id: oldLibrary.id,
|
id: folder.id,
|
||||||
name: oldLibrary.name,
|
fullPath: folder.path,
|
||||||
displayOrder: oldLibrary.displayOrder,
|
libraryId: folder.libraryId,
|
||||||
icon: oldLibrary.icon || null,
|
addedAt: folder.createdAt.valueOf()
|
||||||
mediaType: oldLibrary.mediaType || null,
|
}
|
||||||
provider: oldLibrary.provider,
|
})
|
||||||
settings: oldLibrary.settings?.toJSON() || {},
|
return new oldLibrary({
|
||||||
createdAt: oldLibrary.createdAt,
|
id: libraryExpanded.id,
|
||||||
updatedAt: oldLibrary.lastUpdate,
|
oldLibraryId: libraryExpanded.extraData?.oldLibraryId || null,
|
||||||
extraData
|
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 async createFromOld(oldLibrary) {
|
||||||
|
const library = this.getFromOld(oldLibrary)
|
||||||
|
|
||||||
|
library.libraryFolders = oldLibrary.folders.map(folder => {
|
||||||
|
return {
|
||||||
|
id: folder.id,
|
||||||
|
path: folder.fullPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.create(library, {
|
||||||
|
include: this.sequelize.models.libraryFolder
|
||||||
|
}).catch((error) => {
|
||||||
|
Logger.error(`[Library] Failed to create library ${library.id}`, error)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update library and library folders
|
||||||
|
* @param {object} oldLibrary
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static async updateFromOld(oldLibrary) {
|
||||||
|
const existingLibrary = await this.findByPk(oldLibrary.id, {
|
||||||
|
include: this.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 this.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))
|
||||||
* Destroy library by id
|
for (const existingLibraryFolder of libraryFoldersRemoved) {
|
||||||
* @param {string} libraryId
|
await existingLibraryFolder.destroy()
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static removeById(libraryId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
id: libraryId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return existingLibrary.update(library)
|
||||||
* Get all library ids
|
}
|
||||||
* @returns {Promise<string[]>} array of library ids
|
|
||||||
*/
|
|
||||||
static async getAllLibraryIds() {
|
|
||||||
const libraries = await this.findAll({
|
|
||||||
attributes: ['id', 'displayOrder'],
|
|
||||||
order: [['displayOrder', 'ASC']]
|
|
||||||
})
|
|
||||||
return libraries.map(l => l.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
static getFromOld(oldLibrary) {
|
||||||
* Find Library by primary key & return oldLibrary
|
const extraData = {}
|
||||||
* @param {string} libraryId
|
if (oldLibrary.oldLibraryId) {
|
||||||
* @returns {Promise<oldLibrary|null>} Returns null if not found
|
extraData.oldLibraryId = oldLibrary.oldLibraryId
|
||||||
*/
|
|
||||||
static async getOldById(libraryId) {
|
|
||||||
if (!libraryId) return null
|
|
||||||
const library = await this.findByPk(libraryId, {
|
|
||||||
include: sequelize.models.libraryFolder
|
|
||||||
})
|
|
||||||
if (!library) return null
|
|
||||||
return this.getOldLibrary(library)
|
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
/**
|
id: oldLibrary.id,
|
||||||
* Get the largest value in the displayOrder column
|
name: oldLibrary.name,
|
||||||
* Used for setting a new libraries display order
|
displayOrder: oldLibrary.displayOrder,
|
||||||
* @returns {Promise<number>}
|
icon: oldLibrary.icon || null,
|
||||||
*/
|
mediaType: oldLibrary.mediaType || null,
|
||||||
static getMaxDisplayOrder() {
|
provider: oldLibrary.provider,
|
||||||
return this.max('displayOrder') || 0
|
settings: oldLibrary.settings?.toJSON() || {},
|
||||||
|
createdAt: oldLibrary.createdAt,
|
||||||
|
updatedAt: oldLibrary.lastUpdate,
|
||||||
|
extraData
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates displayOrder to be sequential
|
* Destroy library by id
|
||||||
* Used after removing a library
|
* @param {string} libraryId
|
||||||
*/
|
* @returns
|
||||||
static async resetDisplayOrder() {
|
*/
|
||||||
const libraries = await this.findAll({
|
static removeById(libraryId) {
|
||||||
order: [['displayOrder', 'ASC']]
|
return this.destroy({
|
||||||
})
|
where: {
|
||||||
for (let i = 0; i < libraries.length; i++) {
|
id: libraryId
|
||||||
const library = libraries[i]
|
}
|
||||||
if (library.displayOrder !== i + 1) {
|
})
|
||||||
Logger.dev(`[Library] Updating display order of library from ${library.displayOrder} to ${i + 1}`)
|
}
|
||||||
await library.update({ displayOrder: i + 1 }).catch((error) => {
|
|
||||||
Logger.error(`[Library] Failed to update library display order to ${i + 1}`, error)
|
/**
|
||||||
})
|
* Get all library ids
|
||||||
}
|
* @returns {Promise<string[]>} array of library ids
|
||||||
|
*/
|
||||||
|
static async getAllLibraryIds() {
|
||||||
|
const libraries = await this.findAll({
|
||||||
|
attributes: ['id', 'displayOrder'],
|
||||||
|
order: [['displayOrder', 'ASC']]
|
||||||
|
})
|
||||||
|
return libraries.map(l => l.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find Library by primary key & return oldLibrary
|
||||||
|
* @param {string} libraryId
|
||||||
|
* @returns {Promise<oldLibrary|null>} Returns null if not found
|
||||||
|
*/
|
||||||
|
static async getOldById(libraryId) {
|
||||||
|
if (!libraryId) return null
|
||||||
|
const library = await this.findByPk(libraryId, {
|
||||||
|
include: sequelize.models.libraryFolder
|
||||||
|
})
|
||||||
|
if (!library) return null
|
||||||
|
return this.getOldLibrary(library)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the largest value in the displayOrder column
|
||||||
|
* Used for setting a new libraries display order
|
||||||
|
* @returns {Promise<number>}
|
||||||
|
*/
|
||||||
|
static getMaxDisplayOrder() {
|
||||||
|
return this.max('displayOrder') || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates displayOrder to be sequential
|
||||||
|
* Used after removing a library
|
||||||
|
*/
|
||||||
|
static async resetDisplayOrder() {
|
||||||
|
const libraries = await this.findAll({
|
||||||
|
order: [['displayOrder', 'ASC']]
|
||||||
|
})
|
||||||
|
for (let i = 0; i < libraries.length; i++) {
|
||||||
|
const library = libraries[i]
|
||||||
|
if (library.displayOrder !== i + 1) {
|
||||||
|
Logger.dev(`[Library] Updating display order of library from ${library.displayOrder} to ${i + 1}`)
|
||||||
|
await library.update({ displayOrder: i + 1 }).catch((error) => {
|
||||||
|
Logger.error(`[Library] Failed to update library display order to ${i + 1}`, error)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Library.init({
|
/**
|
||||||
id: {
|
* Initialize model
|
||||||
type: DataTypes.UUID,
|
* @param {import('../Database').sequelize} sequelize
|
||||||
defaultValue: DataTypes.UUIDV4,
|
*/
|
||||||
primaryKey: true
|
static init(sequelize) {
|
||||||
},
|
super.init({
|
||||||
name: DataTypes.STRING,
|
id: {
|
||||||
displayOrder: DataTypes.INTEGER,
|
type: DataTypes.UUID,
|
||||||
icon: DataTypes.STRING,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
mediaType: DataTypes.STRING,
|
primaryKey: true
|
||||||
provider: DataTypes.STRING,
|
},
|
||||||
lastScan: DataTypes.DATE,
|
name: DataTypes.STRING,
|
||||||
lastScanVersion: DataTypes.STRING,
|
displayOrder: DataTypes.INTEGER,
|
||||||
settings: DataTypes.JSON,
|
icon: DataTypes.STRING,
|
||||||
extraData: DataTypes.JSON
|
mediaType: DataTypes.STRING,
|
||||||
}, {
|
provider: DataTypes.STRING,
|
||||||
sequelize,
|
lastScan: DataTypes.DATE,
|
||||||
modelName: 'library'
|
lastScanVersion: DataTypes.STRING,
|
||||||
})
|
settings: DataTypes.JSON,
|
||||||
|
extraData: DataTypes.JSON
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'library'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Library
|
module.exports = Library
|
||||||
}
|
|
@ -1,36 +1,55 @@
|
|||||||
const { DataTypes, Model } = require('sequelize')
|
const { DataTypes, Model } = require('sequelize')
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
class LibraryFolder extends Model {
|
||||||
class LibraryFolder extends Model {
|
constructor(values, options) {
|
||||||
/**
|
super(values, options)
|
||||||
* Gets all library folder path strings
|
|
||||||
* @returns {Promise<string[]>} array of library folder paths
|
/** @type {UUIDV4} */
|
||||||
*/
|
this.id
|
||||||
static async getAllLibraryFolderPaths() {
|
/** @type {string} */
|
||||||
const libraryFolders = await this.findAll({
|
this.path
|
||||||
attributes: ['path']
|
/** @type {UUIDV4} */
|
||||||
})
|
this.libraryId
|
||||||
return libraryFolders.map(l => l.path)
|
/** @type {Date} */
|
||||||
}
|
this.createdAt
|
||||||
|
/** @type {Date} */
|
||||||
|
this.updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
LibraryFolder.init({
|
/**
|
||||||
id: {
|
* Gets all library folder path strings
|
||||||
type: DataTypes.UUID,
|
* @returns {Promise<string[]>} array of library folder paths
|
||||||
defaultValue: DataTypes.UUIDV4,
|
*/
|
||||||
primaryKey: true
|
static async getAllLibraryFolderPaths() {
|
||||||
},
|
const libraryFolders = await this.findAll({
|
||||||
path: DataTypes.STRING
|
attributes: ['path']
|
||||||
}, {
|
})
|
||||||
sequelize,
|
return libraryFolders.map(l => l.path)
|
||||||
modelName: 'libraryFolder'
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const { library } = sequelize.models
|
/**
|
||||||
library.hasMany(LibraryFolder, {
|
* Initialize model
|
||||||
onDelete: 'CASCADE'
|
* @param {import('../Database').sequelize} sequelize
|
||||||
})
|
*/
|
||||||
LibraryFolder.belongsTo(library)
|
static init(sequelize) {
|
||||||
|
super.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
path: DataTypes.STRING
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'libraryFolder'
|
||||||
|
})
|
||||||
|
|
||||||
return LibraryFolder
|
const { library } = sequelize.models
|
||||||
}
|
library.hasMany(LibraryFolder, {
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
})
|
||||||
|
LibraryFolder.belongsTo(library)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = LibraryFolder
|
Loading…
Reference in New Issue
Block a user