gancio-upstream/server/api/controller/event.js

746 lines
23 KiB
JavaScript
Raw Normal View History

2019-04-03 00:25:12 +02:00
const crypto = require('crypto')
const path = require('path')
2021-09-27 10:42:17 +02:00
const config = require('../../config')
const fs = require('fs')
2019-04-03 00:25:12 +02:00
const { Op } = require('sequelize')
2021-03-16 19:54:26 +01:00
const intersection = require('lodash/intersection')
2021-09-30 11:06:59 +02:00
const linkifyHtml = require('linkify-html')
2020-02-11 12:08:15 +01:00
const Sequelize = require('sequelize')
2020-10-25 00:34:22 +02:00
const dayjs = require('dayjs')
2021-01-11 00:17:56 +01:00
const helpers = require('../../helpers')
2020-02-10 00:40:23 +01:00
2020-06-27 02:10:10 +02:00
const Event = require('../models/event')
const Resource = require('../models/resource')
const Tag = require('../models/tag')
const Place = require('../models/place')
const Notification = require('../models/notification')
const APUser = require('../models/ap_user')
2019-10-20 14:22:55 +02:00
const exportController = require('./export')
2021-02-09 12:17:10 +01:00
const log = require('../../log')
2019-04-03 00:25:12 +02:00
const eventController = {
2020-01-27 00:47:03 +01:00
async _getMeta () {
2019-05-30 12:04:14 +02:00
const places = await Place.findAll({
2019-05-30 12:12:51 +02:00
order: [[Sequelize.literal('weigth'), 'DESC']],
2019-05-30 12:04:14 +02:00
attributes: {
2019-09-11 19:12:24 +02:00
include: [[Sequelize.fn('count', Sequelize.col('events.placeId')), 'weigth']],
2020-01-21 01:24:32 +01:00
exclude: ['createdAt', 'updatedAt']
2019-05-30 12:04:14 +02:00
},
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
2019-05-30 12:12:51 +02:00
group: ['place.id']
2019-05-30 12:04:14 +02:00
})
const tags = await Tag.findAll({
order: [[Sequelize.literal('w'), 'DESC']],
2019-05-30 12:04:14 +02:00
attributes: {
include: [[Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'w']]
},
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
group: ['tag.tag']
2019-05-30 12:12:51 +02:00
})
2019-05-30 12:04:14 +02:00
2020-01-27 00:47:03 +01:00
return { places, tags }
},
async getMeta (req, res) {
res.json(await eventController._getMeta())
2019-04-03 00:25:12 +02:00
},
2022-05-25 10:54:46 +02:00
async search (req, res) {
const search = req.query.search
// search for events
const events = Event.findAll({
logging: console.log,
order: [['start_datetime', 'DESC']],
attributes: {
include: [[Sequelize.fn('LOWER', Sequelize.col('title')), 't']],
},
include: [Place],
where: {
recurrent: null,
parentId: null,
title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%'),
},
limit: 3
})
// search for tags
const tags = Tag.findAll({
order: [[Sequelize.literal('w'), 'DESC']],
attributes: {
include: [[Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'w']],
},
where: { tag: { [Op.substring]: search } },
include: [{ model: Event, where: { is_visible: true, recurrent: null, parentId: null }, attributes: [], through: { attributes: [] }, required: true }],
group: ['tag.tag'],
limit: 3
})
// let places
// try {
// places = await Place.findAll({
// logging: console.log,
// where: { name: { [Op.substring]: search } },
// order: [[Sequelize.literal('weigth'), 'DESC']],
// attributes: {
// include: [[Sequelize.fn('count', Sequelize.col('events.placeId')), 'weigth']],
// // exclude: ['createdAt', 'updatedAt']
// },
// include: [{ model: Event, where: { is_visible: true }, through: { attributes: [] }, attributes: [] }],
// limit: 3,
// group: ['place.id'],
// })
// } catch (e) {
// console.error(e)
// }
const [ tmpevents, tmptags] = await Promise.all([events, tags])
let ret = tmpevents
.map(e => ({ ...e.get(), type: 'event' }))
.concat(tmptags.map(t => ({ ...t.get(), type: 'tag' })))
return res.json(ret)
},
async getNotifications (event, action) {
2021-03-05 14:17:10 +01:00
log.debug(`getNotifications ${event.title} ${action}`)
2019-09-11 19:12:24 +02:00
function match (event, filters) {
2019-04-03 00:25:12 +02:00
// matches if no filter specified
2019-09-11 19:12:24 +02:00
if (!filters) { return true }
2019-04-03 00:25:12 +02:00
// check for visibility
2019-09-11 19:12:24 +02:00
if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false }
2019-04-03 00:25:12 +02:00
2019-09-11 19:12:24 +02:00
if (!filters.tags && !filters.places) { return true }
if (!filters.tags.length && !filters.places.length) { return true }
2019-04-03 00:25:12 +02:00
if (filters.tags.length) {
2021-03-16 19:54:26 +01:00
const m = intersection(event.tags.map(t => t.tag), filters.tags)
2019-09-11 19:12:24 +02:00
if (m.length > 0) { return true }
2019-04-03 00:25:12 +02:00
}
if (filters.places.length) {
if (filters.places.find(p => p === event.place.name)) {
return true
}
}
}
2020-01-21 01:24:32 +01:00
const notifications = await Notification.findAll({ where: { action }, include: [Event] })
2019-04-03 00:25:12 +02:00
// get notification that matches with selected event
2022-05-25 10:54:46 +02:00
return notifications.filter(notification => match(event, notification.filters))
2019-04-03 00:25:12 +02:00
},
2019-09-11 19:12:24 +02:00
async updatePlace (req, res) {
2019-04-03 00:25:12 +02:00
const place = await Place.findByPk(req.body.id)
await place.update(req.body)
res.json(place)
},
async _get(slug) {
// retrocompatibility, old events URL does not use slug, use id as fallback
const id = Number(slug) || -1
return Event.findOne({
where: {
[Op.or]: {
slug,
id
}
}
})
},
2019-09-11 19:12:24 +02:00
async get (req, res) {
2019-10-20 14:22:55 +02:00
const format = req.params.format || 'json'
2022-02-26 21:27:40 +01:00
const is_admin = res.locals.user && res.locals.user.is_admin
const slug = req.params.event_slug
// retrocompatibility, old events URL does not use slug, use id as fallback
const id = Number(slug) || -1
let event
2020-06-03 22:52:10 +02:00
try {
2021-04-13 18:04:53 +02:00
event = await Event.findOne({
where: {
[Op.or]: {
slug,
id
}
},
attributes: {
2020-02-05 00:48:55 +01:00
exclude: ['createdAt', 'updatedAt', 'placeId']
},
include: [
2021-04-10 00:20:42 +02:00
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
2020-02-11 11:53:32 +01:00
{ model: Place, attributes: ['name', 'address', 'id'] },
2020-02-05 00:48:55 +01:00
{
model: Resource,
where: !is_admin && { hidden: false },
include: [{ model: APUser, required: false, attributes: ['object', 'ap_id'] }],
required: false,
attributes: ['id', 'activitypub_id', 'data', 'hidden']
},
2020-11-17 00:34:56 +01:00
{ model: Event, required: false, as: 'parent', attributes: ['id', 'recurrent', 'is_visible', 'start_datetime'] }
],
order: [[Resource, 'id', 'DESC']]
})
} catch (e) {
2021-07-08 20:41:56 +02:00
log.error('[EVENT]', e)
return res.sendStatus(400)
}
2020-06-03 22:52:10 +02:00
if (!event) {
2021-04-28 12:44:26 +02:00
return res.sendStatus(404)
2020-06-03 22:52:10 +02:00
}
// get prev and next event
const next = await Event.findOne({
2021-04-26 11:27:14 +02:00
attributes: ['id', 'slug'],
2020-06-03 22:52:10 +02:00
where: {
id: { [Op.not]: event.id },
2020-06-03 22:52:10 +02:00
is_visible: true,
2020-06-13 23:03:07 +02:00
recurrent: null,
[Op.or]: [
{ start_datetime: { [Op.gt]: event.start_datetime } },
{
start_datetime: event.start_datetime,
id: { [Op.gt]: event.id }
}
]
2020-06-03 22:52:10 +02:00
},
order: [['start_datetime', 'ASC'], ['id', 'ASC']]
2020-06-03 22:52:10 +02:00
})
const prev = await Event.findOne({
2021-04-26 11:27:14 +02:00
attributes: ['id', 'slug'],
2020-06-03 22:52:10 +02:00
where: {
is_visible: true,
id: { [Op.not]: event.id },
2020-06-13 23:03:07 +02:00
recurrent: null,
[Op.or]: [
{ start_datetime: { [Op.lt]: event.start_datetime } },
{
start_datetime: event.start_datetime,
id: { [Op.lt]: event.id }
}
]
2020-06-03 22:52:10 +02:00
},
order: [['start_datetime', 'DESC'], ['id', 'DESC']]
2020-06-03 22:52:10 +02:00
})
2021-04-28 12:44:26 +02:00
// TODO: also check if event is mine
2019-07-04 01:02:45 +02:00
if (event && (event.is_visible || is_admin)) {
2020-02-10 01:12:49 +01:00
event = event.get()
2021-04-26 11:27:14 +02:00
event.next = next && (next.slug || next.id)
event.prev = prev && (prev.slug || prev.id)
2019-10-26 00:27:12 +02:00
event.tags = event.tags.map(t => t.tag)
2019-10-20 14:22:55 +02:00
if (format === 'json') {
res.json(event)
} else if (format === 'ics') {
// last arg is alarms/reminder, ref: https://github.com/adamgibbons/ics#attributes (alarms)
exportController.ics(req, res, [event], [{
action: 'display',
trigger: { hours: 1, before: true }
}])
2019-10-20 14:22:55 +02:00
}
} else {
res.sendStatus(404)
}
2019-04-03 00:25:12 +02:00
},
2019-11-06 11:31:56 +01:00
/** confirm an anonymous event
2020-06-04 23:34:48 +02:00
* and send related notifications
2019-11-06 11:31:56 +01:00
*/
2019-09-11 19:12:24 +02:00
async confirm (req, res) {
2019-07-04 00:29:50 +02:00
const id = Number(req.params.event_id)
const event = await Event.findByPk(id, { include: [Place, Tag] })
2021-03-05 14:17:10 +01:00
if (!event) {
log.warn(`Trying to confirm a unknown event, id: ${id}`)
return res.sendStatus(404)
}
2022-02-26 21:27:40 +01:00
if (!res.locals.user.is_admin && res.locals.user.id !== event.userId) {
2021-03-05 14:17:10 +01:00
log.warn(`Someone unallowed is trying to confirm -> "${event.title} `)
return res.sendStatus(403)
}
2019-04-03 00:25:12 +02:00
2021-03-05 14:17:10 +01:00
log.info(`Event "${event.title}" confirmed`)
2019-04-03 00:25:12 +02:00
try {
2019-07-04 01:02:45 +02:00
event.is_visible = true
2019-07-04 01:02:45 +02:00
await event.save()
2019-04-03 00:25:12 +02:00
res.sendStatus(200)
2019-09-11 19:12:24 +02:00
// send notification
const notifier = require('../../notifier')
notifier.notifyEvent('Create', event.id)
2019-04-03 00:25:12 +02:00
} catch (e) {
2021-07-08 20:41:56 +02:00
log.error('[EVENT]', e)
2019-04-03 00:25:12 +02:00
res.sendStatus(404)
}
},
2019-09-11 19:12:24 +02:00
async unconfirm (req, res) {
2019-07-04 00:29:50 +02:00
const id = Number(req.params.event_id)
2019-04-03 00:25:12 +02:00
const event = await Event.findByPk(id)
2019-10-28 17:33:20 +01:00
if (!event) { return req.sendStatus(404) }
2022-02-26 21:27:40 +01:00
if (!res.locals.user.is_admin && res.locals.user.id !== event.userId) {
return res.sendStatus(403)
}
2019-04-03 00:25:12 +02:00
try {
await event.update({ is_visible: false })
2019-04-03 00:25:12 +02:00
res.sendStatus(200)
} catch (e) {
2021-02-09 12:17:10 +01:00
log.info(e)
2019-04-03 00:25:12 +02:00
res.sendStatus(404)
}
},
2019-11-06 11:31:56 +01:00
/** get all unconfirmed events */
2019-09-11 19:12:24 +02:00
async getUnconfirmed (req, res) {
2020-06-02 00:02:02 +02:00
try {
const events = await Event.findAll({
where: {
parentId: null,
is_visible: false,
2020-10-25 00:34:22 +02:00
start_datetime: { [Op.gt]: dayjs().unix() }
2020-06-02 00:02:02 +02:00
},
order: [['start_datetime', 'ASC']],
include: [{ model: Tag, required: false }, Place]
})
res.json(events)
} catch (e) {
2021-02-09 12:17:10 +01:00
log.info(e)
2020-06-02 00:02:02 +02:00
res.sendStatus(400)
}
2019-04-03 00:25:12 +02:00
},
2019-09-11 19:12:24 +02:00
async addNotification (req, res) {
2019-04-03 00:25:12 +02:00
try {
const notification = {
filters: { is_visible: true },
email: req.body.email,
type: 'mail',
remove_code: crypto.randomBytes(16).toString('hex')
}
await Notification.create(notification)
res.sendStatus(200)
} catch (e) {
res.sendStatus(404)
}
},
2019-09-11 19:12:24 +02:00
async delNotification (req, res) {
2019-04-03 00:25:12 +02:00
const remove_code = req.params.code
try {
2020-02-20 20:58:49 +01:00
const notification = await Notification.findOne({ where: { remove_code } })
2019-04-03 00:25:12 +02:00
await notification.destroy()
} catch (e) {
return res.sendStatus(404)
}
res.sendStatus(200)
},
2022-05-03 12:08:10 +02:00
async isAnonEventAllowed (_req, res, next) {
if (!res.locals.settings.allow_anon_event && !res.locals.user) {
2022-03-10 13:51:24 +01:00
return res.sendStatus(403)
}
next()
},
async add (req, res) {
// req.err comes from multer streaming error
if (req.err) {
2021-07-04 00:46:55 +02:00
log.warn(req.err)
return res.status(400).json(req.err.toString())
}
try {
const body = req.body
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
const required_fields = [ 'title', 'start_datetime']
let missing_field = required_fields.find(required_field => !body[required_field])
2022-03-07 17:47:31 +01:00
if (missing_field) {
log.warn(`${missing_field} required`)
return res.status(400).send(`${missing_field} required`)
}
// find or create the place
let place
if (body.place_id) {
place = await Place.findByPk(body.place_id)
} else {
place = await Place.findOne({ where: { name: body.place_name.trim() }})
if (!place) {
if (!body.place_address || !body.place_name) {
return res.status(400).send(`place_id or place_name and place_address required`)
}
place = await Place.create({
name: body.place_name,
address: body.place_address
})
}
2021-07-04 00:46:55 +02:00
}
const eventDetails = {
title: body.title,
2022-05-03 12:08:10 +02:00
// sanitize and linkify html
2022-03-07 17:47:31 +01:00
description: helpers.sanitizeHTML(linkifyHtml(body.description || '')),
multidate: body.multidate,
start_datetime: body.start_datetime,
end_datetime: body.end_datetime,
recurrent,
// publish this event only if authenticated
2022-02-26 21:27:40 +01:00
is_visible: !!res.locals.user
}
if (req.file || body.image_url) {
if (!req.file && body.image_url) {
2022-05-03 12:08:10 +02:00
req.file = await helpers.getImageFromURL(body.image_url)
}
2021-07-21 11:23:32 +02:00
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
focalpoint = [parseFloat(focalpoint[0]).toFixed(2), parseFloat(focalpoint[1]).toFixed(2)]
eventDetails.media = [{
2022-05-03 12:08:10 +02:00
url: req.file.filename,
height: req.file.height,
width: req.file.width,
name: body.image_name || body.title || '',
2021-07-21 11:23:32 +02:00
focalpoint: [parseFloat(focalpoint[0]), parseFloat(focalpoint[1])]
}]
2021-07-19 12:16:16 +02:00
} else {
eventDetails.media = []
}
let event = await Event.create(eventDetails)
await event.setPlace(place)
// create/assign tags
if (body.tags) {
body.tags = body.tags.map(t => t.trim())
await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true })
const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } })
await event.addTags(tags)
}
// associate user to event and reverse
2022-02-26 21:27:40 +01:00
if (res.locals.user) {
await res.locals.user.addEvent(event)
await event.setUser(res.locals.user)
}
event = event.get()
event.tags = body.tags
event.place = place
2020-06-27 02:10:10 +02:00
// return created event to the client
res.json(event)
// create recurrent instances of event if needed
// without waiting for the task manager
if (event.recurrent) {
eventController._createRecurrent()
2020-07-08 00:57:28 +02:00
} else {
2021-12-02 12:09:32 +01:00
// send notifications
2020-07-08 00:57:28 +02:00
const notifier = require('../../notifier')
notifier.notifyEvent('Create', event.id)
}
} catch (e) {
2021-07-08 20:41:56 +02:00
log.error('[EVENT ADD]', e)
2020-06-02 00:02:02 +02:00
res.sendStatus(400)
}
},
async update (req, res) {
2022-02-26 21:27:40 +01:00
if (res.locals.err) {
return res.status(400).json(res.locals.err.toString())
}
try {
const body = req.body
const event = await Event.findByPk(body.id)
if (!event) { return res.sendStatus(404) }
2022-02-26 21:27:40 +01:00
if (!res.locals.user.is_admin && event.userId !== res.locals.user.id) {
return res.sendStatus(403)
}
2020-02-10 12:01:22 +01:00
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
const eventDetails = {
2022-05-03 12:08:10 +02:00
title: body.title || event.title,
// sanitize and linkify html
description: helpers.sanitizeHTML(linkifyHtml(body.description, { target: '_blank' })) || event.description,
multidate: body.multidate,
start_datetime: body.start_datetime,
end_datetime: body.end_datetime,
recurrent
}
2022-05-03 12:08:10 +02:00
// remove old media in case a new one is uploaded
2021-07-19 12:16:16 +02:00
if ((req.file || /^https?:\/\//.test(body.image_url)) && !event.recurrent && event.media && event.media.length) {
2020-02-10 12:01:22 +01:00
try {
2022-05-03 12:08:10 +02:00
const old_path = path.resolve(config.upload_path, event.media[0].url)
const old_thumb_path = path.resolve(config.upload_path, 'thumb', event.media[0].url)
2021-04-26 11:27:14 +02:00
fs.unlinkSync(old_path)
fs.unlinkSync(old_thumb_path)
2020-02-10 12:01:22 +01:00
} catch (e) {
2021-02-09 12:17:10 +01:00
log.info(e.toString())
2020-02-10 12:01:22 +01:00
}
}
2022-05-03 12:08:10 +02:00
// modify associated media only if a new file is uploaded or remote image_url is used
if (req.file || (body.image_url && /^https?:\/\//.test(body.image_url))) {
if (body.image_url) {
2022-05-03 12:08:10 +02:00
req.file = await helpers.getImageFromURL(body.image_url)
}
2021-07-19 12:16:16 +02:00
const focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
eventDetails.media = [{
2022-05-03 12:08:10 +02:00
url: req.file.filename,
2022-05-09 17:16:09 +02:00
height: req.file.height,
width: req.file.width,
2022-05-03 12:08:10 +02:00
name: body.image_name || body.title || '',
focalpoint: [parseFloat(focalpoint[0].slice(0, 6)), parseFloat(focalpoint[1].slice(0, 6))]
}]
} else if (!body.image) {
eventDetails.media = []
}
await event.update(eventDetails)
const [place] = await Place.findOrCreate({
where: { name: body.place_name },
defaults: { address: body.place_address }
})
2020-02-10 12:01:22 +01:00
await event.setPlace(place)
await event.setTags([])
if (body.tags) {
await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true })
const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } })
await event.addTags(tags)
}
const newEvent = await Event.findByPk(event.id, { include: [Tag, Place] })
res.json(newEvent)
// create recurrent instances of event if needed
// without waiting for the task manager
if (event.recurrent) {
eventController._createRecurrent()
} else {
const notifier = require('../../notifier')
notifier.notifyEvent('Update', event.id)
}
} catch (e) {
log.error('[EVENT UPDATE]', e)
res.sendStatus(400)
2020-02-10 12:01:22 +01:00
}
},
async remove (req, res) {
const event = await Event.findByPk(req.params.id)
// check if event is mine (or user is admin)
2022-02-26 21:27:40 +01:00
if (event && (res.locals.user.is_admin || res.locals.user.id === event.userId)) {
if (event.media && event.media.length && !event.recurrent) {
try {
2022-05-03 12:08:10 +02:00
const old_path = path.join(config.upload_path, event.media[0].url)
const old_thumb_path = path.join(config.upload_path, 'thumb', event.media[0].url)
fs.unlinkSync(old_thumb_path)
fs.unlinkSync(old_path)
} catch (e) {
2021-02-09 12:17:10 +01:00
log.info(e.toString())
}
}
const notifier = require('../../notifier')
await notifier.notifyEvent('Delete', event.id)
2021-07-19 12:16:16 +02:00
// unassociate child events
if (event.recurrent) {
await Event.update({ parentId: null }, { where: { parentId: event.id } })
}
log.debug('[EVENT REMOVED] ' + event.title)
await event.destroy()
res.sendStatus(200)
} else {
res.sendStatus(403)
}
},
2021-12-02 12:09:32 +01:00
async _select ({ start, end, tags, places, show_recurrent, max }) {
2020-01-21 01:24:32 +01:00
const where = {
2021-01-11 00:17:56 +01:00
// do not include parent recurrent event
recurrent: null,
2021-01-11 00:17:56 +01:00
2020-10-28 01:28:21 +01:00
// confirmed event only
2020-01-21 01:24:32 +01:00
is_visible: true,
2021-01-11 00:17:56 +01:00
2020-10-27 11:57:11 +01:00
[Op.or]: {
2021-01-11 00:17:56 +01:00
start_datetime: { [Op.gte]: start },
end_datetime: { [Op.gte]: start }
2020-10-27 11:57:11 +01:00
}
2020-10-17 00:41:21 +02:00
}
2021-01-11 00:17:56 +01:00
if (!show_recurrent) {
where.parentId = null
}
2020-10-28 01:28:21 +01:00
if (end) {
2021-01-11 00:17:56 +01:00
where.start_datetime = { [Op.lte]: end }
2020-10-28 01:28:21 +01:00
}
2020-10-25 00:34:22 +02:00
const replacements = []
2022-01-25 01:30:47 +01:00
if (tags && places) {
where[Op.or] = {
2022-01-25 01:30:47 +01:00
placeId: places ? places.split(',') : [],
// '$tags.tag$': Sequelize.literal(`EXISTS (SELECT 1 FROM event_tags WHERE tagTag in ( ${Sequelize.QueryInterface.escape(tags)} ) )`)
}
} else if (tags) {
// where[Op.and] = Sequelize.literal(`EXISTS (SELECT 1 FROM event_tags WHERE eventId=event.id AND tagTag in (?))`)
where[Op.and] = Sequelize.fn('EXISTS', Sequelize.literal('SELECT 1 FROM event_tags WHERE "event_tags"."eventId"="event".id AND "tagTag" in (?)'))
replacements.push(tags)
} else if (places) {
2022-01-25 01:30:47 +01:00
where.placeId = places.split(',')
}
2020-01-15 23:51:57 +01:00
2020-01-27 00:47:03 +01:00
const events = await Event.findAll({
2020-01-15 23:51:57 +01:00
where,
attributes: {
2021-04-13 18:04:53 +02:00
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
2020-01-15 23:51:57 +01:00
},
2021-12-02 12:09:32 +01:00
order: ['start_datetime'],
2020-01-15 23:51:57 +01:00
include: [
{ model: Resource, required: false, attributes: ['id'] },
{
model: Tag,
2021-12-02 12:09:32 +01:00
order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
attributes: ['tag'],
through: { attributes: [] }
},
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
2021-12-02 12:09:32 +01:00
],
limit: max,
replacements
2021-03-10 15:26:09 +01:00
}).catch(e => {
2021-07-08 20:41:56 +02:00
log.error('[EVENT]', e)
return []
2019-04-03 00:25:12 +02:00
})
2019-07-11 23:31:37 +02:00
2020-02-10 01:12:49 +01:00
return events.map(e => {
2020-01-27 00:47:03 +01:00
e = e.get()
e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
2020-01-21 01:24:32 +01:00
return e
2019-07-13 01:02:11 +02:00
})
2020-01-27 00:47:03 +01:00
},
2020-01-21 01:24:32 +01:00
2020-01-27 00:47:03 +01:00
/**
* Select events based on params
*/
async select (req, res) {
2022-03-07 17:47:31 +01:00
const settings = res.locals.settings
2021-12-02 12:09:32 +01:00
const start = req.query.start || dayjs().unix()
2020-10-17 00:41:21 +02:00
const end = req.query.end
const tags = req.query.tags
const places = req.query.places
2021-12-02 12:09:32 +01:00
const max = req.query.max
2022-02-01 12:45:19 +01:00
2022-03-07 17:47:31 +01:00
const show_recurrent = settings.allow_recurrent_event &&
typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settings.recurrent_event_visible
2020-10-17 00:41:21 +02:00
res.json(await eventController._select({
2021-12-02 12:09:32 +01:00
start, end, places, tags, show_recurrent, max
2020-10-17 00:41:21 +02:00
}))
},
2020-01-21 01:24:32 +01:00
/**
2021-05-11 15:12:49 +02:00
* Ensure we have the next instance of a recurrent event
*/
async _createRecurrentOccurrence (e, startAt) {
2021-03-10 15:26:09 +01:00
log.debug(`Create recurrent event [${e.id}] ${e.title}"`)
const event = {
parentId: e.id,
title: e.title,
description: e.description,
media: e.media,
2020-02-10 01:12:49 +01:00
is_visible: true,
userId: e.userId,
placeId: e.placeId
}
const recurrent = e.recurrent
2020-10-25 00:34:22 +02:00
const start_date = dayjs.unix(e.start_datetime)
let cursor = start_date > startAt ? start_date : startAt
startAt = cursor
2020-10-25 00:34:22 +02:00
const duration = dayjs.unix(e.end_datetime).diff(start_date, 's')
const frequency = recurrent.frequency
const type = recurrent.type
2020-11-17 00:34:56 +01:00
cursor = cursor.hour(start_date.hour()).minute(start_date.minute()).second(0)
2021-02-09 12:17:10 +01:00
if (!frequency) { return }
2020-11-17 00:34:56 +01:00
// each week or 2
if (frequency[1] === 'w') {
cursor = cursor.day(start_date.day())
if (cursor.isBefore(startAt)) {
2020-12-04 17:28:41 +01:00
cursor = cursor.add(7, 'day')
}
2021-01-11 00:17:56 +01:00
if (frequency[0] === '2') {
2020-11-17 00:34:56 +01:00
cursor = cursor.add(7, 'day')
}
2020-11-17 00:34:56 +01:00
} else if (frequency === '1m') {
2020-02-10 01:12:49 +01:00
if (type === 'ordinal') {
2020-11-17 00:34:56 +01:00
cursor = cursor.date(start_date.date())
2021-01-11 00:17:56 +01:00
if (cursor.isBefore(startAt)) {
2021-01-11 00:17:56 +01:00
cursor = cursor.add(1, 'month')
}
} else { // weekday
2021-05-11 15:12:49 +02:00
// get weekday
// get recurrent freq details
2021-06-07 00:02:45 +02:00
cursor = helpers.getWeekdayN(cursor, type, start_date.day())
if (cursor.isBefore(startAt)) {
2021-06-07 00:02:45 +02:00
cursor = cursor.add(4, 'week')
cursor = helpers.getWeekdayN(cursor, type, start_date.day())
2021-01-11 00:17:56 +01:00
}
2020-02-10 01:12:49 +01:00
}
2020-11-17 00:34:56 +01:00
}
log.debug(cursor)
2020-11-17 00:34:56 +01:00
event.start_datetime = cursor.unix()
event.end_datetime = event.start_datetime + duration
const newEvent = await Event.create(event)
return newEvent.addTags(e.tags)
},
/**
* Create instances of recurrent events
*/
2020-10-25 00:34:22 +02:00
async _createRecurrent (start_datetime = dayjs().unix()) {
2020-11-17 00:34:56 +01:00
// select recurrent events and its childs
const events = await Event.findAll({
where: { is_visible: true, recurrent: { [Op.ne]: null } },
include: [{ model: Tag, required: false },
{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } }}],
order: [['child', 'start_datetime', 'DESC']]
})
// create a new occurrence for each recurring events but the one's that has an already visible occurrence coming
const creations = events.map(e => {
if (e.child.length) {
if (e.child.find(c => c.is_visible)) return
return eventController._createRecurrentOccurrence(e, dayjs.unix(e.child[0].start_datetime+1))
}
return eventController._createRecurrentOccurrence(e, dayjs())
})
return Promise.all(creations)
}
2019-04-03 00:25:12 +02:00
}
module.exports = eventController