From bbf324ea83acaf0f7f631053ac8a8cd80e11c1e9 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 12 Mar 2023 14:51:45 -0500 Subject: [PATCH] Adding more models --- server/Database.js | 25 +++++++++++ server/models/AudioBookmark.js | 73 ++++++++++++++++++++++++++++++++ server/models/AudioTrack.js | 71 +++++++++++++++++++++++++++++++ server/models/Book.js | 40 ++++++++++++++++++ server/models/BookAuthor.js | 31 ++++++++++++++ server/models/BookChapter.js | 27 ++++++++++++ server/models/BookGenre.js | 31 ++++++++++++++ server/models/BookNarrator.js | 31 ++++++++++++++ server/models/BookSeries.js | 32 ++++++++++++++ server/models/BookTag.js | 31 ++++++++++++++ server/models/Collection.js | 25 +++++++++++ server/models/CollectionBook.js | 30 +++++++++++++ server/models/EBookFile.js | 23 ++++++++++ server/models/FileMetadata.js | 26 ++++++++++++ server/models/Genre.js | 19 +++++++++ server/models/Library.js | 25 +++++++++++ server/models/LibraryFile.js | 25 +++++++++++ server/models/LibraryFolder.js | 23 ++++++++++ server/models/LibraryItem.js | 34 +++++++++++++++ server/models/MediaProgress.js | 75 +++++++++++++++++++++++++++++++++ server/models/Person.js | 26 ++++++++++++ server/models/Podcast.js | 46 ++++++++++++++++++++ server/models/PodcastEpisode.js | 34 +++++++++++++++ server/models/PodcastTag.js | 31 ++++++++++++++ server/models/Series.js | 20 +++++++++ server/models/Tag.js | 19 +++++++++ server/models/User.js | 13 +++++- 27 files changed, 885 insertions(+), 1 deletion(-) create mode 100644 server/models/AudioBookmark.js create mode 100644 server/models/AudioTrack.js create mode 100644 server/models/Book.js create mode 100644 server/models/BookAuthor.js create mode 100644 server/models/BookChapter.js create mode 100644 server/models/BookGenre.js create mode 100644 server/models/BookNarrator.js create mode 100644 server/models/BookSeries.js create mode 100644 server/models/BookTag.js create mode 100644 server/models/Collection.js create mode 100644 server/models/CollectionBook.js create mode 100644 server/models/EBookFile.js create mode 100644 server/models/FileMetadata.js create mode 100644 server/models/Genre.js create mode 100644 server/models/Library.js create mode 100644 server/models/LibraryFile.js create mode 100644 server/models/LibraryFolder.js create mode 100644 server/models/LibraryItem.js create mode 100644 server/models/MediaProgress.js create mode 100644 server/models/Person.js create mode 100644 server/models/Podcast.js create mode 100644 server/models/PodcastEpisode.js create mode 100644 server/models/PodcastTag.js create mode 100644 server/models/Series.js create mode 100644 server/models/Tag.js diff --git a/server/Database.js b/server/Database.js index d5409687..7e119a99 100644 --- a/server/Database.js +++ b/server/Database.js @@ -37,6 +37,31 @@ class Database { buildModels() { require('./models/User')(this.sequelize) + require('./models/FileMetadata')(this.sequelize) + require('./models/Library')(this.sequelize) + require('./models/LibraryFolder')(this.sequelize) + require('./models/LibraryItem')(this.sequelize) + require('./models/EBookFile')(this.sequelize) + require('./models/Book')(this.sequelize) + require('./models/Podcast')(this.sequelize) + require('./models/PodcastEpisode')(this.sequelize) + require('./models/MediaProgress')(this.sequelize) + require('./models/LibraryFile')(this.sequelize) + require('./models/Person')(this.sequelize) + require('./models/AudioBookmark')(this.sequelize) + require('./models/AudioTrack')(this.sequelize) + require('./models/BookAuthor')(this.sequelize) + require('./models/BookChapter')(this.sequelize) + require('./models/Genre')(this.sequelize) + require('./models/BookGenre')(this.sequelize) + require('./models/BookNarrator')(this.sequelize) + require('./models/Series')(this.sequelize) + require('./models/BookSeries')(this.sequelize) + require('./models/Tag')(this.sequelize) + require('./models/BookTag')(this.sequelize) + require('./models/PodcastTag')(this.sequelize) + require('./models/Collection')(this.sequelize) + require('./models/CollectionBook')(this.sequelize) return this.sequelize.sync() } diff --git a/server/models/AudioBookmark.js b/server/models/AudioBookmark.js new file mode 100644 index 00000000..7cb3f9a7 --- /dev/null +++ b/server/models/AudioBookmark.js @@ -0,0 +1,73 @@ +const { DataTypes, Model } = require('sequelize') + +const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}` + +/* + * Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ + * Book has many AudioBookmark. PodcastEpisode has many AudioBookmark. + */ +module.exports = (sequelize) => { + class AudioBookmark extends Model { + getMediaItem(options) { + if (!this.mediaItemType) return Promise.resolve(null) + const mixinMethodName = `get${uppercaseFirst(this.mediaItemType)}` + return this[mixinMethodName](options) + } + } + + AudioBookmark.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + mediaItemId: DataTypes.UUIDV4, + mediaItemType: DataTypes.STRING, + title: DataTypes.STRING, + time: DataTypes.INTEGER + }, { + sequelize, + modelName: 'AudioBookmark' + }) + + const { User, Book, PodcastEpisode } = sequelize.models + Book.hasMany(AudioBookmark, { + foreignKey: 'mediaItemId', + constraints: false, + scope: { + mediaItemType: 'book' + } + }) + AudioBookmark.belongsTo(Book, { foreignKey: 'mediaItemId', constraints: false }) + + PodcastEpisode.hasMany(AudioBookmark, { + foreignKey: 'mediaItemId', + constraints: false, + scope: { + mediaItemType: 'podcastEpisode' + } + }) + AudioBookmark.belongsTo(PodcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) + + AudioBookmark.addHook('afterFind', findResult => { + if (!Array.isArray(findResult)) findResult = [findResult] + for (const instance of findResult) { + if (instance.mediaItemType === 'book' && instance.book !== undefined) { + instance.mediaItem = instance.book + } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) { + instance.mediaItem = instance.podcastEpisode + } + // To prevent mistakes: + delete instance.book + delete instance.dataValues.book + delete instance.podcastEpisode + delete instance.dataValues.podcastEpisode + } + }) + + + User.hasMany(AudioBookmark) + AudioBookmark.belongsTo(User) + + return AudioBookmark +} \ No newline at end of file diff --git a/server/models/AudioTrack.js b/server/models/AudioTrack.js new file mode 100644 index 00000000..d4c5ea7b --- /dev/null +++ b/server/models/AudioTrack.js @@ -0,0 +1,71 @@ +const { DataTypes, Model } = require('sequelize') + +const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}` + +/* + * Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ + * Book has many AudioTrack. PodcastEpisode has one AudioTrack. + */ +module.exports = (sequelize) => { + class AudioTrack extends Model { + getMediaItem(options) { + if (!this.mediaItemType) return Promise.resolve(null) + const mixinMethodName = `get${uppercaseFirst(this.mediaItemType)}` + return this[mixinMethodName](options) + } + } + + AudioTrack.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + mediaItemId: DataTypes.UUIDV4, + mediaItemType: DataTypes.STRING, + index: DataTypes.INTEGER + }, { + sequelize, + modelName: 'AudioTrack' + }) + + const { Book, PodcastEpisode, FileMetadata } = sequelize.models + Book.hasMany(AudioTrack, { + foreignKey: 'mediaItemId', + constraints: false, + scope: { + mediaItemType: 'book' + } + }) + AudioTrack.belongsTo(Book, { foreignKey: 'mediaItemId', constraints: false }) + + PodcastEpisode.hasOne(AudioTrack, { + foreignKey: 'mediaItemId', + constraints: false, + scope: { + mediaItemType: 'podcastEpisode' + } + }) + AudioTrack.belongsTo(PodcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) + + AudioTrack.addHook('afterFind', findResult => { + if (!Array.isArray(findResult)) findResult = [findResult] + for (const instance of findResult) { + if (instance.mediaItemType === 'book' && instance.book !== undefined) { + instance.mediaItem = instance.book + } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) { + instance.mediaItem = instance.podcastEpisode + } + // To prevent mistakes: + delete instance.book + delete instance.dataValues.book + delete instance.podcastEpisode + delete instance.dataValues.podcastEpisode + } + }) + + FileMetadata.hasOne(AudioTrack) + AudioTrack.belongsTo(FileMetadata) + + return AudioTrack +} \ No newline at end of file diff --git a/server/models/Book.js b/server/models/Book.js new file mode 100644 index 00000000..a5f6bb68 --- /dev/null +++ b/server/models/Book.js @@ -0,0 +1,40 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Book extends Model { } + + Book.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + title: DataTypes.STRING, + subtitle: DataTypes.STRING, + publishedYear: DataTypes.STRING, + publishedDate: DataTypes.STRING, + publisher: DataTypes.STRING, + description: DataTypes.TEXT, + isbn: DataTypes.STRING, + asin: DataTypes.STRING, + language: DataTypes.STRING, + explicit: DataTypes.BOOLEAN, + lastCoverSearchQuery: DataTypes.STRING, + lastCoverSearch: DataTypes.DATE + }, { + sequelize, + modelName: 'Book' + }) + + const { LibraryItem, FileMetadata, EBookFile } = sequelize.models + LibraryItem.hasOne(Book) + Book.belongsTo(LibraryItem) + + FileMetadata.hasOne(Book) + Book.belongsTo(FileMetadata, { as: 'ImageFile' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias + + EBookFile.hasOne(Book) + Book.belongsTo(EBookFile) + + return Book +} \ No newline at end of file diff --git a/server/models/BookAuthor.js b/server/models/BookAuthor.js new file mode 100644 index 00000000..686c8e89 --- /dev/null +++ b/server/models/BookAuthor.js @@ -0,0 +1,31 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class BookAuthor extends Model { } + + BookAuthor.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'BookAuthor', + timestamps: false + }) + + // Super Many-to-Many + // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship + const { Book, Person } = sequelize.models + Book.belongsToMany(Person, { through: BookAuthor }) + Person.belongsToMany(Book, { through: BookAuthor }) + + Book.hasMany(BookAuthor) + BookAuthor.belongsTo(Book) + + Person.hasMany(BookAuthor) + BookAuthor.belongsTo(Person) + + return BookAuthor +} \ No newline at end of file diff --git a/server/models/BookChapter.js b/server/models/BookChapter.js new file mode 100644 index 00000000..7cbeeeed --- /dev/null +++ b/server/models/BookChapter.js @@ -0,0 +1,27 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class BookChapter extends Model { } + + BookChapter.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + index: DataTypes.INTEGER, + title: DataTypes.STRING, + start: DataTypes.INTEGER, + end: DataTypes.INTEGER + }, { + sequelize, + modelName: 'BookChapter' + }) + + const { Book } = sequelize.models + + Book.hasMany(BookChapter) + BookChapter.belongsTo(Book) + + return BookChapter +} \ No newline at end of file diff --git a/server/models/BookGenre.js b/server/models/BookGenre.js new file mode 100644 index 00000000..0fde5353 --- /dev/null +++ b/server/models/BookGenre.js @@ -0,0 +1,31 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class BookGenre extends Model { } + + BookGenre.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'BookGenre', + timestamps: false + }) + + // Super Many-to-Many + // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship + const { Book, Genre } = sequelize.models + Book.belongsToMany(Genre, { through: BookGenre }) + Genre.belongsToMany(Book, { through: BookGenre }) + + Book.hasMany(BookGenre) + BookGenre.belongsTo(Book) + + Genre.hasMany(BookGenre) + BookGenre.belongsTo(Genre) + + return BookGenre +} \ No newline at end of file diff --git a/server/models/BookNarrator.js b/server/models/BookNarrator.js new file mode 100644 index 00000000..d246160e --- /dev/null +++ b/server/models/BookNarrator.js @@ -0,0 +1,31 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class BookNarrator extends Model { } + + BookNarrator.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'BookNarrator', + timestamps: false + }) + + // Super Many-to-Many + // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship + const { Book, Person } = sequelize.models + Book.belongsToMany(Person, { through: BookNarrator }) + Person.belongsToMany(Book, { through: BookNarrator }) + + Book.hasMany(BookNarrator) + BookNarrator.belongsTo(Book) + + Person.hasMany(BookNarrator) + BookNarrator.belongsTo(Person) + + return BookNarrator +} \ No newline at end of file diff --git a/server/models/BookSeries.js b/server/models/BookSeries.js new file mode 100644 index 00000000..f4cf31a3 --- /dev/null +++ b/server/models/BookSeries.js @@ -0,0 +1,32 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class BookSeries extends Model { } + + BookSeries.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + sequence: DataTypes.STRING + }, { + sequelize, + modelName: 'BookSeries', + timestamps: false + }) + + // Super Many-to-Many + // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship + const { Book, Series } = sequelize.models + Book.belongsToMany(Series, { through: BookSeries }) + Series.belongsToMany(Book, { through: BookSeries }) + + Book.hasMany(BookSeries) + BookSeries.belongsTo(Book) + + Series.hasMany(BookSeries) + BookSeries.belongsTo(Series) + + return BookSeries +} \ No newline at end of file diff --git a/server/models/BookTag.js b/server/models/BookTag.js new file mode 100644 index 00000000..2089f623 --- /dev/null +++ b/server/models/BookTag.js @@ -0,0 +1,31 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class BookTag extends Model { } + + BookTag.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'BookTag', + timestamps: false + }) + + // Super Many-to-Many + // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship + const { Book, Tag } = sequelize.models + Book.belongsToMany(Tag, { through: BookTag }) + Tag.belongsToMany(Book, { through: BookTag }) + + Book.hasMany(BookTag) + BookTag.belongsTo(Book) + + Tag.hasMany(BookTag) + BookTag.belongsTo(Tag) + + return BookTag +} \ No newline at end of file diff --git a/server/models/Collection.js b/server/models/Collection.js new file mode 100644 index 00000000..42a41ac6 --- /dev/null +++ b/server/models/Collection.js @@ -0,0 +1,25 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Collection extends Model { } + + Collection.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: DataTypes.STRING, + description: DataTypes.TEXT + }, { + sequelize, + modelName: 'Collection' + }) + + const { Library } = sequelize.models + + Library.hasMany(Collection) + Collection.belongsTo(Library) + + return Collection +} \ No newline at end of file diff --git a/server/models/CollectionBook.js b/server/models/CollectionBook.js new file mode 100644 index 00000000..8767e95c --- /dev/null +++ b/server/models/CollectionBook.js @@ -0,0 +1,30 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class CollectionBook extends Model { } + + CollectionBook.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'CollectionBook' + }) + + // Super Many-to-Many + // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship + const { Book, Collection } = sequelize.models + Book.belongsToMany(Collection, { through: CollectionBook }) + Collection.belongsToMany(Book, { through: CollectionBook }) + + Book.hasMany(CollectionBook) + CollectionBook.belongsTo(Book) + + Collection.hasMany(CollectionBook) + CollectionBook.belongsTo(Collection) + + return CollectionBook +} \ No newline at end of file diff --git a/server/models/EBookFile.js b/server/models/EBookFile.js new file mode 100644 index 00000000..5ae5d629 --- /dev/null +++ b/server/models/EBookFile.js @@ -0,0 +1,23 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class EBookFile extends Model { } + + EBookFile.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'EBookFile' + }) + + const { FileMetadata } = sequelize.models + + FileMetadata.hasOne(EBookFile) + EBookFile.belongsTo(FileMetadata) + + return EBookFile +} \ No newline at end of file diff --git a/server/models/FileMetadata.js b/server/models/FileMetadata.js new file mode 100644 index 00000000..84dc5a1b --- /dev/null +++ b/server/models/FileMetadata.js @@ -0,0 +1,26 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class FileMetadata extends Model { } + + FileMetadata.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + ino: DataTypes.STRING, + filename: DataTypes.STRING, + ext: DataTypes.STRING, + path: DataTypes.STRING, + size: DataTypes.BIGINT, + mtime: DataTypes.DATE(6), + ctime: DataTypes.DATE(6), + birthtime: DataTypes.DATE(6) + }, { + sequelize, + modelName: 'FileMetadata' + }) + + return FileMetadata +} \ No newline at end of file diff --git a/server/models/Genre.js b/server/models/Genre.js new file mode 100644 index 00000000..52823ae1 --- /dev/null +++ b/server/models/Genre.js @@ -0,0 +1,19 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Genre extends Model { } + + Genre.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: DataTypes.STRING + }, { + sequelize, + modelName: 'Genre' + }) + + return Genre +} \ No newline at end of file diff --git a/server/models/Library.js b/server/models/Library.js new file mode 100644 index 00000000..7e5c0fb5 --- /dev/null +++ b/server/models/Library.js @@ -0,0 +1,25 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Library extends Model { } + + Library.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: DataTypes.STRING, + displayOrder: DataTypes.INTEGER, + icon: DataTypes.STRING, + mediaType: DataTypes.STRING, + provider: DataTypes.STRING, + lastScan: DataTypes.DATE, + scanVersion: DataTypes.STRING + }, { + sequelize, + modelName: 'Library' + }) + + return Library +} \ No newline at end of file diff --git a/server/models/LibraryFile.js b/server/models/LibraryFile.js new file mode 100644 index 00000000..f4839a15 --- /dev/null +++ b/server/models/LibraryFile.js @@ -0,0 +1,25 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class LibraryFile extends Model { } + + LibraryFile.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'LibraryFile' + }) + + const { LibraryItem, FileMetadata } = sequelize.models + LibraryItem.hasMany(LibraryFile) + LibraryFile.belongsTo(LibraryItem) + + FileMetadata.hasOne(LibraryFile) + LibraryFile.belongsTo(FileMetadata) + + return LibraryFile +} \ No newline at end of file diff --git a/server/models/LibraryFolder.js b/server/models/LibraryFolder.js new file mode 100644 index 00000000..028dcdf3 --- /dev/null +++ b/server/models/LibraryFolder.js @@ -0,0 +1,23 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class LibraryFolder extends Model { } + + LibraryFolder.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + path: DataTypes.STRING + }, { + sequelize, + modelName: 'LibraryFolder' + }) + + const { Library } = sequelize.models + Library.hasMany(LibraryFolder) + LibraryFolder.belongsTo(Library) + + return LibraryFolder +} \ No newline at end of file diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js new file mode 100644 index 00000000..1a212ef8 --- /dev/null +++ b/server/models/LibraryItem.js @@ -0,0 +1,34 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class LibraryItem extends Model { } + + LibraryItem.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + ino: DataTypes.STRING, + path: DataTypes.STRING, + relPath: DataTypes.STRING, + mediaType: DataTypes.STRING, + isFile: DataTypes.BOOLEAN, + isMissing: DataTypes.BOOLEAN, + isInvalid: DataTypes.BOOLEAN, + mtime: DataTypes.DATE(6), + ctime: DataTypes.DATE(6), + birthtime: DataTypes.DATE(6), + lastScan: DataTypes.DATE, + scanVersion: DataTypes.STRING + }, { + sequelize, + modelName: 'LibraryItem' + }) + + const { LibraryFolder } = sequelize.models + LibraryFolder.hasMany(LibraryItem) + LibraryItem.belongsTo(LibraryFolder) + + return LibraryItem +} \ No newline at end of file diff --git a/server/models/MediaProgress.js b/server/models/MediaProgress.js new file mode 100644 index 00000000..71cd4520 --- /dev/null +++ b/server/models/MediaProgress.js @@ -0,0 +1,75 @@ +const { DataTypes, Model } = require('sequelize') + +const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}` + +/* + * Polymorphic association: https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ + * Book has many MediaProgress. PodcastEpisode has many MediaProgress. + */ +module.exports = (sequelize) => { + class MediaProgress extends Model { + getMediaItem(options) { + if (!this.mediaItemType) return Promise.resolve(null) + const mixinMethodName = `get${uppercaseFirst(this.mediaItemType)}` + return this[mixinMethodName](options) + } + } + + MediaProgress.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + mediaItemId: DataTypes.UUIDV4, + mediaItemType: DataTypes.STRING, + duration: DataTypes.INTEGER, + currentTime: DataTypes.INTEGER, + isFinished: DataTypes.BOOLEAN, + hideFromContinueListening: DataTypes.BOOLEAN, + finishedAt: DataTypes.DATE + }, { + sequelize, + modelName: 'MediaProgress' + }) + + const { Book, PodcastEpisode, User } = sequelize.models + Book.hasMany(MediaProgress, { + foreignKey: 'mediaItemId', + constraints: false, + scope: { + mediaItemType: 'book' + } + }) + MediaProgress.belongsTo(Book, { foreignKey: 'mediaItemId', constraints: false }) + + PodcastEpisode.hasMany(MediaProgress, { + foreignKey: 'mediaItemId', + constraints: false, + scope: { + mediaItemType: 'podcastEpisode' + } + }) + MediaProgress.belongsTo(PodcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) + + MediaProgress.addHook('afterFind', findResult => { + if (!Array.isArray(findResult)) findResult = [findResult] + for (const instance of findResult) { + if (instance.mediaItemType === 'book' && instance.book !== undefined) { + instance.mediaItem = instance.book + } else if (instance.mediaItemType === 'podcastEpisode' && instance.podcastEpisode !== undefined) { + instance.mediaItem = instance.podcastEpisode + } + // To prevent mistakes: + delete instance.book + delete instance.dataValues.book + delete instance.podcastEpisode + delete instance.dataValues.podcastEpisode + } + }) + + User.hasMany(MediaProgress) + MediaProgress.belongsTo(User) + + return MediaProgress +} \ No newline at end of file diff --git a/server/models/Person.js b/server/models/Person.js new file mode 100644 index 00000000..6bdccf11 --- /dev/null +++ b/server/models/Person.js @@ -0,0 +1,26 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Person extends Model { } + + Person.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + type: DataTypes.STRING, + name: DataTypes.STRING, + asin: DataTypes.STRING, + description: DataTypes.TEXT + }, { + sequelize, + modelName: 'Person' + }) + + const { FileMetadata } = sequelize.models + FileMetadata.hasMany(Person) + Person.belongsTo(FileMetadata, { as: 'ImageFile' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias + + return Person +} \ No newline at end of file diff --git a/server/models/Podcast.js b/server/models/Podcast.js new file mode 100644 index 00000000..9bfdf81f --- /dev/null +++ b/server/models/Podcast.js @@ -0,0 +1,46 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Podcast extends Model { } + + Podcast.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + // Metadata + title: DataTypes.STRING, + author: DataTypes.STRING, + releaseDate: DataTypes.STRING, + feedUrl: DataTypes.STRING, + imageUrl: DataTypes.STRING, + description: DataTypes.TEXT, + itunesPageUrl: DataTypes.STRING, + itunesId: DataTypes.STRING, + itunesArtistId: DataTypes.STRING, + language: DataTypes.STRING, + type: DataTypes.STRING, + explicit: DataTypes.BOOLEAN, + + autoDownloadEpisodes: DataTypes.BOOLEAN, + autoDownloadSchedule: DataTypes.STRING, + lastEpisodeCheck: DataTypes.DATE, + maxEpisodesToKeep: DataTypes.INTEGER, + maxNewEpisodesToDownload: DataTypes.INTEGER, + lastCoverSearchQuery: DataTypes.STRING, + lastCoverSearch: DataTypes.DATE + }, { + sequelize, + modelName: 'Podcast' + }) + + const { LibraryItem, FileMetadata } = sequelize.models + LibraryItem.hasOne(Podcast) + Podcast.belongsTo(LibraryItem) + + FileMetadata.hasOne(Podcast) + Podcast.belongsTo(FileMetadata, { as: 'ImageFile' }) // Ref: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-an-alias + + return Podcast +} \ No newline at end of file diff --git a/server/models/PodcastEpisode.js b/server/models/PodcastEpisode.js new file mode 100644 index 00000000..73693c8a --- /dev/null +++ b/server/models/PodcastEpisode.js @@ -0,0 +1,34 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class PodcastEpisode extends Model { } + + PodcastEpisode.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + index: DataTypes.INTEGER, + season: DataTypes.STRING, + episode: DataTypes.STRING, + episodeType: DataTypes.STRING, + title: DataTypes.STRING, + subtitle: DataTypes.STRING(1000), + description: DataTypes.TEXT, + pubDate: DataTypes.STRING, + enclosureURL: DataTypes.STRING, + enclosureLength: DataTypes.BIGINT, + enclosureType: DataTypes.STRING, + publishedAt: DataTypes.DATE + }, { + sequelize, + modelName: 'PodcastEpisode' + }) + + const { Podcast } = sequelize.models + Podcast.hasMany(PodcastEpisode) + PodcastEpisode.belongsTo(Podcast) + + return PodcastEpisode +} \ No newline at end of file diff --git a/server/models/PodcastTag.js b/server/models/PodcastTag.js new file mode 100644 index 00000000..4a6c54a0 --- /dev/null +++ b/server/models/PodcastTag.js @@ -0,0 +1,31 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class PodcastTag extends Model { } + + PodcastTag.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + } + }, { + sequelize, + modelName: 'PodcastTag', + timestamps: false + }) + + // Super Many-to-Many + // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship + const { Podcast, Tag } = sequelize.models + Podcast.belongsToMany(Tag, { through: PodcastTag }) + Tag.belongsToMany(Podcast, { through: PodcastTag }) + + Podcast.hasMany(PodcastTag) + PodcastTag.belongsTo(Podcast) + + Tag.hasMany(PodcastTag) + PodcastTag.belongsTo(Tag) + + return PodcastTag +} \ No newline at end of file diff --git a/server/models/Series.js b/server/models/Series.js new file mode 100644 index 00000000..814b3691 --- /dev/null +++ b/server/models/Series.js @@ -0,0 +1,20 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Series extends Model { } + + Series.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: DataTypes.STRING, + description: DataTypes.TEXT + }, { + sequelize, + modelName: 'Series' + }) + + return Series +} \ No newline at end of file diff --git a/server/models/Tag.js b/server/models/Tag.js new file mode 100644 index 00000000..4268f4af --- /dev/null +++ b/server/models/Tag.js @@ -0,0 +1,19 @@ +const { DataTypes, Model } = require('sequelize') + +module.exports = (sequelize) => { + class Tag extends Model { } + + Tag.init({ + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: DataTypes.STRING + }, { + sequelize, + modelName: 'Tag' + }) + + return Tag +} \ No newline at end of file diff --git a/server/models/User.js b/server/models/User.js index b1977439..a15b77f6 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -4,7 +4,18 @@ module.exports = (sequelize) => { class User extends Model { } User.init({ - username: DataTypes.STRING + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + username: DataTypes.STRING, + pash: DataTypes.STRING, + type: DataTypes.STRING, + token: DataTypes.STRING, + isActive: DataTypes.BOOLEAN, + isLocked: DataTypes.BOOLEAN, + lastSeen: DataTypes.DATE }, { sequelize, modelName: 'User'