2019-04-03 00:25:12 +02:00
const crypto = require ( 'crypto' )
2019-10-20 14:22:55 +02:00
const moment = require ( 'moment-timezone' )
2020-01-31 14:56:31 +01:00
const path = require ( 'path' )
const config = require ( 'config' )
const fs = require ( 'fs' )
2019-04-03 00:25:12 +02:00
const { Op } = require ( 'sequelize' )
2020-01-21 01:24:32 +01:00
const _ = require ( 'lodash' )
2020-02-10 00:40:23 +01:00
const helpers = require ( '../../helpers' )
2020-02-05 00:48:55 +01:00
const {
event : Event ,
resource : Resource ,
tag : Tag ,
place : Place ,
notification : Notification ,
ap _user : APUser
} = require ( '../models' )
2019-05-30 12:04:14 +02:00
const Sequelize = require ( 'sequelize' )
2019-10-20 14:22:55 +02:00
const exportController = require ( './export' )
2020-01-31 14:56:31 +01:00
2019-09-11 23:17:12 +02:00
const debug = require ( 'debug' ) ( 'controller:event' )
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
} ,
2019-05-30 12:12:51 +02:00
include : [ { model : Event , attributes : [ ] } ] ,
group : [ 'place.id' ]
2019-05-30 12:04:14 +02:00
} )
const tags = await Tag . findAll ( {
2020-01-15 23:51:57 +01:00
raw : true ,
2019-06-07 17:02:33 +02:00
order : [ [ 'weigth' , 'DESC' ] ] ,
2019-05-30 12:04:14 +02:00
attributes : {
2020-01-21 01:24:32 +01:00
exclude : [ 'createdAt' , 'updatedAt' ]
2019-09-11 19:12:24 +02:00
}
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
} ,
2019-10-02 21:04:03 +02:00
async getNotifications ( event , action ) {
debug ( 'getNotifications "%s" (%s)' , 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 ) {
2020-01-21 01:24:32 +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
2019-10-02 21:04:03 +02:00
const ret = notifications . filter ( notification => match ( event , notification . filters ) )
return ret
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 )
} ,
2019-06-14 23:26:13 +02:00
// TODO retrieve next/prev event also
// select id, start_datetime, title from events where start_datetime > (select start_datetime from events where id=89) order by start_datetime limit 20;
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'
2019-07-04 01:09:35 +02:00
const is _admin = req . user && req . user . is _admin
2020-01-15 23:51:57 +01:00
const id = Number ( req . params . event _id )
2020-01-30 12:37:19 +01:00
let event
try {
event = await Event . findByPk ( id , {
attributes : {
2020-02-05 00:48:55 +01:00
exclude : [ 'createdAt' , 'updatedAt' , 'placeId' ]
2020-01-30 12:37:19 +01:00
} ,
include : [
2020-02-05 00:48:55 +01:00
{ model : Tag , required : false , attributes : [ 'tag' , 'weigth' ] , through : { attributes : [ ] } } ,
2020-01-30 12:37:19 +01:00
{ model : Place , attributes : [ 'name' , 'address' ] } ,
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-02-10 01:12:49 +01:00
{ model : Event , required : false , as : 'parent' , attributes : [ 'id' , 'recurrent' ] }
2020-01-30 12:37:19 +01:00
] ,
order : [ [ Resource , 'id' , 'DESC' ] ]
} )
} catch ( e ) {
return res . sendStatus ( 400 )
}
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 ( )
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
* and send its relative notifications
* /
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 )
2019-04-03 00:25:12 +02:00
const event = await Event . findByPk ( id )
2019-09-11 19:12:24 +02:00
if ( ! event ) { return res . sendStatus ( 404 ) }
2019-10-28 17:42:21 +01:00
if ( ! req . user . is _admin && req . user . id !== event . userId ) {
return res . sendStatus ( 403 )
}
2019-04-03 00:25:12 +02:00
try {
2019-07-04 01:02:45 +02:00
event . is _visible = true
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 ) {
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 ) }
2019-10-28 17:42:21 +01:00
if ( ! req . user . is _admin && req . user . id !== event . userId ) {
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 ) {
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 ) {
2019-04-03 00:25:12 +02:00
const events = await Event . findAll ( {
where : {
is _visible : false
} ,
order : [ [ 'start_datetime' , 'ASC' ] ] ,
include : [ Tag , Place ]
} )
res . json ( events )
} ,
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 {
const notification = await Notification . findOne ( { where : { remove _code : { [ Op . eq ] : remove _code } } } )
await notification . destroy ( )
} catch ( e ) {
return res . sendStatus ( 404 )
}
res . sendStatus ( 200 )
} ,
2020-01-31 14:56:31 +01:00
async add ( req , res ) {
// req.err comes from multer streaming error
if ( req . err ) {
debug ( 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 eventDetails = {
title : body . title ,
// remove html tags
2020-02-10 00:45:51 +01:00
description : helpers . sanitizeHTML ( 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
is _visible : ! ! req . user
}
if ( req . file ) {
eventDetails . image _path = req . file . filename
}
const event = await Event . create ( eventDetails )
// create place if needed
const place = await Place . findOrCreate ( {
where : { name : body . place _name } ,
defaults : { address : body . place _address }
} )
. spread ( ( place , created ) => place )
await event . setPlace ( place )
event . place = place
// create/assign tags
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 Promise . all ( tags . map ( t => t . update ( { weigth : Number ( t . weigth ) + 1 } ) ) )
await event . addTags ( tags )
event . tags = tags
}
// associate user to event and reverse
if ( req . user ) {
await req . user . addEvent ( event )
await event . setUser ( req . user )
}
// create recurrent instances of event if needed
// without waiting for the task manager
if ( event . recurrent ) {
eventController . _createRecurrent ( )
}
// return created event to the client
res . json ( event )
// send notification (mastodon/email)
// only if user is authenticated
2020-02-10 01:12:49 +01:00
if ( req . user && ! event . recurrent ) {
2020-01-31 14:56:31 +01:00
const notifier = require ( '../../notifier' )
notifier . notifyEvent ( 'Create' , event . id )
}
} catch ( e ) {
res . sendStatus ( 400 )
debug ( e )
}
} ,
async update ( req , res ) {
if ( req . err ) {
return res . status ( 400 ) . json ( req . err . toString ( ) )
}
const body = req . body
const event = await Event . findByPk ( body . id )
if ( ! req . user . is _admin && event . userId !== req . user . id ) {
return res . sendStatus ( 403 )
}
if ( req . file ) {
if ( event . image _path ) {
const old _path = path . resolve ( config . upload _path , event . image _path )
const old _thumb _path = path . resolve ( config . upload _path , 'thumb' , event . image _path )
await fs . unlink ( old _path , e => console . error ( e ) )
await fs . unlink ( old _thumb _path , e => console . error ( e ) )
}
body . image _path = req . file . filename
}
2020-02-10 00:40:23 +01:00
body . description = helpers . sanitizeHTML ( body . description )
2020-01-31 14:56:31 +01:00
await event . update ( body )
let place
try {
place = await Place . findOrCreate ( {
where : { name : body . place _name } ,
defaults : { address : body . place _address }
} ) . spread ( ( place , created ) => place )
} catch ( e ) {
console . log ( 'error' , e )
}
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 )
const notifier = require ( '../../notifier' )
notifier . notifyEvent ( 'Update' , event . id )
} ,
async remove ( req , res ) {
const event = await Event . findByPk ( req . params . id )
// check if event is mine (or user is admin)
if ( event && ( req . user . is _admin || req . user . id === event . userId ) ) {
if ( event . image _path ) {
const old _path = path . join ( config . upload _path , event . image _path )
const old _thumb _path = path . join ( config . upload _path , 'thumb' , event . image _path )
try {
fs . unlinkSync ( old _thumb _path )
fs . unlinkSync ( old _path )
} catch ( e ) {
debug ( e )
}
}
const notifier = require ( '../../notifier' )
await notifier . notifyEvent ( 'Delete' , event . id )
await event . destroy ( )
res . sendStatus ( 200 )
} else {
res . sendStatus ( 403 )
}
} ,
2020-01-30 12:37:19 +01:00
async _select ( start = moment . unix ( ) , limit = 100 ) {
2020-01-21 01:24:32 +01:00
const where = {
// confirmed event only
2020-01-30 12:37:19 +01:00
recurrent : null ,
2020-01-21 01:24:32 +01:00
is _visible : true ,
2020-01-27 00:47:03 +01:00
start _datetime : { [ Op . gt ] : start }
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 ,
limit ,
attributes : {
2020-02-10 01:12:49 +01:00
exclude : [ 'slug' , 'likes' , 'boost' , 'userId' , 'is_visible' , 'createdAt' , 'updatedAt' , 'placeId' ]
2020-01-15 23:51:57 +01:00
// include: [[Sequelize.fn('COUNT', Sequelize.col('activitypub_id')), 'ressources']]
} ,
order : [ 'start_datetime' , [ Tag , 'weigth' , 'DESC' ] ] ,
include : [
{ model : Resource , required : false , attributes : [ 'id' ] } ,
2020-01-27 00:47:03 +01:00
{ model : Tag , attributes : [ 'tag' ] , required : false , through : { attributes : [ ] } } ,
2019-05-30 12:12:51 +02:00
{ model : Place , required : false , attributes : [ 'id' , 'name' , 'address' ] }
2019-04-29 00:27:29 +02:00
]
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 ) {
const start = req . query . start || moment ( ) . unix ( )
const limit = req . query . limit || 100
2020-01-30 12:37:19 +01:00
res . json ( await eventController . _select ( start , limit ) )
} ,
2020-01-21 01:24:32 +01:00
2020-01-30 12:37:19 +01:00
/ * *
2020-02-10 01:12:49 +01:00
* Ensure we have the next instances of recurrent events
2020-01-30 12:37:19 +01:00
* /
_createRecurrentOccurrence ( e ) {
const event = {
parentId : e . id ,
title : e . title ,
description : e . description ,
image _path : e . image _path ,
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-02-10 01:12:49 +01:00
const cursor = moment ( )
// let cursor = start.startOf('week')
2020-01-30 12:37:19 +01:00
const start _date = moment . unix ( e . start _datetime )
const duration = moment . unix ( e . end _datetime ) . diff ( start _date , 's' )
const frequency = recurrent . frequency
const days = recurrent . days
const type = recurrent . type
// default frequency is '1d' => each day
const toAdd = { n : 1 , unit : 'day' }
// each week or 2 (search for the first specified day)
if ( frequency === '1w' || frequency === '2w' ) {
2020-02-10 01:12:49 +01:00
// cursor.add(days[0] - 1, 'day')
2020-01-30 12:37:19 +01:00
if ( frequency === '2w' ) {
const nWeeks = cursor . diff ( e . start _datetime , 'w' ) % 2
if ( ! nWeeks ) { cursor . add ( 1 , 'week' ) }
}
toAdd . n = Number ( frequency [ 0 ] )
toAdd . unit = 'week'
}
cursor . set ( 'hour' , start _date . hour ( ) ) . set ( 'minute' , start _date . minutes ( ) )
// each month or 2
if ( frequency === '1m' || frequency === '2m' ) {
// find first match
toAdd . n = 1
toAdd . unit = 'month'
if ( type === 'weekday' ) {
} else if ( type === 'ordinal' ) {
}
}
// add event at specified frequency
2020-02-10 01:12:49 +01:00
// const first_event_of_week = cursor.clone()
days . forEach ( d => {
if ( type === 'ordinal' ) {
cursor . date ( d )
} else {
cursor . day ( d - 1 )
}
event . start _datetime = cursor . unix ( )
event . end _datetime = event . start _datetime + duration
Event . create ( event )
cursor . set ( 'hour' , start _date . hour ( ) ) . set ( 'minute' , start _date . minutes ( ) )
} )
// cursor = first_event_of_week.add(toAdd.n, toAdd.unit)
2020-01-30 12:37:19 +01:00
} ,
/ * *
* Create instances of recurrent events
2020-02-10 01:12:49 +01:00
* @ param { Number } start _datetime
2020-01-30 12:37:19 +01:00
* /
async _createRecurrent ( start _datetime = moment ( ) . unix ( ) ) {
// select recurrent events
const events = await Event . findAll ( {
where : { is _visible : true , recurrent : { [ Op . ne ] : null } } ,
2020-02-10 01:12:49 +01:00
include : [ { model : Event , as : 'child' , required : false , where : { start _datetime : { [ Op . gte ] : start _datetime } } } ] ,
2020-01-30 12:37:19 +01:00
order : [ 'start_datetime' ]
} )
2020-02-10 01:12:49 +01:00
const creations = events
. filter ( e => e . child . length === 0 )
. map ( eventController . _createRecurrentOccurrence )
2020-01-30 12:37:19 +01:00
return Promise . all ( creations )
}
2019-04-03 00:25:12 +02:00
}
module . exports = eventController