From abc9bb4dee2bf85bf62ae5856452e87bfa29c64b Mon Sep 17 00:00:00 2001 From: David Arranz Date: Wed, 4 Oct 2023 12:20:50 +0200 Subject: [PATCH] =?UTF-8?q?Ordenar=20los=20pr=C3=B3ximos=20eventos=20ponie?= =?UTF-8?q?ndo=20primero=20los=20del=20pa=C3=ADs=20del=20usuario=20o=20dis?= =?UTF-8?q?positivo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/controllers/index.js | 1 + core/express.js | 67 +- middlewares/deviceCountry.js | 17 + modules/events/event.controller.js | 64 +- modules/events/event.model.js | 936 +++++++++--------- modules/events/event.routes.js | 31 +- .../events/events_reservations.controller.js | 5 +- modules/locations/location.model.js | 182 ++-- .../notification/notification.controller.js | 2 +- package.json | 2 +- 10 files changed, 725 insertions(+), 582 deletions(-) create mode 100644 middlewares/deviceCountry.js diff --git a/core/controllers/index.js b/core/controllers/index.js index 7be2143..709dc1a 100644 --- a/core/controllers/index.js +++ b/core/controllers/index.js @@ -155,3 +155,4 @@ const generateControllers = (service, extraControllers = {}, options = {}) => { module.exports = generateControllers; module.exports.buildContext = buildContext; +module.exports.defaultOptions = defaultOptions; diff --git a/core/express.js b/core/express.js index 9907ab3..cc455b2 100644 --- a/core/express.js +++ b/core/express.js @@ -1,18 +1,20 @@ -'use strict'; +"use strict"; -const express = require('express'); +const express = require("express"); //const morgan = require('morgan'); -const bodyParser = require('body-parser'); -const compress = require('compression'); -const responseTime = require('response-time'); -const methodOverride = require('method-override'); -const cors = require('cors'); -const helmet = require('helmet'); -const passport = require('passport'); +const bodyParser = require("body-parser"); +const compress = require("compression"); +const responseTime = require("response-time"); +const methodOverride = require("method-override"); +const cors = require("cors"); +const helmet = require("helmet"); +const passport = require("passport"); -const config = require('../config'); -const router = require('./router'); -const error = require('../middlewares/error'); +const config = require("../config"); +const router = require("./router"); +const error = require("../middlewares/error"); + +const deviceCountryMiddleware = require("../middlewares/deviceCountry"); /** * Express instance @@ -24,10 +26,13 @@ const app = express(); //app.use(morgan(logs)); // parse body params and attache them to req.body -app.use(bodyParser.json({ limit: '5mb' })); -app.use(bodyParser.urlencoded({ - limit: '5mb', extended: true -})); +app.use(bodyParser.json({ limit: "5mb" })); +app.use( + bodyParser.urlencoded({ + limit: "5mb", + extended: true, + }) +); // set up the response-time middleware app.use(responseTime()); @@ -43,14 +48,23 @@ app.use(methodOverride()); app.use(helmet()); // enable CORS - Cross Origin Resource Sharing -//var allowedOrigins = ['http://localhost:8080', 'http://127.0.0.1:8080', 'https://adminapp2.loquedeverdadimporta.org']; +//var allowedOrigins = ['http://localhost:8080', 'http://127.0.0.1:8080', 'https://adminapp2.loquedeverdadimporta.org']; // enable CORS - Cross Origin Resource Sharing -app.use(cors({ - exposedHeaders: ['Content-Disposition', 'Content-Type', 'Content-Length', - 'X-Total-Count', 'Pagination-Count', 'Pagination-Page', 'Pagination-Limit'], -})); - +app.use( + cors({ + exposedHeaders: [ + "X-Country", + "Content-Disposition", + "Content-Type", + "Content-Length", + "X-Total-Count", + "Pagination-Count", + "Pagination-Page", + "Pagination-Limit", + ], + }) +); /*app.use(cors({ origin: function (origin, callback) { @@ -74,7 +88,6 @@ app.use(cors({ } }));*/ - /*app.use(cors({ origin: '*', exposeHeaders: [ @@ -113,14 +126,14 @@ app.use(cors({ ], }));*/ - +app.use(deviceCountryMiddleware.middleware()); // Access validator app.use(passport.initialize()); -require('./passport'); +require("./passport"); // Set routes -app.use('/api', router()); +app.use("/api", router()); // if error is not an instanceOf APIError, convert it. app.use(error.converter); @@ -131,4 +144,4 @@ app.use(error.notFound); // error handler, send stacktrace only during development app.use(error.handler); -module.exports = app; \ No newline at end of file +module.exports = app; diff --git a/middlewares/deviceCountry.js b/middlewares/deviceCountry.js new file mode 100644 index 0000000..340f4ef --- /dev/null +++ b/middlewares/deviceCountry.js @@ -0,0 +1,17 @@ +const middleware = (config) => { + return function (req, res, next) { + const country = req.headers["x-country"]; + + if (country) { + req.deviceCountry = country; + } else { + req.deviceCountry = "ES"; + } + + next(); + }; +}; + +module.exports = { + middleware, +}; diff --git a/modules/events/event.controller.js b/modules/events/event.controller.js index 429133f..114effe 100644 --- a/modules/events/event.controller.js +++ b/modules/events/event.controller.js @@ -2,6 +2,7 @@ const moment = require("moment"); const httpStatus = require("http-status"); const generateControllers = require("../../core/controllers"); +const { defaultOptions, buildContext } = require("../../core/controllers"); const QRHelper = require("../../helpers/qr.helper"); const emailHelper = require("../../helpers/mail.helper"); const notificationHelper = require("../../helpers/notification.helpers"); @@ -31,7 +32,7 @@ const MODULE_NAME = "[event.controller]"; const controllerOptions = { MODULE_NAME, - findOneCallback: eventInscriptionService._fillInscriptionsColor + findOneCallback: eventInscriptionService._fillInscriptionsColor, }; function generateMemberInscription(user, inscription, reservation) { @@ -61,8 +62,6 @@ function generateMemberInscription(user, inscription, reservation) { return memberInscription; } - - const extraControllers = { checkCapacity: async (req, res, next) => { const params = extractParamsFromRequest(req, res, {}); @@ -72,7 +71,7 @@ const extraControllers = { allow: false, error: undefined, }; - console.log('>>>>>>>>>>>>>>>>>>>>>>>checkCapacity', result); + console.log(">>>>>>>>>>>>>>>>>>>>>>>checkCapacity", result); if (!result.eventId) { return handleResultResponse(null, null, params, res, httpStatus.BAD_REQUEST); @@ -123,8 +122,6 @@ const extraControllers = { const userId = req.user.id; var result = null; - - console.log(params, req.user.level); if (eventId) { try { @@ -138,7 +135,7 @@ const extraControllers = { } else { try { result = await eventInscriptionService._getInscriptionsUser(userId); - result = result.map(row => row.toJSON()); + result = result.map((row) => row.toJSON()); // Asigno colores a las entradas normales result = eventInscriptionService._fillInscriptionsColor(result); return handleResultResponse(result, null, params, res, result === null ? httpStatus.NOT_FOUND : httpStatus.OK); @@ -156,8 +153,7 @@ const extraControllers = { const userId = req.user.id; var result = null; - if (!eventId) - return handleResultResponse("Es necesario el ID del evento", null, params, res, httpStatus.NOT_FOUND); + if (!eventId) return handleResultResponse("Es necesario el ID del evento", null, params, res, httpStatus.NOT_FOUND); try { result = await eventInscriptionService._getInscriptionOnlineByEvent(eventId); @@ -167,26 +163,53 @@ const extraControllers = { } catch (error) { return handleErrorResponse(MODULE_NAME, "getInscriptionsOnline", error, res); } - - }, - getInscriptionsOfNextEventsCount: async (req, res, next) => { const params = extractParamsFromRequest(req, res, {}); const userId = req.user.id; try { const result = await eventInscriptionService._getInscriptionsOfNextEventsUser(userId); - console.log("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); return handleResultResponse({ count: result }, null, params, res, httpStatus.OK); } catch (error) { - console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); return handleErrorResponse(MODULE_NAME, "getInscriptionsOfNextEventsCount", error, res); } }, + findNextEventsByCountry: (config) => { + function placesCountrysEventsFirst(rows, countryCode) { + const countryEvents = []; + const restEvents = []; + for (const event of rows) { + if (event.location.country_code === countryCode) { + countryEvents.push(event); + } else { + restEvents.push(event); + } + } + return countryEvents.concat(restEvents); + } + + return async (req, res, next) => { + config = config || { + scopes: [], + }; + + const params = extractParamsFromRequest(req, res, defaultOptions.params.find); + try { + const result = await eventService.fetchAll(params, buildContext(req, config)); + + const orderedRows = placesCountrysEventsFirst(result.rows, req.user ? req.user.country : req.deviceCountry); + + console.log(orderedRows); + return handleResultResponse(orderedRows, result.count, params, res); + } catch (error) { + return handleErrorResponse(MODULE_NAME, "findNextEventsByCountry", error, res); + } + }; + }, findPartners: async (req, res, next) => { const params = extractParamsFromRequest(req, res, {}); @@ -326,11 +349,15 @@ const extraControllers = { console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> { const params = extractParamsFromRequest(req, res, {}); const inscriptionId = params.params.id; @@ -381,7 +408,8 @@ const extraControllers = { //console.log(headerMail, bodyMail); try { - if (member.validated) emailHelper.sendTicket(mailService.generateHeaderMail(member), mailService.generateBodyMail(member)); + if (member.validated) + emailHelper.sendTicket(mailService.generateHeaderMail(member), mailService.generateBodyMail(member)); else emailHelper.sendListaEspera(mailService.generateHeaderMail(member), mailService.generateBodyMail(member)); } catch (error) { console.log("No se ha podido mandar email con entrada"); diff --git a/modules/events/event.model.js b/modules/events/event.model.js index 3eb4a68..06b5b9f 100644 --- a/modules/events/event.model.js +++ b/modules/events/event.model.js @@ -1,496 +1,544 @@ -'use strict'; -const moment = require('moment'); -const Sequelize = require('sequelize'); -moment.locale('es'); +"use strict"; +const moment = require("moment"); +const Sequelize = require("sequelize"); +moment.locale("es"); const getStateCode = (event) => { - var currentDate = moment(), - initDate = moment(event.init_date), - endDate = moment(event.end_date), - init_availableDate = moment(event.init_available_date), - end_availableDate = moment(event.end_available_date); + var currentDate = moment(), + initDate = moment(event.init_date), + endDate = moment(event.end_date), + init_availableDate = moment(event.init_available_date), + end_availableDate = moment(event.end_available_date); - if (currentDate.isBetween(initDate, endDate)) { - return 'current_event'; + if (currentDate.isBetween(initDate, endDate)) { + return "current_event"; + } else { + if (endDate.isBefore(currentDate)) { + return "closed_event"; } else { - if (endDate.isBefore(currentDate)) { - return 'closed_event'; + if (currentDate.isBefore(init_availableDate)) { + return "future_registrations"; + } else { + if (currentDate.isBetween(init_availableDate, end_availableDate)) { + if (event.url_registration) { + return "url_registrations_open"; + } else { + return event.sold_out == 1 ? "waitinglist_open" : "registrations_open"; + } } else { - if (currentDate.isBefore(init_availableDate)){ - return 'future_registrations' - } else { - if (currentDate.isBetween(init_availableDate, end_availableDate)) { - if (event.url_registration) { - return 'url_registrations_open'; - } else { - return (event.sold_out == 1) ? 'waitinglist_open' : 'registrations_open'; - } - } - else { - if (currentDate.isAfter(end_availableDate)) - return 'closed_registrations' - else - return 'N/A'; - } - - } + if (currentDate.isAfter(end_availableDate)) return "closed_registrations"; + else return "N/A"; } - }; + } + } + } }; const getStateText = (event) => { - var currentDate = moment(), - initDate = moment(event.init_date), - endDate = moment(event.end_date), - init_availableDate = moment(event.init_available_date), - end_availableDate = moment(event.end_available_date); + var currentDate = moment(), + initDate = moment(event.init_date), + endDate = moment(event.end_date), + init_availableDate = moment(event.init_available_date), + end_availableDate = moment(event.end_available_date); - if (currentDate.isBetween(initDate, endDate)) { - return 'Congreso en curso'; + if (currentDate.isBetween(initDate, endDate)) { + return "Congreso en curso"; + } else { + if (endDate.isBefore(currentDate)) { + return "Congreso finalizado"; } else { - if (endDate.isBefore(currentDate)) { - return 'Congreso finalizado'; + if (currentDate.isBefore(init_availableDate)) { + return "Inscripciones a partir del " + init_availableDate.format("D [de] MMMM"); + } else { + if (currentDate.isBetween(init_availableDate, end_availableDate)) { + if (event.url_registration) { + return "Inscripciones abiertas"; + } else { + return event.sold_out == 1 ? "Inscripciones abiertas a lista de espera" : "Inscripciones abiertas"; + } } else { - if (currentDate.isBefore(init_availableDate)) { - return 'Inscripciones a partir del ' + init_availableDate.format('D [de] MMMM'); - } else { - if (currentDate.isBetween(init_availableDate, end_availableDate)) { - if (event.url_registration) { - return 'Inscripciones abiertas'; - } else { - return (event.sold_out == 1) ? 'Inscripciones abiertas a lista de espera' : 'Inscripciones abiertas'; - } - } - else { - if (currentDate.isAfter(end_availableDate)) - return 'Inscripciones cerradas' - else - return 'N/A'; - } - - } + if (currentDate.isAfter(end_availableDate)) return "Inscripciones cerradas"; + else return "N/A"; } - }; + } + } + } }; const getAssistanceType = (event) => { - if (event.virtual) { - if (event.assistants > 0) { - return 'onsite, online'; - } else { - return 'online'; - } + if (event.virtual) { + if (event.assistants > 0) { + return "onsite, online"; } else { - return 'onsite'; + return "online"; } + } else { + return "onsite"; + } }; const getAssistanceTypeText = (event) => { - if (event.virtual) { - if (event.assistants > 0) { - return 'evento presencial y online'; - } else { - return 'evento online'; - } + if (event.virtual) { + if (event.assistants > 0) { + return "evento presencial y online"; } else { - return 'evento presencial'; + return "evento online"; } + } else { + return "evento presencial"; + } }; module.exports = function (sequelize, DataTypes) { + const Event = sequelize.define( + "Event", + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.STRING, + }, + campaign_text: { + type: DataTypes.TEXT, + allowNull: true, + }, + init_date: { + type: DataTypes.DATE, + allowNull: false, + }, + end_date: { + type: DataTypes.DATE, + }, + init_available_date: { + type: DataTypes.DATE, + }, + end_available_date: { + type: DataTypes.DATE, + }, + gmt: { + type: DataTypes.INTEGER, + defaultValue: 1, + }, + assistants: { + type: DataTypes.INTEGER, + }, + confirmed: { + type: DataTypes.INTEGER, + }, + sold_out: { + //Se han vendido todas y se ha abierto lista de espera si hay asignada + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + allow_multiple: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + multiple_limit: { + type: DataTypes.INTEGER, + }, + allow_overflow: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + overflow_eventId: { + type: DataTypes.UUID, + foreignKey: true, + require: false, + }, + state: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: "draft", + }, + stateCode: { + type: Sequelize.VIRTUAL(Sequelize.STRING, [ + "init_date", + "end_date", + "init_available_date", + "end_available_date", + "sold_out", + ]), + get: function () { + return getStateCode(this); + }, + }, + stateText: { + type: Sequelize.VIRTUAL(Sequelize.STRING, [ + "init_date", + "end_date", + "init_available_date", + "end_available_date", + "sold_out", + ]), + get: function () { + return getStateText(this); + }, + }, + typeId: { + type: DataTypes.UUID, + foreignKey: true, + }, + venueId: { + type: DataTypes.UUID, + foreignKey: true, + }, + locationId: { + type: DataTypes.UUID, + foreignKey: true, + }, + url_streaming: { + type: DataTypes.STRING, + }, + url_poll: { + type: DataTypes.STRING, + }, + url_registration: { + type: DataTypes.STRING, + }, + marketing_list: { + type: DataTypes.STRING, + }, + userId: { + type: DataTypes.UUID, + foreignKey: true, + }, + featured: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + virtual: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + allow_questions: { + type: DataTypes.BOOLEAN, + defaultValue: true, + }, + assistanceType: { + type: Sequelize.VIRTUAL(Sequelize.STRING, ["virtual", "assistants"]), + get: function () { + return getAssistanceType(this); + }, + }, + assistanceTypeText: { + type: Sequelize.VIRTUAL(Sequelize.STRING, ["virtual", "assistants"]), + get: function () { + return getAssistanceTypeText(this); + }, + }, + }, + { + tableName: "events", + freezeTableName: true, + timestamps: true, - const Event = sequelize.define('Event', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, + defaultScope: { + where: { + state: "publish", + typeId: { [Sequelize.Op.ne]: 2 }, }, - name: { - type: DataTypes.STRING, - allowNull: false, - }, - description: { - type: DataTypes.STRING, - }, - campaign_text: { - type: DataTypes.TEXT, - allowNull: true - }, - init_date: { - type: DataTypes.DATE, - allowNull: false, - }, - end_date: { - type: DataTypes.DATE, - }, - init_available_date: { - type: DataTypes.DATE, - }, - end_available_date: { - type: DataTypes.DATE, - }, - gmt: { - type: DataTypes.INTEGER, - defaultValue: 1, - }, - assistants: { - type: DataTypes.INTEGER, - }, - confirmed: { - type: DataTypes.INTEGER, - }, - sold_out: { //Se han vendido todas y se ha abierto lista de espera si hay asignada - type: DataTypes.BOOLEAN, - defaultValue: false, - }, - allow_multiple: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - }, - multiple_limit: { - type: DataTypes.INTEGER, - }, - allow_overflow: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - }, - overflow_eventId: { - type: DataTypes.UUID, - foreignKey: true, + include: [ + { + model: sequelize.models.EventType, + as: "type", + attributes: ["name", "title"], + }, + { + association: "location", + attributes: ["country", "city", "country_code"], require: false, - }, - state: { - type: DataTypes.STRING, - allowNull: false, - defaultValue: 'draft' - }, - stateCode: { - type: Sequelize.VIRTUAL(Sequelize.STRING, ['init_date', 'end_date', 'init_available_date', 'end_available_date', 'sold_out']), - get: function () { - return getStateCode(this); - }, - }, - stateText: { - type: Sequelize.VIRTUAL(Sequelize.STRING, ['init_date', 'end_date', 'init_available_date', 'end_available_date', 'sold_out']), - get: function () { - return getStateText(this); - }, - }, - typeId: { - type: DataTypes.UUID, - foreignKey: true, - }, - venueId: { - type: DataTypes.UUID, - foreignKey: true, - }, - locationId:{ - type: DataTypes.UUID, - foreignKey: true, - }, - url_streaming: { - type: DataTypes.STRING, - }, - url_poll: { - type: DataTypes.STRING, - }, - url_registration: { - type: DataTypes.STRING, - }, - marketing_list: { - type: DataTypes.STRING, - }, - userId: { - type: DataTypes.UUID, - foreignKey: true, - }, - featured: { - type: DataTypes.BOOLEAN, - defaultValue: false, - }, - virtual: { - type: DataTypes.BOOLEAN, - defaultValue: false, - }, - allow_questions: { - type: DataTypes.BOOLEAN, - defaultValue: true, - }, - assistanceType: { - type: Sequelize.VIRTUAL(Sequelize.STRING, ['virtual', 'assistants']), - get: function () { - return getAssistanceType(this); - }, - }, - assistanceTypeText: { - type: Sequelize.VIRTUAL(Sequelize.STRING, ['virtual', 'assistants']), - get: function () { - return getAssistanceTypeText(this); - }, - } + }, + ], + }, + } + ); - }, { - tableName: 'events', - freezeTableName: true, - timestamps: true, - - defaultScope: { - where: { - state: 'publish', - typeId: { [Sequelize.Op.ne]: 2 }, - }, - include: [{ - model: sequelize.models.EventType, - as: 'type', - attributes: ['name', 'title'], - }, - { - association: 'location', - attributes: ['country', 'city'], - require:false, - }], - }, + Event.associate = function (models) { + Event.OverflowEvent = Event.belongsTo(models.Event, { + as: "overflowEvent", + foreignKey: "overflow_eventId", + required: false, }); - - Event.associate = function (models) { - Event.OverflowEvent = Event.belongsTo(models.Event, { - as: 'overflowEvent', - foreignKey: 'overflow_eventId', - required: false }); + Event.Type = Event.belongsTo(models.EventType, { foreignKey: "typeId", as: "type" }); + Event.UserCreate = Event.belongsTo(models.User, { foreignKey: "userId", as: "user" }); + Event.Venue = Event.belongsTo(models.Venue, { foreignKey: "venueId", as: "venue", required: false }); + Event.Details = Event.hasMany(models.EventDetail, { foreignKey: "eventId", as: "details" }); + Event.Location = Event.belongsTo(models.Location, { foreignKey: "locationId", as: "location" }); - Event.Type = Event.belongsTo(models.EventType, { foreignKey: 'typeId', as: "type" }); - Event.UserCreate = Event.belongsTo(models.User, { foreignKey: 'userId', as: "user" }); - Event.Venue = Event.belongsTo(models.Venue, { foreignKey: 'venueId', as: "venue", required: false}); - Event.Details = Event.hasMany(models.EventDetail, { foreignKey: 'eventId', as: "details" }); - Event.Location = Event.belongsTo(models.Location, { foreignKey: 'locationId', as: "location" }); + //OJO antes de force comentar + // OJO GENERA UN FOREIGN KEY Con eventos y habrá ID de otras entidades que no exitan en la tabla eventos, porque son post o speakers + Event.Comments = Event.hasMany(models.Comment, { + foreignKey: "entityId", + scope: { + entityName: "event", + }, + as: "comments", + required: false, + }); -//OJO antes de force comentar -// OJO GENERA UN FOREIGN KEY Con eventos y habrá ID de otras entidades que no exitan en la tabla eventos, porque son post o speakers - Event.Comments = Event.hasMany(models.Comment, { - foreignKey: 'entityId', - scope: { - entityName: 'event' - }, - as: "comments", - required: false, - }); + Event.Multimedias = Event.hasMany(models.Multimedia, { + foreignKey: "entityId", + as: { singular: "multimedia", plural: "multimedias" }, + required: false, + }); - Event.Multimedias = Event.hasMany(models.Multimedia, { - foreignKey: 'entityId', - as: { singular: 'multimedia', plural: 'multimedias' }, - required: false, - }); + Event.Reservations = Event.hasMany(models.EventReservation, { + foreignKey: "eventId", + as: "reservations", + required: false, + }); + Event.Inscriptions = Event.hasMany(models.EventInscription, { + foreignKey: "eventId", + as: "inscriptions", + required: false, + }); + Event.Questions = Event.hasMany(models.EventQuestion, { + foreignKey: "eventId", + as: "questions", + required: false, + }); + }; - Event.Reservations = Event.hasMany(models.EventReservation, { - foreignKey: 'eventId', as: "reservations", - required: false, - }); - Event.Inscriptions = Event.hasMany(models.EventInscription, { - foreignKey: 'eventId', as: "inscriptions", - required: false, - }); - Event.Questions = Event.hasMany(models.EventQuestion, { - foreignKey: 'eventId', as: "questions" , - required: false, - }); + Event.addScope("featured", { + where: { + featured: true, + }, + }); + + Event.addScope("includeVenue", () => { + return { + include: [{ model: sequelize.models.Venue, as: "venue" }], }; - - Event.addScope('featured', { - where: { - featured: true + }); + + Event.addScope("includeMultimedias", () => { + return { + include: [ + { + model: sequelize.models.Multimedia, + as: { singular: "multimedia", plural: "multimedias" }, + required: false, + include: [ + { + model: sequelize.models.MultimediaFile, + as: "multimediaFile", + }, + ], + order: [["type", "ASC"]], // <- esto no funciona por ahora en Sequelize }, - }); + ], + }; + }); - Event.addScope('includeVenue', () => { - return { - include: [ - { model: sequelize.models.Venue, as: 'venue' } - ] - } - }); - - Event.addScope('includeMultimedias', () => { - return { - include: [{ - model: sequelize.models.Multimedia, - as: { singular: 'multimedia', plural: 'multimedias' }, - required: false, - include : [{ - model: sequelize.models.MultimediaFile, - as: "multimediaFile" - }], - order: [["type", "ASC"]], // <- esto no funciona por ahora en Sequelize + Event.addScope("includeMultimediaAvatar", () => { + return { + include: [ + { + model: sequelize.models.Multimedia, + as: { singular: "multimedia", plural: "multimedias" }, + where: { type: "avatar" }, + required: false, + include: [ + { + model: sequelize.models.MultimediaFile, + as: "multimediaFile", }, - ] - } - }); + ], + }, + ], + }; + }); - Event.addScope('includeMultimediaAvatar', () => { - return { - include: [{ - model: sequelize.models.Multimedia, - as: { singular: 'multimedia', plural: 'multimedias' }, - where: { type: 'avatar'}, - required: false, - include: [{ - model: sequelize.models.MultimediaFile, - as: "multimediaFile" - }] - }, - ] - } - }); + Event.addScope("includeInscription", (userId) => { + return { + include: [ + { model: sequelize.models.EventInscription, as: "inscriptions", required: false, where: { userId: userId } }, + ], + }; + }); - Event.addScope('includeInscription', (userId) => { - return { - include: [ - { model: sequelize.models.EventInscription, as: 'inscriptions', - required: false, - where: { userId: userId } } - ] - } - }); + Event.addScope("includeOverflowEvent", () => { + return { + include: [ + { + model: sequelize.models.Event, + as: "overflowEvent", + required: false, + where: { typeId: { [Sequelize.Op.ne]: null } }, + // attributes: ['assistants', 'confirmed'], + }, + ], + }; + }); - Event.addScope('includeOverflowEvent', () => { - return { - include: [ - { model: sequelize.models.Event, - as: 'overflowEvent', + Event.addScope("includeDetails", () => { + return { + include: [ + { + model: sequelize.models.EventDetail, + as: "details", + required: false, + include: [ + { + model: sequelize.models.Speaker, + as: "speaker", + required: false, + include: [ + { + model: sequelize.models.Multimedia, + as: { singular: "multimedia", plural: "multimedias" }, required: false, - where: { typeId: { [Sequelize.Op.ne]: null}}, -// attributes: ['assistants', 'confirmed'], - } - ] - } - }); - - Event.addScope('includeDetails', () => { - return { - include: [{ - model: sequelize.models.EventDetail, - as: 'details', - required: false, - include: [{ - model: sequelize.models.Speaker, - as: 'speaker', - required: false, - include : [{ - model: sequelize.models.Multimedia, - as: { singular: 'multimedia', plural: 'multimedias' }, - required: false, - include: [{ - model: sequelize.models.MultimediaFile, - as: "multimediaFile" - }] - }] - }] - }] - } - }); - - Event.addScope('includeComments', () => { - return { - include: [{ - model: sequelize.models.Comment, - as: 'comments', - required: false, - include: [{ - model: sequelize.models.User, - as: 'user', - }] - }] - } - }); - - Event.addScope('next', { - where: { - init_date: { - [Sequelize.Op.gte]: moment().add(1, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss') - } + include: [ + { + model: sequelize.models.MultimediaFile, + as: "multimediaFile", + }, + ], + }, + ], + }, + ], }, - }); + ], + }; + }); - Event.addScope('today', { - where: { - init_date: { - [Sequelize.Op.gte]: moment().startOf('day').format('YYYY-MM-DD HH:mm:ss'), - [Sequelize.Op.lt]: moment().add(1, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'), - } - } - }); - - Event.addScope('current', { - where: { - init_date: { - [Sequelize.Op.lte]: moment().format('YYYY-MM-DD HH:mm:ss'), + Event.addScope("includeComments", () => { + return { + include: [ + { + model: sequelize.models.Comment, + as: "comments", + required: false, + include: [ + { + model: sequelize.models.User, + as: "user", }, - end_date:{ - [Sequelize.Op.gt]: moment().format('YYYY-MM-DD HH:mm:ss'), - }, - } - }); - - Event.addScope('tomorrow', { - where: { - init_date: { - [Sequelize.Op.gte]: moment().add(1, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'), - [Sequelize.Op.lt]: moment().add(2, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'), - } - } - }); - - Event.addScope('yesterday', { - where: { - init_date: { - [Sequelize.Op.gte]: moment().add(-1, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'), - [Sequelize.Op.lt]: moment().add('day').startOf('day').format('YYYY-MM-DD HH:mm:ss'), - } - } - }); - - Event.addScope('past', { - where: { - init_date: { - [Sequelize.Op.lt]: moment().startOf('day').format('YYYY-MM-DD HH:mm:ss') - } - } - }); - - Event.addScope('withOpenInscriptions', { - where: { - init_available_date: { - [Sequelize.Op.lte]: moment().format('YYYY-MM-DD HH:mm:ss') - }, - end_available_date: { - [Sequelize.Op.gt]: moment().format('YYYY-MM-DD HH:mm:ss') - } + ], }, - }); + ], + }; + }); - Event.addScope('CitiesOfEvents', () => { - return { - include: [{ - model: sequelize.models.Location, as: 'location', required: false, - include: [{ - model: sequelize.models.Multimedia, as: { singular: 'multimedia', plural: 'multimedias' }, required: false, attributes: ['type'], - include: [{ model: sequelize.models.MultimediaFile, as: "multimediaFile", attributes: ['name', 'description', 'class', 'provider', 'url'] },] - }] - }, - { model: sequelize.models.EventType, as: 'type', attributes: ['name', 'title'] }], - group: ['location.country', 'location.city', 'Event.typeId',], - attributes: ['location.country', 'location.city', 'type.name', 'type.title', [sequelize.fn('COUNT', sequelize.col('Event.typeId')), 'ediciones'], [sequelize.fn('SUM', sequelize.col('Event.assistants')), 'assistants']] - } - }); + Event.addScope("next", { + where: { + init_date: { + [Sequelize.Op.gte]: moment().add(1, "days").startOf("day").format("YYYY-MM-DD HH:mm:ss"), + }, + }, + }); - Event.addScope('onlyOfLocation', (location) => { - return { - where: { locationId: { [Sequelize.Op.eq]: location } }, - } - }); + Event.addScope("today", { + where: { + init_date: { + [Sequelize.Op.gte]: moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), + [Sequelize.Op.lt]: moment().add(1, "days").startOf("day").format("YYYY-MM-DD HH:mm:ss"), + }, + }, + }); + Event.addScope("current", { + where: { + init_date: { + [Sequelize.Op.lte]: moment().format("YYYY-MM-DD HH:mm:ss"), + }, + end_date: { + [Sequelize.Op.gt]: moment().format("YYYY-MM-DD HH:mm:ss"), + }, + }, + }); - return Event; -}; \ No newline at end of file + Event.addScope("tomorrow", { + where: { + init_date: { + [Sequelize.Op.gte]: moment().add(1, "days").startOf("day").format("YYYY-MM-DD HH:mm:ss"), + [Sequelize.Op.lt]: moment().add(2, "days").startOf("day").format("YYYY-MM-DD HH:mm:ss"), + }, + }, + }); + + Event.addScope("yesterday", { + where: { + init_date: { + [Sequelize.Op.gte]: moment().add(-1, "days").startOf("day").format("YYYY-MM-DD HH:mm:ss"), + [Sequelize.Op.lt]: moment().add("day").startOf("day").format("YYYY-MM-DD HH:mm:ss"), + }, + }, + }); + + Event.addScope("past", { + where: { + init_date: { + [Sequelize.Op.lt]: moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), + }, + }, + }); + + Event.addScope("withOpenInscriptions", { + where: { + init_available_date: { + [Sequelize.Op.lte]: moment().format("YYYY-MM-DD HH:mm:ss"), + }, + end_available_date: { + [Sequelize.Op.gt]: moment().format("YYYY-MM-DD HH:mm:ss"), + }, + }, + }); + + Event.addScope("CitiesOfEvents", () => { + return { + include: [ + { + model: sequelize.models.Location, + as: "location", + required: false, + include: [ + { + model: sequelize.models.Multimedia, + as: { singular: "multimedia", plural: "multimedias" }, + required: false, + attributes: ["type"], + include: [ + { + model: sequelize.models.MultimediaFile, + as: "multimediaFile", + attributes: ["name", "description", "class", "provider", "url"], + }, + ], + }, + ], + }, + { model: sequelize.models.EventType, as: "type", attributes: ["name", "title"] }, + ], + group: ["location.country", "location.city", "Event.typeId"], + attributes: [ + "location.country", + "location.city", + "type.name", + "type.title", + [sequelize.fn("COUNT", sequelize.col("Event.typeId")), "ediciones"], + [sequelize.fn("SUM", sequelize.col("Event.assistants")), "assistants"], + ], + }; + }); + + Event.addScope("onlyOfLocation", (location) => { + return { + where: { locationId: { [Sequelize.Op.eq]: location } }, + }; + }); + + return Event; +}; diff --git a/modules/events/event.routes.js b/modules/events/event.routes.js index f7a9613..eca9a28 100644 --- a/modules/events/event.routes.js +++ b/modules/events/event.routes.js @@ -64,6 +64,7 @@ routes.get( }) ); +// Esta ruta se dejará de usar a favor de "/events/next_by_country" routes.get( "/events/next", isOptionalUser, @@ -78,11 +79,29 @@ routes.get( if (isLogged) { scopes.push({ method: ["includeInscription", req.user.id] }); } - // console.log(moment().add(1, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss')); return eventController.find({ scopes })(req, res, next); } ); +// Sustituye a "/events/next" a partir de la versión de la APP 1.4.4 +routes.get( + "/events/next_by_country", + isOptionalUser, + FieldMiddleware.middleware({ + invalidFields: generalInvalidFields, + }), + PaginateMiddleware.middleware(), + SortMiddleware.middleware({ default: "init_available_date" }), + (req, res, next) => { + const isLogged = req.user && req.user.id; + const scopes = ["defaultScope", "next", "includeVenue", "includeMultimedias"]; + if (isLogged) { + scopes.push({ method: ["includeInscription", req.user.id] }); + } + return eventController.findNextEventsByCountry({ scopes })(req, res, next); + } +); + routes.get( "/events/past", isOptionalUser, @@ -274,7 +293,7 @@ routes.get( eventInscriptionController.getInscription ); -// Hacer una inscripción +// Hacer una inscripción routes.post( "/events/:id/inscriptions", isLoggedUser, @@ -285,7 +304,7 @@ routes.post( eventReservationController.recuperateReservationByCode, //Recupera a registra el usuario que se va a inscribir authController.getOrCreateUser, - //Comprobamos si tiene ya una incripción, en tal caso, comprobamos el código de reserva sea el de la inscripcion hecha, + //Comprobamos si tiene ya una incripción, en tal caso, comprobamos el código de reserva sea el de la inscripcion hecha, //si no es así es el caso de un usuario que se reinscribe con otro código de reserva eventInscriptionController.checkInscriptionByUser, //Si es un usuario tutor y solicita un group_size se crea la reserva @@ -357,7 +376,7 @@ routes.post( eventReservationController.recuperateReservationByCode, //Recupera a registra el usuario que se va a inscribir authController.getOrCreateUser, - //Comprobamos si tiene ya una incripción, en tal caso, comprobamos el código de reserva sea el de la inscripcion hecha, + //Comprobamos si tiene ya una incripción, en tal caso, comprobamos el código de reserva sea el de la inscripcion hecha, //si no es así es el caso de un usuario que se reinscribe con otro código de reserva eventInscriptionController.checkInscriptionByUser, //Al ser via web siempre será inscripción con codigo de reserva @@ -535,8 +554,8 @@ routes.put( "/admin/reservations/:id", isAdministratorUser, //SchemaValidator(eventValidation.ReservationInputType, true), - eventReservationController.checkAssitantsUpdate, - eventReservationController.update() + eventReservationController.checkAssitantsUpdate, + eventReservationController.update() ); routes.put( diff --git a/modules/events/events_reservations.controller.js b/modules/events/events_reservations.controller.js index b918f43..003ab39 100644 --- a/modules/events/events_reservations.controller.js +++ b/modules/events/events_reservations.controller.js @@ -46,9 +46,10 @@ async function activeReservation(reservation) { } reservation.state = "publish"; - //Finalmente hay que validar la inscripción del tutor + //Finalmente hay que validar la inscripción del tutor en el caso de aquellos centros que no son aliados if ((await eventInscriptionService._validateInscriptionTutorOfReservation(reservation.id, reservation.userId)) <= 0) { - console.log("No se ha podido validar la inscripción del tutor por ser de un centro aliado se hace a posteriori en este caso"); + //En el caso de ser un centro aliado no se puede validar la inscripcion ya que se hace a posteriori por lo tanto no se hace nada +// console.log("No se ha podido validar la inscripción del tutor por ser de un centro aliado se hace a posteriori en este caso"); // return null; } diff --git a/modules/locations/location.model.js b/modules/locations/location.model.js index befd36a..494a1ad 100644 --- a/modules/locations/location.model.js +++ b/modules/locations/location.model.js @@ -1,93 +1,109 @@ -const Sequelize = require('sequelize'); +const Sequelize = require("sequelize"); module.exports = function (sequelize, DataTypes) { - const Location = sequelize.define('Location', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, + const Location = sequelize.define( + "Location", + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + country: { + type: DataTypes.STRING(125), + allowNull: false, + }, + city: { + type: DataTypes.STRING(125), + allowNull: false, + }, + description: { + type: DataTypes.STRING, + }, + country_code: { + type: DataTypes.STRING(2), + default: "ES", + }, + }, + { + tableName: "locations", + freezeTableName: true, + timestamps: true, + indexes: [ + { + unique: true, + fields: ["country", "city"], }, - country: { - type: DataTypes.STRING(125), - allowNull: false, + { + unique: false, + fields: ["country_code"], }, - city: { - type: DataTypes.STRING(125), - allowNull: false, - }, - description: { - type: DataTypes.STRING, - }, - }, { - tableName: 'locations', - freezeTableName: true, - timestamps: true, - indexes: [{ - unique: true, - fields: ['country', 'city'] - }] + ], + } + ); + + Location.associate = function (models) { + Location.Events = Location.hasMany(models.Event, { as: "events", foreignKey: "locationId" }); + + //OJO antes de force comentar + // OJO GENERA UN FOREIGN KEY Con eventos y habrá ID de otras entidades que no exitan en la tabla eventos, porque son post o speakers + Location.Multimedias = Location.hasMany(models.Multimedia, { + foreignKey: "entityId", + as: { singular: "multimedia", plural: "multimedias" }, }); + }; - Location.associate = function (models) { - Location.Events = Location.hasMany(models.Event, {as: 'events', foreignKey: 'locationId'}); - - //OJO antes de force comentar - // OJO GENERA UN FOREIGN KEY Con eventos y habrá ID de otras entidades que no exitan en la tabla eventos, porque son post o speakers - Location.Multimedias = Location.hasMany(models.Multimedia, { - foreignKey: 'entityId', - as: { singular: 'multimedia', plural: 'multimedias' }, - }); + Location.addScope("includeMultimedias", () => { + return { + include: [ + { + model: sequelize.models.Multimedia, + as: { singular: "multimedia", plural: "multimedias" }, + required: false, + include: [ + { + model: sequelize.models.MultimediaFile, + as: "multimediaFile", + }, + ], + }, + ], }; + }); - Location.addScope('includeMultimedias', () => { - return { - include: [{ - model: sequelize.models.Multimedia, - as: { singular: 'multimedia', plural: 'multimedias' }, - required: false, - include: [{ - model: sequelize.models.MultimediaFile, - as: "multimediaFile" - }] - }, - ] - } - }); + Location.addScope("includeEvents", () => { + return { + include: [ + { + model: sequelize.models.Event, + as: "events", + required: false, + // group: ['typeId'], + // attributes: ['name', 'type.title',] //[sequelize.fn('COUNT', sequelize.col('events.typeId')), 'ediciones'],] // [sequelize.fn('SUM', sequelize.col('Event.assistants')), 'assistants']] + // attributes: [[sequelize.fn('COUNT', sequelize.col('typeId')), 'ediciones'],] // [sequelize.fn('SUM', sequelize.col('Event.assistants')), 'assistants']] + // include: [{ + // model: sequelize.models.MultimediaFile, + // as: "multimediaFile" + // }] + }, + ], + }; + }); + Location.addScope("includeGroupEvents", () => { + return { + include: [ + { + as: "events", + required: false, + // include: [{ + // model: sequelize.models.MultimediaFile, + // as: "multimediaFile" + // }] + }, + ], + }; + }); - Location.addScope('includeEvents', () => { - return { - include: [{ - model: sequelize.models.Event, - as: 'events', - required: false, -// group: ['typeId'], -// attributes: ['name', 'type.title',] //[sequelize.fn('COUNT', sequelize.col('events.typeId')), 'ediciones'],] // [sequelize.fn('SUM', sequelize.col('Event.assistants')), 'assistants']] -// attributes: [[sequelize.fn('COUNT', sequelize.col('typeId')), 'ediciones'],] // [sequelize.fn('SUM', sequelize.col('Event.assistants')), 'assistants']] -// include: [{ -// model: sequelize.models.MultimediaFile, -// as: "multimediaFile" -// }] - }, - ] - } - }); - - Location.addScope('includeGroupEvents', () => { - return { - include: [{ - - as: 'events', - required: false, - // include: [{ - // model: sequelize.models.MultimediaFile, - // as: "multimediaFile" - // }] - }, - ] - } - }); - - - return Location; -}; \ No newline at end of file + return Location; +}; diff --git a/modules/notification/notification.controller.js b/modules/notification/notification.controller.js index f9f286b..1ae311f 100644 --- a/modules/notification/notification.controller.js +++ b/modules/notification/notification.controller.js @@ -118,7 +118,7 @@ const extraControllers = { * "segment": "ALL" | "ALL_VALIDATED" | "ALL_NOT_VALIDATED" | * "PARTNERS_ALL" | "PARTNERS_VALIDATED" | "PARTNERS_NOT_VALIDATED" | * "COLLEGE_ALL" | "COLLEGE_VALIDATED" | "COLLEGE_NOT_VALIDATED" - * OPCION 3 - A todos los dispositivos activos + * OPCION 3 - A todos los dispositivos activos de un país * "deviceIds": ["*" | "ExponentPushToken[YbOS1AIZjQbchZbxNaVRqC]", ...] * "country": "*" | "ES" | "MX" * }, diff --git a/package.json b/package.json index a5c5c24..7d963e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lqdvi-api3", - "version": "1.4.1", + "version": "1.4.4", "description": "", "author": "Rodax Software", "license": "ISC",