diff --git a/index.js b/index.js index 419aad3..cfe8949 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,14 @@ const User = sequelize.import('models/user'); // Ваши relations между моделями :) +Cart.belongsTo(User); +Review.belongsTo(User); +Souvenir.belongsTo(Country); +Souvenir.hasMany(Review); + +Souvenir.belongsToMany(Tag, { through: 'souvenir_tags' }); +Cart.belongsToMany(Souvenir, { through: 'cart_souvenirs' }); + module.exports.sequelize = sequelize; module.exports.Country = Country; diff --git a/models/cart.js b/models/cart.js index e781846..66d0b60 100644 --- a/models/cart.js +++ b/models/cart.js @@ -2,4 +2,19 @@ module.exports = (sequelize, DataTypes) => { // Ваша модель корзины + return sequelize.define('carts', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + userId: { + type: DataTypes.INTEGER, + reference: { + model: 'users', + key: 'id' + } + } + }); }; diff --git a/models/country.js b/models/country.js index dfe55d7..bcea5c4 100644 --- a/models/country.js +++ b/models/country.js @@ -2,4 +2,23 @@ module.exports = (sequelize, DataTypes) => { // Ваша модель страны + return sequelize.define('countries', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.TEXT, + unique: true + } + }, { + indexes: [ + { + name: 'countries_name', + fields: ['name'] + } + ] + }); }; diff --git a/models/review.js b/models/review.js index aba643d..69f6138 100644 --- a/models/review.js +++ b/models/review.js @@ -2,4 +2,35 @@ module.exports = (sequelize, DataTypes) => { // Ваша модель отзыва + return sequelize.define('reviews', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + text: DataTypes.TEXT, + rating: { + type: DataTypes.INTEGER, + validate: { min: 0, max: 5 } + }, + isApproved: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + souvenirId: { + type: DataTypes.INTEGER, + reference: { + model: 'souvenirs', + key: 'id' + } + }, + userId: { + type: DataTypes.INTEGER, + reference: { + model: 'users', + key: 'id' + } + } + }); }; diff --git a/models/souvenir.js b/models/souvenir.js index 86ce8c0..6da015e 100644 --- a/models/souvenir.js +++ b/models/souvenir.js @@ -2,4 +2,43 @@ module.exports = (sequelize, DataTypes) => { // Ваша модель сувенира + return sequelize.define('souvenirs', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.TEXT, + allowNull: false + }, + image: DataTypes.TEXT, + price: { + type: DataTypes.DOUBLE, + allowNull: false, + validate: { min: 0 } + }, + rating: { + type: DataTypes.DOUBLE, + validate: { min: 0, max: 5 } + }, + amount: { + type: DataTypes.INTEGER, + validate: { min: 0 }, + defaultValue: 0 + }, + isRecent: DataTypes.BOOLEAN, + countryId: { + type: DataTypes.INTEGER, + reference: { + model: 'countries', + key: 'id' + } + } + }, { + indexes: [{ + fields: ['rating', 'price'] + }] + }); }; diff --git a/models/tag.js b/models/tag.js index 4db36f5..e2e9cee 100644 --- a/models/tag.js +++ b/models/tag.js @@ -2,4 +2,13 @@ module.exports = (sequelize, DataTypes) => { // Ваша модель тэга + return sequelize.define('tags', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + name: DataTypes.TEXT + }); }; diff --git a/models/user.js b/models/user.js index 1988ff6..d367a6b 100644 --- a/models/user.js +++ b/models/user.js @@ -2,4 +2,16 @@ module.exports = (sequelize, DataTypes) => { // Ваша модель юзера + return sequelize.define('users', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + login: { + type: DataTypes.TEXT, + unique: true + } + }); }; diff --git a/queries.js b/queries.js index 7e9b7f9..2ed64a8 100644 --- a/queries.js +++ b/queries.js @@ -2,26 +2,55 @@ class Queries { constructor(models) { - // Что-нибудь инициализируем в конструкторе + this.sequelize = models.sequelize; + this.op = models.sequelize.Op; + this.country = models.Country; + this.tag = models.Tag; + this.review = models.Review; + this.souvenir = models.Souvenir; + this.cart = models.Cart; + this.user = models.User; } // Далее идут методы, которые вам необходимо реализовать: getAllSouvenirs() { // Данный метод должен возвращать все сувениры. + return this.souvenir.findAll(); } getCheapSouvenirs(price) { // Данный метод должен возвращать все сувениры, цена которых меньше или равна price. + return this.souvenir.findAll({ + where: { price: { [this.op.lte]: price } } + }); } getTopRatingSouvenirs(n) { // Данный метод должен возвращать топ n сувениров с самым большим рейтингом. + return this.souvenir.findAll({ + order: [ + ['rating', 'DESC'] + ], + limit: n + }); } getSouvenirsByTag(tag) { // Данный метод должен возвращать все сувениры, в тегах которых есть tag. // Кроме того, в ответе должны быть только поля id, name, image, price и rating. + return this.souvenir.findAll({ + attributes: ['id', 'name', 'image', 'price', 'rating'], + include: [ + { + model: this.tag, + where: { + name: tag + }, + attributes: [] + } + ] + }); } getSouvenirsCount({ country, rating, price }) { @@ -31,16 +60,49 @@ class Queries { // Важно, чтобы метод работал очень быстро, // поэтому учтите это при определении моделей (!). + return this.souvenir.count({ + where: { + rating: { [this.op.gte]: rating }, + price: { [this.op.lte]: price } + }, + include: [ + { + model: this.country, + where: { + name: country + } + } + ] + }); } searchSouvenirs(substring) { // Данный метод должен возвращать все сувениры, в название которых входит // подстрока substring. Поиск должен быть регистронезависимым. + return this.souvenir.findAll({ + where: { + name: { [this.op.iLike]: `%${substring}%` } + } + }); } getDisscusedSouvenirs(n) { // Данный метод должен возвращать все сувениры, имеющих >= n отзывов. // Кроме того, в ответе должны быть только поля id, name, image, price и rating. + return this.souvenir.findAll({ + attributes: ['id', 'name', 'image', 'price', 'rating'], + include: [ + { + model: this.review, + attributes: [] + } + ], + order: ['id'], + group: 'souvenirs.id', + having: this.sequelize.where( + this.sequelize.fn('COUNT', this.sequelize.col('reviews.id')), '>=', n + ) + }); } deleteOutOfStockSouvenirs() { @@ -48,19 +110,76 @@ class Queries { // (то есть amount = 0). // Метод должен возвращать количество удаленных сувениров в случае успешного удаления. + return this.souvenir.destroy({ + where: { + amount: 0 + } + }); } - addReview(souvenirId, { login, text, rating }) { + async addReview(souvenirId, { login, text, rating }) { // Данный метод должен добавлять отзыв к сувениру souvenirId // содержит login, text, rating - из аргументов. // Обратите внимание, что при добавлении отзыва рейтинг сувенира должен быть пересчитан, // и всё это должно происходить за одну транзакцию (!). + return this.sequelize.transaction(async transaction => { + const user = await this.user.findOne({ + where: { + login: login + }, + transaction: transaction + }); + await this.review.create( + { + text: text, + rating: rating, + souvenirId: souvenirId, + userId: user.id + }, + { + transaction: transaction + } + ); + + const ratings = (await this.review.findAll({ + where: { + souvenirId: souvenirId + }, + transaction: transaction + })).map(review => review.rating); + + rating = ratings.reduce((sum, current) => sum + current, 0) / ratings.length; + + const souvenir = await this.souvenir.findOne({ + where: { + id: souvenirId + }, + transaction: transaction + }); + + await souvenir.update({ rating }, { transaction }); + }); } getCartSum(login) { // Данный метод должен считать общую стоимость корзины пользователя login // У пользователя может быть только одна корзина, поэтому это тоже можно отразить // в модели. + return this.cart.sum('souvenirs.price', { + group: 'carts.id', + includeIgnoreAttributes: false, + include: [ + { + model: this.souvenir + }, + { + model: this.user, + where: { + login: login + } + } + ] + }); } }