diff --git a/index.js b/index.js index 419aad3..dd9bea6 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,15 @@ const Cart = sequelize.import('models/cart'); const User = sequelize.import('models/user'); // Ваши relations между моделями :) +Cart.belongsTo(User); +Cart.belongsToMany(Souvenir, { through: 'cart_souvenirs' }); +Review.belongsTo(Souvenir); +Review.belongsTo(User); +User.hasOne(Cart); +User.hasMany(Review); +Souvenir.hasMany(Review); +Souvenir.belongsTo(Country); +Souvenir.belongsToMany(Tag, { through: 'souvenir_tags' }); module.exports.sequelize = sequelize; diff --git a/models/cart.js b/models/cart.js index e781846..3ad0388 100644 --- a/models/cart.js +++ b/models/cart.js @@ -1,5 +1,18 @@ 'use strict'; module.exports = (sequelize, DataTypes) => { - // Ваша модель корзины + return sequelize.define('cart', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + userId: { + type: DataTypes.INTEGER, + allowNull: true + } + }, { + timestamps: true + }); }; diff --git a/models/country.js b/models/country.js index dfe55d7..891329f 100644 --- a/models/country.js +++ b/models/country.js @@ -1,5 +1,18 @@ 'use strict'; module.exports = (sequelize, DataTypes) => { - // Ваша модель страны + return sequelize.define('country', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + timestamps: true + }); }; diff --git a/models/review.js b/models/review.js index aba643d..ee24fd3 100644 --- a/models/review.js +++ b/models/review.js @@ -1,5 +1,35 @@ 'use strict'; module.exports = (sequelize, DataTypes) => { - // Ваша модель отзыва + return sequelize.define('review', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + text: { + type: DataTypes.TEXT, + allowNull: true + }, + rating: { + type: DataTypes.INTEGER, + allowNull: true + }, + isApproved: { + type: DataTypes.BOOLEAN, + allowNull: true, + default: false + }, + souvenirId: { + type: DataTypes.INTEGER, + allowNull: true + }, + userId: { + type: DataTypes.INTEGER, + allowNull: true + } + }, { + timestamps: true + }); }; diff --git a/models/souvenir.js b/models/souvenir.js index 86ce8c0..a737aaa 100644 --- a/models/souvenir.js +++ b/models/souvenir.js @@ -1,5 +1,48 @@ 'use strict'; module.exports = (sequelize, DataTypes) => { - // Ваша модель сувенира + return sequelize.define('souvenir', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.TEXT, + allowNull: false + }, + image: { + type: DataTypes.TEXT, + allowNull: true + }, + price: { + type: DataTypes.DOUBLE, + allowNull: false + }, + rating: { + type: DataTypes.DOUBLE, + allowNull: true + }, + amount: { + type: DataTypes.INTEGER, + allowNull: true, + default: 0 + }, + isRecent: { + type: DataTypes.BOOLEAN, + allowNull: true + }, + countryId: { + type: DataTypes.INTEGER, + allowNull: true + } + }, { + timestamps: true, + indexes: [ + { + fields: ['countryId', 'rating', 'price'] + } + ] + }); }; diff --git a/models/tag.js b/models/tag.js index 4db36f5..cc48844 100644 --- a/models/tag.js +++ b/models/tag.js @@ -1,5 +1,18 @@ 'use strict'; module.exports = (sequelize, DataTypes) => { - // Ваша модель тэга + return sequelize.define('tag', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + timestamps: true + }); }; diff --git a/models/user.js b/models/user.js index 1988ff6..bbcfc8c 100644 --- a/models/user.js +++ b/models/user.js @@ -1,5 +1,18 @@ 'use strict'; module.exports = (sequelize, DataTypes) => { - // Ваша модель юзера + return sequelize.define('user', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + login: { + type: DataTypes.TEXT, + allowNull: true + } + }, { + timestamps: true + }); }; diff --git a/playground.js b/playground.js index 7c1b98e..19286b3 100644 --- a/playground.js +++ b/playground.js @@ -10,7 +10,7 @@ const Queries = require('./queries'); try { // Здесь можно делать запросы, чтобы проверять, что они правильно работают - const result = await queries.getAllSouvenirs(); + const result = await queries.getDisscusedSouvenirs(5); console.info(result); } catch (error) { diff --git a/queries.js b/queries.js index 7e9b7f9..4042a6b 100644 --- a/queries.js +++ b/queries.js @@ -1,66 +1,114 @@ 'use strict'; +const Sequelize = require('sequelize'); class Queries { - constructor(models) { - // Что-нибудь инициализируем в конструкторе + constructor({ sequelize, Country, Tag, Review, Souvenir, Cart, User }) { + this.sequelize = sequelize; + this.Country = Country; + this.Tag = Tag; + this.Review = Review; + this.Souvenir = Souvenir; + this.Cart = Cart; + this.User = User; } // Далее идут методы, которые вам необходимо реализовать: getAllSouvenirs() { - // Данный метод должен возвращать все сувениры. + return this.Souvenir.findAll(); } getCheapSouvenirs(price) { - // Данный метод должен возвращать все сувениры, цена которых меньше или равна price. + return this.Souvenir.findAll({ + where: { + price: { [Sequelize.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 }) { - // Данный метод должен возвращать количество сувениров, - // из страны country, с рейтингом больше или равной rating, - // и ценой меньше или равной price. - - // Важно, чтобы метод работал очень быстро, - // поэтому учтите это при определении моделей (!). + return this.Souvenir.count({ + where: { + rating: { + [Sequelize.Op.gte]: rating + }, + price: { + [Sequelize.Op.lte]: price + } + }, + include: { + model: this.Country, + where: { name: country } + } + }); } searchSouvenirs(substring) { - // Данный метод должен возвращать все сувениры, в название которых входит - // подстрока substring. Поиск должен быть регистронезависимым. + return this.Souvenir.findAll({ + where: { + name: { [Sequelize.Op.contains]: substring } + } + }); } getDisscusedSouvenirs(n) { - // Данный метод должен возвращать все сувениры, имеющих >= n отзывов. - // Кроме того, в ответе должны быть только поля id, name, image, price и rating. + return this.Review.findAll({ + attributes: ['souvenir.id'], + group: ['souvenir.id'], + include: { + model: this.Souvenir, + attributes: ['name', 'image', 'price', 'rating'] + }, + having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col('souvenir.id')), '>=', n) + }).then(s => s.map(x => x.souvenir)); } deleteOutOfStockSouvenirs() { - // Данный метод должен удалять все сувениры, которых нет в наличии - // (то есть amount = 0). - - // Метод должен возвращать количество удаленных сувениров в случае успешного удаления. + return this.Souvenir.destroy({ where: { amount: 0 } }); } - addReview(souvenirId, { login, text, rating }) { - // Данный метод должен добавлять отзыв к сувениру souvenirId - // содержит login, text, rating - из аргументов. - // Обратите внимание, что при добавлении отзыва рейтинг сувенира должен быть пересчитан, - // и всё это должно происходить за одну транзакцию (!). + async addReview(souvenirId, { login, text, rating }) { + const user = await this.User.findOne({ where: { login } }); + await this.Review.create({ text, rating, souvenirId, userId: user.id }); + + const souvenir = await this.Souvenir.findOne({ where: { souvenirId } }); + const ratings = await this.Review.findAll({ where: { souvenirId } }).then(r => r.rating); + souvenir.rating = ratings.reduce((a, b) => a + b, 0) / ratings.length; + + await souvenir.save(); } getCartSum(login) { - // Данный метод должен считать общую стоимость корзины пользователя login - // У пользователя может быть только одна корзина, поэтому это тоже можно отразить - // в модели. + return this.Cart.sum('souvenirs.price', { + group: 'carts.id', + include: [ + { + model: this.User, + where: { login } + }, + { model: this.Souvenir } + ] + }); } }