2019-04-03 00:25:12 +02:00
const crypto = require ( 'crypto' )
2020-01-31 14:56:31 +01:00
const path = require ( 'path' )
2021-09-27 10:42:17 +02:00
const config = require ( '../../config' )
2020-01-31 14:56:31 +01:00
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' )
2022-06-18 01:14:26 +02:00
const Col = helpers . col
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' )
2022-06-18 01:14:26 +02:00
const tagController = require ( './tag' )
2020-01-31 14:56:31 +01:00
2021-02-09 12:17:10 +01:00
const log = require ( '../../log' )
2019-04-03 00:25:12 +02:00
const eventController = {
2022-09-06 21:42:32 +02:00
async searchMeta ( req , res ) {
2022-05-31 15:29:52 +02:00
const search = req . query . search
2019-05-30 12:04:14 +02:00
const places = await Place . findAll ( {
2022-05-31 15:29:52 +02:00
order : [ [ Sequelize . col ( 'w' ) , 'DESC' ] ] ,
where : {
[ Op . or ] : [
2022-09-06 21:42:32 +02:00
Sequelize . where ( Sequelize . fn ( 'LOWER' , Sequelize . col ( 'name' ) ) , 'LIKE' , '%' + search + '%' ) ,
2022-06-18 01:14:26 +02:00
Sequelize . where ( Sequelize . fn ( 'LOWER' , Sequelize . col ( 'address' ) ) , 'LIKE' , '%' + search + '%' )
2022-05-31 15:29:52 +02:00
]
2019-05-30 12:04:14 +02:00
} ,
2022-09-19 05:13:57 +02:00
attributes : [ [ 'name' , 'label' ] , 'address' , 'latitude' , 'longitude' , 'id' , [ Sequelize . cast ( Sequelize . fn ( 'COUNT' , Sequelize . col ( 'events.placeId' ) ) , 'INTEGER' ) , 'w' ] ] ,
2021-04-09 23:54:17 +02:00
include : [ { model : Event , where : { is _visible : true } , required : true , attributes : [ ] } ] ,
2022-05-31 15:29:52 +02:00
group : [ 'place.id' ] ,
raw : true
2019-05-30 12:04:14 +02:00
} )
const tags = await Tag . findAll ( {
2022-05-31 15:29:52 +02:00
order : [ [ Sequelize . col ( 'w' ) , 'DESC' ] ] ,
where : {
tag : Sequelize . where ( Sequelize . fn ( 'LOWER' , Sequelize . col ( 'tag' ) ) , 'LIKE' , '%' + search + '%' ) ,
2022-09-02 08:32:13 +02:00
} ,
2022-09-06 21:42:32 +02:00
attributes : [ [ 'tag' , 'label' ] , [ Sequelize . cast ( Sequelize . fn ( 'COUNT' , Sequelize . col ( 'tag.tag' ) ) , 'INTEGER' ) , 'w' ] ] ,
2021-04-09 23:54:17 +02:00
include : [ { model : Event , where : { is _visible : true } , attributes : [ ] , through : { attributes : [ ] } , required : true } ] ,
2022-05-31 15:29:52 +02:00
group : [ 'tag.tag' ] ,
raw : true
2019-05-30 12:12:51 +02:00
} )
2019-05-30 12:04:14 +02:00
2022-05-31 15:29:52 +02:00
const ret = places . map ( p => {
p . type = 'place'
return p
} ) . concat ( tags . map ( t => {
t . type = 'tag'
return t
2022-09-06 21:42:32 +02:00
} ) ) . sort ( ( a , b ) => b . w - a . w ) . slice ( 0 , 10 )
2020-01-27 00:47:03 +01:00
2022-05-31 15:29:52 +02:00
return res . json ( ret )
2019-04-03 00:25:12 +02:00
} ,
2022-05-31 15:29:52 +02:00
2022-09-06 21:42:32 +02:00
async search ( req , res ) {
2022-06-01 14:09:44 +02:00
const search = req . query . search . trim ( ) . toLocaleLowerCase ( )
const show _recurrent = req . query . show _recurrent || false
const end = req . query . end
const replacements = [ ]
const where = {
// do not include parent recurrent event
recurrent : null ,
// confirmed event only
is _visible : true ,
}
if ( ! show _recurrent ) {
where . parentId = null
}
if ( end ) {
where . start _datetime = { [ Op . lte ] : end }
}
if ( search ) {
replacements . push ( search )
where [ Op . or ] =
2022-09-06 21:42:32 +02:00
[
{ title : Sequelize . where ( Sequelize . fn ( 'LOWER' , Sequelize . col ( 'title' ) ) , 'LIKE' , '%' + search + '%' ) } ,
Sequelize . where ( Sequelize . fn ( 'LOWER' , Sequelize . col ( 'name' ) ) , 'LIKE' , '%' + search + '%' ) ,
Sequelize . fn ( 'EXISTS' , Sequelize . literal ( ` SELECT 1 FROM event_tags WHERE ${ Col ( 'event_tags.eventId' ) } = ${ Col ( 'event.id' ) } AND LOWER( ${ Col ( 'tagTag' ) } ) = ? ` ) )
]
2022-06-01 14:09:44 +02:00
}
2022-05-25 10:54:46 +02:00
2022-05-31 15:29:52 +02:00
const events = await Event . findAll ( {
2022-06-01 14:09:44 +02:00
where ,
2022-05-25 10:54:46 +02:00
attributes : {
2022-06-01 14:09:44 +02:00
exclude : [ 'likes' , 'boost' , 'userId' , 'is_visible' , 'createdAt' , 'updatedAt' , 'description' , 'resources' ]
2022-05-25 10:54:46 +02:00
} ,
2022-06-01 14:09:44 +02:00
order : [ [ 'start_datetime' , 'DESC' ] ] ,
include : [
{
model : Tag ,
2022-06-18 01:14:26 +02:00
// order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
2022-06-01 14:09:44 +02:00
attributes : [ 'tag' ] ,
through : { attributes : [ ] }
} ,
2022-09-19 05:13:57 +02:00
{ model : Place , required : true , attributes : [ 'id' , 'name' , 'address' , 'latitude' , 'longitude' ] }
2022-06-01 14:09:44 +02:00
] ,
replacements ,
limit : 30 ,
} ) . catch ( e => {
log . error ( '[EVENT]' , e )
return res . json ( [ ] )
2022-05-25 10:54:46 +02:00
} )
2022-06-01 14:09:44 +02:00
const ret = events . map ( e => {
e = e . get ( )
e . tags = e . tags ? e . tags . map ( t => t && t . tag ) : [ ]
return e
} )
return res . json ( ret )
2022-05-25 10:54:46 +02:00
} ,
2022-09-06 21:42:32 +02:00
async getNotifications ( event , action ) {
2021-03-05 14:17:10 +01:00
log . debug ( ` getNotifications ${ event . title } ${ action } ` )
2022-09-06 21:42:32 +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
}
}
}
2019-10-02 21:04:03 +02:00
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
} ,
2021-11-11 16:55:11 +01:00
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
}
}
} )
} ,
2022-09-06 21:42:32 +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
2021-11-11 16:55:11 +01:00
const slug = req . params . event _slug
// retrocompatibility, old events URL does not use slug, use id as fallback
const id = Number ( slug ) || - 1
2020-01-30 12:37:19 +01:00
let event
2020-06-03 22:52:10 +02:00
2020-01-30 12:37:19 +01:00
try {
2021-04-13 18:04:53 +02:00
event = await Event . findOne ( {
where : {
[ Op . or ] : {
slug ,
id
}
} ,
2020-01-30 12:37:19 +01:00
attributes : {
2020-02-05 00:48:55 +01:00
exclude : [ 'createdAt' , 'updatedAt' , 'placeId' ]
2020-01-30 12:37:19 +01:00
} ,
include : [
2021-04-10 00:20:42 +02:00
{ model : Tag , required : false , attributes : [ 'tag' ] , through : { attributes : [ ] } } ,
2022-09-19 05:13:57 +02:00
{ model : Place , attributes : [ 'name' , 'address' , 'latitude' , 'longitude' , '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' ] }
2020-01-30 12:37:19 +01:00
] ,
order : [ [ Resource , 'id' , 'DESC' ] ]
} )
} catch ( e ) {
2021-07-08 20:41:56 +02:00
log . error ( '[EVENT]' , e )
2020-01-30 12:37:19 +01:00
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 : {
2022-02-23 09:55:17 +01:00
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 ,
2022-02-23 09:55:17 +01:00
[ Op . or ] : [
{ start _datetime : { [ Op . gt ] : event . start _datetime } } ,
2022-09-02 08:32:13 +02:00
{
2022-02-23 09:55:17 +01:00
start _datetime : event . start _datetime ,
id : { [ Op . gt ] : event . id }
}
]
2020-06-03 22:52:10 +02:00
} ,
2022-02-23 09:55:17 +01: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 ,
2022-02-23 09:55:17 +01:00
id : { [ Op . not ] : event . id } ,
2020-06-13 23:03:07 +02:00
recurrent : null ,
2022-02-23 09:55:17 +01:00
[ Op . or ] : [
{ start _datetime : { [ Op . lt ] : event . start _datetime } } ,
2022-09-02 08:32:13 +02:00
{
2022-02-23 09:55:17 +01:00
start _datetime : event . start _datetime ,
id : { [ Op . lt ] : event . id }
}
]
2020-06-03 22:52:10 +02:00
} ,
2022-02-23 09:55:17 +01:00
order : [ [ 'start_datetime' , 'DESC' ] , [ 'id' , 'DESC' ] ]
2020-06-03 22:52:10 +02:00
} )
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' ) {
2019-11-06 11:21:00 +01:00
// 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
}
2019-06-12 22:26:28 +02:00
} else {
res . sendStatus ( 404 )
}
2019-04-03 00:25:12 +02:00
} ,
2019-11-06 11:31:56 +01:00
/ * * c o n f i r m a n a n o n y m o u s e v e n t
2020-06-04 23:34:48 +02:00
* and send related notifications
2019-11-06 11:31:56 +01:00
* /
2022-09-06 21:42:32 +02:00
async confirm ( req , res ) {
2019-07-04 00:29:50 +02:00
const id = Number ( req . params . event _id )
2020-08-31 17:38:49 +02:00
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 ) {
2022-06-18 01:14:26 +02:00
log . warn ( ` Someone not allowed is trying to confirm -> " ${ event . title } ` )
2019-10-28 17:42:21 +01:00
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
2020-08-31 17:38:49 +02:00
2019-07-04 01:02:45 +02:00
await event . save ( )
2019-07-08 01:53:37 +02:00
2019-04-03 00:25:12 +02:00
res . sendStatus ( 200 )
2019-09-11 19:12:24 +02:00
2019-07-08 01:53:37 +02:00
// send notification
2019-10-02 21:04:03 +02:00
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 )
}
} ,
2022-09-06 21:42:32 +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 ) {
2022-06-18 01:14:26 +02:00
log . warn ( ` Someone not allowed is trying to unconfirm -> " ${ event . title } ` )
2019-10-28 17:42:21 +01:00
return res . sendStatus ( 403 )
}
2019-04-03 00:25:12 +02:00
try {
2020-01-30 12:37:19 +01:00
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 */
2022-09-06 21:42:32 +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
} ,
2022-09-06 21:42:32 +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 )
}
} ,
2022-09-06 21:42:32 +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-09-06 21:42:32 +02:00
async isAnonEventAllowed ( _req , res , next ) {
2022-05-06 23:02:43 +02:00
if ( ! res . locals . settings . allow _anon _event && ! res . locals . user ) {
2022-03-10 13:51:24 +01:00
return res . sendStatus ( 403 )
}
next ( )
} ,
2022-09-06 21:42:32 +02:00
async add ( req , res ) {
2020-01-31 14:56:31 +01:00
// req.err comes from multer streaming error
if ( req . err ) {
2021-07-04 00:46:55 +02:00
log . warn ( req . err )
2020-01-31 14:56:31 +01:00
return res . status ( 400 ) . json ( req . err . toString ( ) )
}
try {
const body = req . body
const recurrent = body . recurrent ? JSON . parse ( body . recurrent ) : null
2022-09-06 21:42:32 +02:00
const required _fields = [ 'title' , 'start_datetime' ]
2022-05-06 23:02:43 +02:00
let missing _field = required _fields . find ( required _field => ! body [ required _field ] )
2022-03-07 17:47:31 +01:00
if ( missing _field ) {
2022-05-06 23:02:43 +02:00
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 )
2022-07-27 11:23:57 +02:00
if ( ! place ) {
return res . status ( 400 ) . send ( ` Place not found ` )
}
2022-05-06 23:02:43 +02:00
} else {
2022-07-27 11:23:57 +02:00
if ( ! body . place _name ) {
return res . status ( 400 ) . send ( ` Place not found ` )
}
2022-09-06 21:42:32 +02:00
place = await Place . findOne ( { where : Sequelize . where ( Sequelize . fn ( 'LOWER' , Sequelize . col ( 'name' ) ) , Op . eq , body . place _name . trim ( ) . toLocaleLowerCase ( ) ) } )
2022-05-06 23:02:43 +02:00
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 ,
2022-09-02 08:32:13 +02:00
address : body . place _address ,
2022-09-19 05:13:57 +02:00
latitude : body . place _latitude ,
longitude : body . place _longitude
2022-05-06 23:02:43 +02:00
} )
}
2021-07-04 00:46:55 +02:00
}
2020-01-31 14:56:31 +01: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 || '' ) ) ,
2020-01-31 14:56:31 +01:00
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
2020-01-31 14:56:31 +01:00
}
2021-07-15 16:18:56 +02:00
if ( req . file || body . image _url ) {
2022-05-05 11:11:40 +02:00
if ( ! req . file && body . image _url ) {
2022-05-03 12:08:10 +02:00
req . file = await helpers . getImageFromURL ( body . image _url )
2021-07-15 16:18:56 +02:00
}
2021-07-21 11:23:32 +02:00
let focalpoint = body . image _focalpoint ? body . image _focalpoint . split ( ',' ) : [ '0' , '0' ]
2022-06-28 18:51:05 +02:00
focalpoint = [ parseFloat ( parseFloat ( focalpoint [ 0 ] ) . toFixed ( 2 ) ) , parseFloat ( parseFloat ( focalpoint [ 1 ] ) . toFixed ( 2 ) ) ]
2021-07-15 16:18:56 +02:00
eventDetails . media = [ {
2022-05-03 12:08:10 +02:00
url : req . file . filename ,
height : req . file . height ,
width : req . file . width ,
2021-11-11 16:55:11 +01:00
name : body . image _name || body . title || '' ,
2022-06-18 01:14:26 +02:00
size : req . file . size || 0 ,
2022-06-28 18:51:05 +02:00
focalpoint
2021-07-15 16:18:56 +02:00
} ]
2021-07-19 12:16:16 +02:00
} else {
eventDetails . media = [ ]
2020-01-31 14:56:31 +01:00
}
2022-05-05 11:11:40 +02:00
let event = await Event . create ( eventDetails )
2020-01-31 14:56:31 +01:00
await event . setPlace ( place )
// create/assign tags
2022-06-18 01:14:26 +02:00
let tags = [ ]
2020-01-31 14:56:31 +01:00
if ( body . tags ) {
2022-06-18 01:14:26 +02:00
tags = await tagController . _findOrCreate ( body . tags )
await event . setTags ( tags )
2020-01-31 14:56:31 +01:00
}
// 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 )
2020-01-31 14:56:31 +01:00
}
2022-05-05 11:11:40 +02:00
event = event . get ( )
2022-06-18 01:14:26 +02:00
event . tags = tags . map ( t => t . tag )
2022-05-05 11:11:40 +02:00
event . place = place
2020-06-27 02:10:10 +02:00
// return created event to the client
res . json ( event )
2020-01-31 14:56:31 +01:00
// 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 )
2020-01-31 14:56:31 +01:00
}
} 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 )
2020-01-31 14:56:31 +01:00
}
} ,
2022-09-06 21:42:32 +02:00
async update ( req , res ) {
2022-06-28 18:51:05 +02:00
if ( res . err ) {
log . warn ( req . err )
return res . status ( 400 ) . json ( req . err . toString ( ) )
2020-01-31 14:56:31 +01:00
}
2021-07-15 16:18:56 +02:00
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 ) {
2021-07-15 16:18:56 +02:00
return res . sendStatus ( 403 )
}
2020-02-10 12:01:22 +01:00
2021-07-15 16:18:56 +02: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
2022-08-02 12:29:32 +02:00
description : helpers . sanitizeHTML ( linkifyHtml ( body . description || '' , { target : '_blank' } ) ) || event . description ,
2021-07-15 16:18:56 +02:00
multidate : body . multidate ,
2022-08-02 12:29:32 +02:00
start _datetime : body . start _datetime || event . start _datetime ,
2021-07-15 16:18:56 +02:00
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
}
2020-01-31 14:56:31 +01:00
}
2022-05-03 12:08:10 +02:00
2022-05-06 23:02:43 +02:00
// modify associated media only if a new file is uploaded or remote image_url is used
2022-08-02 12:29:32 +02:00
if ( req . file || ( body . image _url && /^https?:\/\// . test ( body . image _url ) ) ) {
2022-06-28 18:51:05 +02:00
if ( ! req . file && body . image _url ) {
2022-05-03 12:08:10 +02:00
req . file = await helpers . getImageFromURL ( body . image _url )
2021-07-15 16:18:56 +02:00
}
2020-01-31 14:56:31 +01:00
2022-06-28 18:51:05 +02:00
let focalpoint = body . image _focalpoint ? body . image _focalpoint . split ( ',' ) : [ '0' , '0' ]
focalpoint = [ parseFloat ( parseFloat ( focalpoint [ 0 ] ) . toFixed ( 2 ) ) , parseFloat ( parseFloat ( focalpoint [ 1 ] ) . toFixed ( 2 ) ) ]
2021-07-15 16:18:56 +02:00
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 || '' ,
2022-06-18 01:14:26 +02:00
size : req . file . size || 0 ,
2022-06-28 18:51:05 +02:00
focalpoint
2021-07-15 16:18:56 +02:00
} ]
2022-05-06 23:02:43 +02:00
} else if ( ! body . image ) {
2021-07-15 16:18:56 +02:00
eventDetails . media = [ ]
2022-06-28 18:51:05 +02:00
} else if ( body . image _focalpoint && event . media . length ) {
let focalpoint = body . image _focalpoint ? body . image _focalpoint . split ( ',' ) : [ '0' , '0' ]
focalpoint = [ parseFloat ( parseFloat ( focalpoint [ 0 ] ) . toFixed ( 2 ) ) , parseFloat ( parseFloat ( focalpoint [ 1 ] ) . toFixed ( 2 ) ) ]
2022-09-06 21:42:32 +02:00
eventDetails . media = [ { ... event . media [ 0 ] , focalpoint } ] // [0].focalpoint = focalpoint
2021-07-15 16:18:56 +02:00
}
await event . update ( eventDetails )
2022-08-02 12:29:32 +02:00
// find or create the place
let place
if ( body . place _id ) {
place = await Place . findByPk ( body . place _id )
if ( ! place ) {
return res . status ( 400 ) . send ( ` Place not found ` )
}
} else {
if ( ! body . place _name ) {
return res . status ( 400 ) . send ( ` Place not found ` )
}
2022-09-06 21:42:32 +02:00
place = await Place . findOne ( { where : Sequelize . where ( Sequelize . fn ( 'LOWER' , Sequelize . col ( 'name' ) ) , Op . eq , body . place _name . trim ( ) . toLocaleLowerCase ( ) ) } )
2022-08-02 12:29:32 +02:00
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 ,
2022-09-02 08:32:13 +02:00
address : body . place _address ,
2022-09-19 05:13:57 +02:00
latitude : body . place _latitude ,
longitude : body . place _longitude
2022-08-02 12:29:32 +02:00
} )
}
}
2020-02-10 12:01:22 +01:00
2021-07-15 16:18:56 +02:00
await event . setPlace ( place )
2022-09-02 08:32:13 +02:00
2022-06-21 22:46:30 +02:00
// create/assign tags
let tags = [ ]
2021-07-15 16:18:56 +02:00
if ( body . tags ) {
2022-06-21 22:46:30 +02:00
tags = await tagController . _findOrCreate ( body . tags )
2021-07-15 16:18:56 +02:00
}
2022-09-14 14:51:29 +02:00
await event . setTags ( tags )
2022-06-21 22:46:30 +02:00
2022-08-02 12:29:32 +02:00
let newEvent = await Event . findByPk ( event . id , { include : [ Tag , Place ] } )
newEvent = newEvent . get ( )
newEvent . tags = tags . map ( t => t . tag )
newEvent . place = place
2021-07-15 16:18:56 +02:00
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
}
2020-01-31 14:56:31 +01:00
} ,
2022-09-06 21:42:32 +02:00
async remove ( req , res ) {
2020-01-31 14:56:31 +01:00
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 ) ) {
2021-07-15 16:18:56 +02:00
if ( event . media && event . media . length && ! event . recurrent ) {
2020-01-31 14:56:31 +01:00
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 )
2020-01-31 14:56:31 +01:00
fs . unlinkSync ( old _thumb _path )
fs . unlinkSync ( old _path )
} catch ( e ) {
2021-02-09 12:17:10 +01:00
log . info ( e . toString ( ) )
2020-01-31 14:56:31 +01:00
}
}
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 )
2020-01-31 14:56:31 +01:00
await event . destroy ( )
res . sendStatus ( 200 )
} else {
res . sendStatus ( 403 )
}
} ,
2022-06-18 01:14:26 +02:00
/ * *
* Method to search for events with pagination and filtering
2022-09-18 23:15:22 +02:00
* @ returns
2022-06-18 01:14:26 +02:00
* /
2022-09-06 21:42:32 +02:00
async _select ( {
2022-06-18 01:14:26 +02:00
start = dayjs ( ) . unix ( ) ,
end ,
tags ,
places ,
show _recurrent ,
limit ,
page ,
older } ) {
2021-12-02 12:09:32 +01:00
2020-01-21 01:24:32 +01:00
const where = {
2022-06-18 01:14:26 +02:00
// do not include _parent_ recurrent event
2020-01-30 12:37:19 +01:00
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 ] : {
2022-06-18 01:14:26 +02:00
start _datetime : { [ older ? Op . lte : Op . gte ] : start } ,
end _datetime : { [ older ? Op . lte : Op . gte ] : start }
2020-10-27 11:57:11 +01:00
}
2020-10-17 00:41:21 +02:00
}
2022-06-18 01:14:26 +02:00
// include recurrent events?
2021-01-11 00:17:56 +01:00
if ( ! show _recurrent ) {
where . parentId = null
}
2022-05-20 12:48:11 +02:00
2020-10-28 01:28:21 +01:00
if ( end ) {
2022-06-18 01:14:26 +02:00
where . start _datetime = { [ older ? Op . gte : Op . lte ] : end }
}
// normalize tags
if ( tags ) {
tags = tags . split ( ',' ) . map ( t => t . trim ( ) . toLocaleLowerCase ( ) )
2020-10-28 01:28:21 +01:00
}
2020-10-25 00:34:22 +02:00
2022-05-20 12:48:11 +02:00
const replacements = [ ]
2022-01-25 01:30:47 +01:00
if ( tags && places ) {
2022-09-02 08:32:13 +02:00
where [ Op . and ] = [
2022-09-06 21:42:32 +02:00
{ placeId : places ? places . split ( ',' ) : [ ] } ,
2022-07-27 11:23:57 +02:00
Sequelize . fn ( 'EXISTS' , Sequelize . literal ( ` SELECT 1 FROM event_tags WHERE ${ Col ( 'event_tags.eventId' ) } = ${ Col ( 'event.id' ) } AND LOWER( ${ Col ( 'tagTag' ) } ) in (?) ` ) )
2022-06-18 01:14:26 +02:00
]
replacements . push ( tags )
2022-05-20 12:48:11 +02:00
} else if ( tags ) {
2022-07-27 11:23:57 +02:00
where [ Op . and ] = Sequelize . fn ( 'EXISTS' , Sequelize . literal ( ` SELECT 1 FROM event_tags WHERE ${ Col ( 'event_tags.eventId' ) } = ${ Col ( 'event.id' ) } AND LOWER( ${ Col ( 'tagTag' ) } ) in (?) ` ) )
2022-05-20 12:48:11 +02:00
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
2022-06-18 01:14:26 +02:00
let pagination = { }
if ( limit ) {
2022-09-02 08:32:13 +02:00
pagination = {
2022-06-18 01:14:26 +02:00
limit ,
offset : limit * page ,
2022-09-02 08:32:13 +02:00
}
2022-06-18 01:14:26 +02:00
}
2020-01-27 00:47:03 +01:00
const events = await Event . findAll ( {
2020-01-15 23:51:57 +01:00
where ,
attributes : {
2022-08-09 14:55:30 +02:00
exclude : [ 'likes' , 'boost' , 'userId' , 'is_visible' , 'createdAt' , 'description' , 'resources' , 'recurrent' , 'placeId' , 'image_path' ]
2020-01-15 23:51:57 +01:00
} ,
2022-09-06 21:42:32 +02:00
order : [ [ 'start_datetime' , older ? 'DESC' : 'ASC' ] ] ,
2020-01-15 23:51:57 +01:00
include : [
2021-04-09 23:54:17 +02:00
{
model : Tag ,
2022-06-18 01:14:26 +02:00
// order: [Sequelize.literal('(SELECT COUNT(tagTag) FROM event_tags WHERE tagTag = tag) DESC')],
2021-04-09 23:54:17 +02:00
attributes : [ 'tag' ] ,
through : { attributes : [ ] }
} ,
2022-09-19 05:13:57 +02:00
{ model : Place , required : true , attributes : [ 'id' , 'name' , 'address' , 'latitude' , 'longitude' ] }
2021-12-02 12:09:32 +01:00
] ,
2022-06-18 01:14:26 +02:00
... pagination ,
2022-05-20 12:48:11 +02:00
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
* /
2022-09-06 21:42:32 +02:00
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
2022-09-06 21:52:27 +02:00
const limit = Number ( req . query . max ) || 0
const page = Number ( req . query . page ) || 0
2022-06-18 01:14:26 +02:00
const older = req . query . older || false
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 ( {
2022-06-18 01:14:26 +02:00
start , end , places , tags , show _recurrent , limit , page , older
2020-10-17 00:41:21 +02:00
} ) )
2020-01-30 12:37:19 +01:00
} ,
2020-01-21 01:24:32 +01:00
2020-01-30 12:37:19 +01:00
/ * *
2021-05-11 15:12:49 +02:00
* Ensure we have the next instance of a recurrent event
2020-01-30 12:37:19 +01:00
* /
2022-09-06 21:42:32 +02:00
async _createRecurrentOccurrence ( e , startAt ) {
2021-03-10 15:26:09 +01:00
log . debug ( ` Create recurrent event [ ${ e . id } ] ${ e . title } " ` )
2020-01-30 12:37:19 +01:00
const event = {
parentId : e . id ,
title : e . title ,
description : e . description ,
2021-07-15 16:18:56 +02:00
media : e . media ,
2020-02-10 01:12:49 +01:00
is _visible : true ,
2020-01-30 12:37:19 +01:00
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 )
2022-01-27 23:17:31 +01:00
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' )
2020-01-30 12:37:19 +01:00
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-01-30 12:37:19 +01:00
2020-11-17 00:34:56 +01:00
// each week or 2
if ( frequency [ 1 ] === 'w' ) {
cursor = cursor . day ( start _date . day ( ) )
2022-01-27 23:17:31 +01:00
if ( cursor . isBefore ( startAt ) ) {
2020-12-04 17:28:41 +01:00
cursor = cursor . add ( 7 , 'day' )
2020-01-30 12:37:19 +01:00
}
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-01-30 12:37:19 +01:00
}
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
2022-01-27 23:17:31 +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 ( ) )
2022-01-27 23:17:31 +01:00
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
}
2022-01-27 23:17:31 +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
2022-01-27 23:15:14 +01:00
const newEvent = await Event . create ( event )
return newEvent . addTags ( e . tags )
2020-01-30 12:37:19 +01:00
} ,
/ * *
* Create instances of recurrent events
* /
2022-09-06 21:42:32 +02:00
async _createRecurrent ( start _datetime = dayjs ( ) . unix ( ) ) {
2020-11-17 00:34:56 +01:00
// select recurrent events and its childs
2020-01-30 12:37:19 +01:00
const events = await Event . findAll ( {
where : { is _visible : true , recurrent : { [ Op . ne ] : null } } ,
2022-01-27 23:15:14 +01:00
include : [ { model : Tag , required : false } ,
2022-09-06 21:42:32 +02:00
{ model : Event , as : 'child' , required : false , where : { start _datetime : { [ Op . gte ] : start _datetime } } } ] ,
2022-01-27 23:15:14 +01:00
order : [ [ 'child' , 'start_datetime' , 'DESC' ] ]
2020-01-30 12:37:19 +01:00
} )
2022-01-27 23:17:31 +01:00
// 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
2022-09-06 21:42:32 +02:00
return eventController . _createRecurrentOccurrence ( e , dayjs . unix ( e . child [ 0 ] . start _datetime + 1 ) )
2022-01-27 23:17:31 +01:00
}
return eventController . _createRecurrentOccurrence ( e , dayjs ( ) )
} )
2020-01-30 12:37:19 +01:00
return Promise . all ( creations )
}
2019-04-03 00:25:12 +02:00
}
module . exports = eventController