diff --git a/server/index.js b/server/index.js index ead5ce7..81bf788 100644 --- a/server/index.js +++ b/server/index.js @@ -102,5 +102,5 @@ fastify.listen(fastify.siteConfig.port, function (err, address) { } fastify.decorate('canEmail', typeof fastify.nodemailer !== 'undefined'); - fastify.decorate('models', require('./sequelize/getModels')(fastify.sequelize)); + fastify.decorate('models', require('./sequelize/models')(fastify.sequelize)); }); \ No newline at end of file diff --git a/server/sequelize/associations/BookReference.js b/server/sequelize/associations/BookReference.js new file mode 100644 index 0000000..c4efbcb --- /dev/null +++ b/server/sequelize/associations/BookReference.js @@ -0,0 +1,10 @@ +module.exports = models => { + const { + BookReference, + Review, + } = models; + + BookReference.hasMany(Review); + + return BookReference; +} \ No newline at end of file diff --git a/server/sequelize/associations/Recommendation.js b/server/sequelize/associations/Recommendation.js new file mode 100644 index 0000000..4ddc85e --- /dev/null +++ b/server/sequelize/associations/Recommendation.js @@ -0,0 +1,23 @@ +module.exports = models => { + const { + Recommendation, + User, + BookReference, + } = models; + + Recommendation.belongsTo(User, { + foreignKey: 'fromUser', + onDelete: 'SET NULL', + }); + Recommendation.belongsTo(User, { + foreignKey: 'toUser', + onDelete: 'CASCADE', + }); + + Recommendation.belongsTo(BookReference, { + foreignKey: 'bookId', + onDelete: 'CASCADE', + }); + + return Recommendation; +} \ No newline at end of file diff --git a/server/sequelize/associations/Review.js b/server/sequelize/associations/Review.js new file mode 100644 index 0000000..d6432bc --- /dev/null +++ b/server/sequelize/associations/Review.js @@ -0,0 +1,25 @@ +module.exports = models => { + const { + Review, + User, + PermissionLevel, + BookReference, + } = models; + + Review.belongsTo(User, { + foreignKey: 'userId', + onDelete: 'CASCADE', + }); + + Review.belongsTo(PermissionLevel, { + foreignKey: 'permissionLevel', + onDelete: 'SET NULL', + }); + + Review.belongsTo(BookReference, { + foreignKey: 'bookReferenceId', + onDelete: 'SET NULL', + }); + + return Review; +} \ No newline at end of file diff --git a/server/sequelize/associations/Shelf.js b/server/sequelize/associations/Shelf.js new file mode 100644 index 0000000..2129894 --- /dev/null +++ b/server/sequelize/associations/Shelf.js @@ -0,0 +1,27 @@ +module.exports = models => { + const { + Shelf, + User, + ShelfItem, + PermissionLevel, + } = models; + + Shelf.belongsTo(User, { + foreignKey: 'userId', + onDelete: 'CASCADE', + }); + + Shelf.belongsTo(PermissionLevel, { + foreignKey: 'permissionLevel', + onDelete: 'SET NULL', + }); + + Shelf.belongsTo(User, { + foreignKey: 'permissionLevel', + onDelete: 'CASCADE', + }); + + Shelf.hasMany(ShelfItem); + + return Shelf; +} \ No newline at end of file diff --git a/server/sequelize/associations/ShelfItem.js b/server/sequelize/associations/ShelfItem.js new file mode 100644 index 0000000..1a86f73 --- /dev/null +++ b/server/sequelize/associations/ShelfItem.js @@ -0,0 +1,22 @@ +module.exports = models => { + const { + ShelfItem, + Shelf, + BookReference, + Status, + } = models; + + ShelfItem.belongsTo(Shelf, { + foreignKey: 'shelfId', + onDelete: 'CASCADE', + }); + + ShelfItem.belongsTo(BookReference, { + foreignKey: 'bookId', + onDelete: 'CASCADE', + }); + + ShelfItem.hasMany(Status); + + return ShelfItem; +} \ No newline at end of file diff --git a/server/sequelize/associations/Status.js b/server/sequelize/associations/Status.js new file mode 100644 index 0000000..7f15d31 --- /dev/null +++ b/server/sequelize/associations/Status.js @@ -0,0 +1,24 @@ +module.exports = models => { + const { + Status, + User, + ShelfItem, + } = models; + + Status.belongsTo(User, { + foreignKey: 'userId', + onDelete: 'CASCADE', + }); + + Status.belongsTo(PermissionLevel, { + foreignKey: 'permissionLevel', + onDelete: 'SET NULL', + }); + + Status.belongsTo(ShelfItem, { + foreignKey: 'shelfItemId', + onDelete: 'SET NULL', + }); + + return Status; +} \ No newline at end of file diff --git a/server/sequelize/associations/User.js b/server/sequelize/associations/User.js new file mode 100644 index 0000000..3da9c1d --- /dev/null +++ b/server/sequelize/associations/User.js @@ -0,0 +1,23 @@ +module.exports = models => { + const { + User, + PermissionLevel, + Shelf, + Status, + Review, + Recommendation, + } = models; + + User.belongsTo(PermissionLevel, { + foreignKey: 'permissionLevel', + onDelete: 'SET NULL', + }); + User.hasMany(Shelf); + User.hasMany(Status); + User.hasMany(Review); + User.hasMany(Recommendation, { + foreignKey: 'toUser', + }); + + return User; +} \ No newline at end of file diff --git a/server/sequelize/associations/index.js b/server/sequelize/associations/index.js new file mode 100644 index 0000000..8a230f5 --- /dev/null +++ b/server/sequelize/associations/index.js @@ -0,0 +1,17 @@ +const path = require('path'); +const fs = require('fs'); + +module.exports = models => { + const associatedModels = {}; + + Object.keys(models).forEach(modelName => { + const associationFileName = path.resolve(__dirname, modelName, '.js'); + if (fs.existsSync(associationFileName)) { + associatedModels[modelName] = require(associationFileName)(models); + } else { + associatedModels[modelName] = models[modelName]; + } + }); + + return associatedModels; +}; \ No newline at end of file diff --git a/server/sequelize/getModels.js b/server/sequelize/getModels.js deleted file mode 100644 index c6b7575..0000000 --- a/server/sequelize/getModels.js +++ /dev/null @@ -1,392 +0,0 @@ -const Sequelize = require('sequelize'); - -function getModels (sequelize) { - const User = sequelize.define('user', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - email: { - type: Sequelize.STRING, - allowNull: false, - unique: true, - validate: { - isEmail: true, - len: [5, 150], - }, - }, - username: { - type: Sequelize.STRING, - allowNull: false, - unique: true, - validate: { - is: /^[a-z0-9_]+$/i, // Is a set of characters a-z, 0-9, or _, case insensitive - len: [2, 32], - }, - }, - displayName: { - type: Sequelize.STRING, - allowNull: false, - unique: true, - validate: { - len: [1, 32], - }, - }, - passwordHash: { - type: Sequelize.STRING, - allowNull: false, - }, - passwordSalt: { - type: Sequelize.STRING, - allowNull: false, - }, - accountConfirm: { - type: Sequelize.STRING, - allowNull: true, - }, - - // Timestamps - createdAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - }); - - const Shelf = sequelize.define('shelf', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - userId: { - type: Sequelize.INTEGER, - references: { - model: User, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - len: [1, 32], - }, - }, - isPublic: { - type: Sequelize.BOOLEAN, - allowNull: false, - defaultValue: false, - }, - isDeletable: { - type: Sequelize.BOOLEAN, - allowNull: false, - defaultValue: true, - }, - - // Timestamps - createdAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - }); - Shelf.belongsTo(User, { - foreignKey: 'userId', - onDelete: 'CASCADE', - }); - User.hasMany(Shelf); - - const BookReference = sequelize.define('bookReference', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - name: { - type: Sequelize.STRING, - allowNull: false, - }, - description: { - type: Sequelize.STRING, - allowNull: false, - comment: 'The description is attempted to be made up of the type of work and the author', - }, - sources: { - type: Sequelize.JSON, - allowNull: true, - defaultValue: [], - comment: 'A JSON array with each element being an object with named source and source id ', - }, - covers: { - type: Sequelize.JSON, - allowNull: true, - defaultValue: [], - comment: 'A JSON array with each element being an object with image url and source id ', - }, - - // Timestamps - createdAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - }, { - indexes: [ - { - fields: ['name', 'description'], - }, - ], - }); - - const ShelfItem = sequelize.define('shelfItem', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - shelfId: { - type: Sequelize.INTEGER, - references: { - model: Shelf, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - bookId: { - type: Sequelize.INTEGER, - references: { - model: BookReference, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - bookEdition: { - type: Sequelize.JSON, - allowNull: true, - comment: 'An object with properties `source` and `id`', - }, - order: { - type: Sequelize.INTEGER, - defaultValue: 0, - }, - - // Timestamps - createdAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - }); - Shelf.hasMany(ShelfItem); - ShelfItem.belongsTo(Shelf, { - foreignKey: 'shelfId', - onDelete: 'CASCADE', - }); - ShelfItem.belongsTo(BookReference, { - foreignKey: 'bookId', - onDelete: 'CASCADE', - }); - - const Status = sequelize.define('status', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - userId: { - type: Sequelize.INTEGER, - references: { - model: User, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - text: { - type: Sequelize.TEXT, - allowNull: true, - }, - shelfItemId: { - type: Sequelize.INTEGER, - allowNull: true, - references: { - model: ShelfItem, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - progress: { - type: Sequelize.INTEGER, - allowNull: true, - }, - - // Timestamps - createdAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - }); - User.hasMany(Status); - Status.belongsTo(User, { - foreignKey: 'userId', - onDelete: 'CASCADE', - }); - ShelfItem.hasMany(Status); - Status.belongsTo(ShelfItem, { - foreignKey: 'shelfItemId', - // onDelete: 'IGNORE' - }); - - const Review = sequelize.define('review', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - userId: { - type: Sequelize.INTEGER, - references: { - model: User, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - text: { - type: Sequelize.TEXT, - allowNull: true, - }, - bookReferenceId: { - type: Sequelize.INTEGER, - allowNull: true, - references: { - model: BookReference, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - rating: { - type: Sequelize.INTEGER, - allowNull: true, - }, - - // Timestamps - createdAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - }); - User.hasMany(Review); - Review.belongsTo(User, { - foreignKey: 'userId', - onDelete: 'CASCADE', - }); - BookReference.hasMany(Review); - Review.belongsTo(BookReference, { - foreignKey: 'shelfItemId', - // onDelete: 'IGNORE' - }); - - const Recommendation = sequelize.define('recommendation', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - toUser: { - type: Sequelize.INTEGER, - allowNull: false, - references: { - model: User, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - text: { - type: Sequelize.TEXT, - allowNull: false, - }, - book: { - type: Sequelize.INTEGER, - allowNull: false, - references: { - model: BookReference, - key: 'id', - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, - } - }, - data: { - type: Sequelize.JSON, - allowNull: true, - }, - - // Timestamps - createdAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - defaultValue: Sequelize.NOW, - }, - }); - Recommendation.belongsTo(User, { - foreignKey: 'fromUser', - onDelete: 'SET NULL', - }); - Recommendation.belongsTo(User, { - foreignKey: 'toUser', - onDelete: 'CASCADE', - }); - User.hasMany(Recommendation, { - foreignKey: 'toUser', - }) - - return { - User, - Shelf, - BookReference, - ShelfItem, - StatusType, - Status, - Review, - Recommendation, - } -} - -module.exports = getModels; \ No newline at end of file diff --git a/server/sequelize/migration.js b/server/sequelize/migration.js index 0f08b6c..29bfd26 100644 --- a/server/sequelize/migration.js +++ b/server/sequelize/migration.js @@ -4,6 +4,7 @@ const fs = require('fs'); const dbVersion = '0.0.0'; function migrateDb(oldVersion, sequelize) { + const models = sequelize.models; // if (oldVersion < targetVersion) { // migrate db here // } diff --git a/server/sequelize/models/BookReference.js b/server/sequelize/models/BookReference.js new file mode 100644 index 0000000..812b7da --- /dev/null +++ b/server/sequelize/models/BookReference.js @@ -0,0 +1,44 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('BookReference', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + description: { + type: Sequelize.STRING, + allowNull: false, + comment: 'The description is attempted to be made up of the type of work and the author', + }, + sources: { + type: Sequelize.JSON, + allowNull: false, + defaultValue: [], + comment: 'A JSON array with each element being an object with named source and source id ', + }, + covers: { + type: Sequelize.JSON, + allowNull: false, + defaultValue: [], + comment: 'A JSON array with each element being an object with image url and source id ', + }, +}, { + timestamps: false, + indexes: [ + { + unique: true, + fields: ['name', 'description'], + }, + { + fields: ['name'], + }, + { + fields: ['description'], + }, + ], +}); \ No newline at end of file diff --git a/server/sequelize/models/PermissionLevel.js b/server/sequelize/models/PermissionLevel.js new file mode 100644 index 0000000..1608314 --- /dev/null +++ b/server/sequelize/models/PermissionLevel.js @@ -0,0 +1,21 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('PermissionLevel', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: false, + comment: 'Represents the level number with 100 being the private and 0 being public', + }, + name: { + type: Sequelize.STRING, + unique: true, + allowNull: false, + }, + description: { + type: Sequelize.STRING, + allowNull: false, + }, +}, { + timestamps: false, +}); \ No newline at end of file diff --git a/server/sequelize/models/Recommendation.js b/server/sequelize/models/Recommendation.js new file mode 100644 index 0000000..1b1a045 --- /dev/null +++ b/server/sequelize/models/Recommendation.js @@ -0,0 +1,69 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('Recommendation', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + fromUser: { + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: sequelize.models.User, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + }, + }, + toUser: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.User, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + }, + }, + text: { + type: Sequelize.TEXT, + allowNull: false, + validate: { + isNotBlank: value => value.length > 2, + } + }, + bookId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.BookReference, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + }, + }, + data: { + type: Sequelize.JSON, + allowNull: true, + }, + + // Timestamps + createdAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, +}, { + paranoid: true, // If toUser deletes recommendation, keep in database so fromUser can still see that they sent the recommendation. + indexes: [ + { + fields: ['toUser'], + }, + { + fields: ['fromUser'], + }, + ], +}); \ No newline at end of file diff --git a/server/sequelize/models/Review.js b/server/sequelize/models/Review.js new file mode 100644 index 0000000..bbbe870 --- /dev/null +++ b/server/sequelize/models/Review.js @@ -0,0 +1,70 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('Review', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.User, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + } + }, + permissionLevel: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.PermissionLevel, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + } + }, + text: { + type: Sequelize.TEXT, + allowNull: true, + }, + bookReferenceId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.BookReference, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + } + }, + bookEdition: { + type: Sequelize.JSON, + allowNull: true, + comment: 'An object with properties `source` and `id` where `source` is the domain where the edition was taken from `id` is the reference to the edition in source', + }, + rating: { + type: Sequelize.INTEGER, + allowNull: true, + }, + + // Timestamps + createdAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, +}, { + indexes: [ + { + fields: ['userId'], + }, + { + fields: ['bookReferenceId'], + }, + ], +}); \ No newline at end of file diff --git a/server/sequelize/models/Shelf.js b/server/sequelize/models/Shelf.js new file mode 100644 index 0000000..ff8cf1a --- /dev/null +++ b/server/sequelize/models/Shelf.js @@ -0,0 +1,60 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('Shelf', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.User, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + } + }, + name: { + type: Sequelize.STRING, + allowNull: false, + validate: { + len: [1, 32], + }, + }, + permissionLevel: { + type: Sequelize.BOOLEAN, + allowNull: false, + references: { + model: sequelize.models.PermissionLevel, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + } + }, + isDeletable: { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + + // Timestamps + createdAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, +}, { + indexes: [ + { + fields: ['userId'], + }, + { + fields: ['permissionLevel'], + }, + ], +}); \ No newline at end of file diff --git a/server/sequelize/models/ShelfItem.js b/server/sequelize/models/ShelfItem.js new file mode 100644 index 0000000..ba9e13a --- /dev/null +++ b/server/sequelize/models/ShelfItem.js @@ -0,0 +1,54 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('ShelfItem', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + shelfId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.Shelf, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + } + }, + bookId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.BookReference, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + } + }, + bookEdition: { + type: Sequelize.JSON, + allowNull: true, + comment: 'An object with properties `source` and `id` where `source` is the domain where the edition was taken from `id` is the reference to the edition in source', + }, + order: { + type: Sequelize.INTEGER, + defaultValue: 0, + }, + + // Timestamps + createdAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, +}, { + indexes: [ + { + fields: ['shelfId'], + }, + ], +}); \ No newline at end of file diff --git a/server/sequelize/models/Status.js b/server/sequelize/models/Status.js new file mode 100644 index 0000000..618144b --- /dev/null +++ b/server/sequelize/models/Status.js @@ -0,0 +1,67 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('Status', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.User, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + }, + }, + permissionLevel: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.PermissionLevel, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + }, + comment: 'This permission level should be the level of the ShelfItem or lower.', + }, + text: { + type: Sequelize.TEXT, + allowNull: true, + }, + shelfItemId: { + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: sequelize.models.ShelfItem, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + }, + }, + progress: { + type: Sequelize.INTEGER, + allowNull: true, + comment: 'A number from 0 to 100 that represents percent completion', + }, + + // Timestamps + createdAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, +}, { + indexes: [ + { + fields: ['userId'], + }, + { + fields: ['shelfItemId'], + }, + ], +}); \ No newline at end of file diff --git a/server/sequelize/models/User.js b/server/sequelize/models/User.js new file mode 100644 index 0000000..1935dcc --- /dev/null +++ b/server/sequelize/models/User.js @@ -0,0 +1,68 @@ +const Sequelize = require('sequelize'); + +module.exports = sequelize => sequelize.define('User', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + email: { + type: Sequelize.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true, + len: [5, 150], + }, + }, + username: { + type: Sequelize.STRING, + allowNull: false, + unique: true, + validate: { + is: /^[a-z0-9_]+$/i, // Is a set of characters a-z, 0-9, or _, case insensitive + len: [2, 32], + }, + }, + displayName: { + type: Sequelize.STRING, + allowNull: false, + validate: { + len: [1, 32], + }, + }, + permissionLevel: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: sequelize.models.PermissionLevel, + key: 'id', + deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE, + }, + comment: 'Enable profile to be viewed only by users of this permission level. "Followers" is interpreted as "Users this user is following" for this case.', + }, + passwordHash: { + type: Sequelize.STRING, + allowNull: false, + }, + passwordSalt: { + type: Sequelize.STRING, + allowNull: false, + }, + accountConfirm: { + type: Sequelize.STRING, + allowNull: true, + }, + + // Timestamps + createdAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, +}); \ No newline at end of file diff --git a/server/sequelize/models/index.js b/server/sequelize/models/index.js new file mode 100644 index 0000000..1dde40e --- /dev/null +++ b/server/sequelize/models/index.js @@ -0,0 +1,22 @@ +const path = require('path'); + +module.exports = sequelize => { + const modelNames = [ // This is the order required to correctly get references. + 'PermissionLevel', + 'User', + 'BookReference', + 'Shelf', + 'ShelfItem', + 'Status', + 'Review', + 'Recommendation', + ]; + + const models = {}; + modelNames.forEach(modelName => { + const filename = `./${modelName}.js`; + models[modelName] = require(path.resolve(__dirname, filename))(sequelize); + }); + + return require(path.resolve(__dirname, '../associations'))(models); +}; diff --git a/server/sequelize/setup-database.js b/server/sequelize/setup-database.js index 9bf652e..5651365 100644 --- a/server/sequelize/setup-database.js +++ b/server/sequelize/setup-database.js @@ -37,6 +37,7 @@ switch (siteConfig.db_engine) { } const sequelize = new Sequelize(sequelizeConfig); +const models = require('./models')(sequelize); const migration = require('./migration'); const dbVersionPath = path.resolve(__dirname, './.dbversion'); @@ -45,7 +46,7 @@ if (!force) { const installedDbVersion = fs.readFileSync(dbVersionPath); if (installedDbVersion < migration.dbVersion) { console.log(`Migrating from ${installedDbVersion} to ${migration.dbVersion}...`); - migration.migrateDb(installedDbVersion, sequelize); + migration.migrateDb(installedDbVersion, sequelize, models); return fs.writeFile(dbVersionPath, migration.dbVersion, err => { if (err) { console.error(err); @@ -63,8 +64,34 @@ if (!force) { console.log(`Installing database tables${force ? ', dropping existing ones first' : ''}...`); sequelize.sync({ force }).then(() => { + console.log(`Tables installed! Creating Permission Levels...`); + return models.PermissionLevel.bulkCreate([ + { + id: 100, + name: 'db.privacyLevel.private', + description: 'db.privacyLevel.privateDescription', + }, + { + id: 66, + name: 'db.privacyLevel.friends', + description: 'db.privacyLevel.friendsDescription', + }, + { + id: 33, + name: 'db.privacyLevel.followers', + description: 'db.privacyLevel.followersDescription', + }, + { + id: 0, + name: 'db.privacyLevel.public', + description: 'db.privacyLevel.publicDescription', + }, + ]).catch((err) => { + console.error('Could not create Permission Levels:\n', err); + }); +}).then(() => { sequelize.close(); - console.log(`Tables installed! Writing database version to ${dbVersionPath}...`); + console.log(`Permission Levels created! Writing database version to ${dbVersionPath}...`); fs.writeFile(dbVersionPath, migration.dbVersion, err => { if (err) { console.error(err); diff --git a/yarn.lock b/yarn.lock index cb90792..2a1ebe0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4608,22 +4608,6 @@ node-libs-browser@^2.0.0: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-pre-gyp@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" @@ -5215,10 +5199,10 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@^7.16.0: - version "7.17.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-7.17.0.tgz#1fcf82238dcbebea63e192c944345c25c86992fc" - integrity sha512-70Q4ZzIdPgwMPb3zUIzAUwigNJ4v5vsWdMED6OzXMfOECeYTvTm7iSC3FpKizu/R1BHL8Do3bLs6ltGfOTAnqg== +pg@^7.17.0: + version "7.17.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-7.17.1.tgz#1eb4d900e1f21f43978b306972b02a2329138755" + integrity sha512-SYWEip6eADsgDQIZk0bmB2JDOrC8Xu6z10KlhlXl03NSomwVmHB6ZTVyDCwOfT6bXHI8QndJdk5XxSSRXikaSA== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" @@ -6914,7 +6898,7 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4, tar@^4.4.2: +tar@^4: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==