mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 16:42:22 +01:00
taskManager & recurrent events generation
This commit is contained in:
parent
0d83a48452
commit
6ad7fd1d79
21 changed files with 366 additions and 291 deletions
|
@ -1,5 +1,5 @@
|
|||
<template lang="pug">
|
||||
nuxt-link.event(:to='`/event/${link}`' :class='{ withImg: event.image_path }')
|
||||
nuxt-link.event(:to='`/event/${event.id}`' :class='{ withImg: event.image_path }')
|
||||
|
||||
//- image
|
||||
el-image(v-if='showImage && event.image_path' lazy :src='`/media/thumb/${event.image_path}`')
|
||||
|
@ -24,7 +24,7 @@ import { mapState } from 'vuex'
|
|||
|
||||
export default {
|
||||
props: {
|
||||
event: Object,
|
||||
event: { type: Object, default: () => ({}) },
|
||||
showTags: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
|
@ -35,16 +35,7 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
date () {
|
||||
return new Date(this.event.start_datetime).getDate()
|
||||
},
|
||||
link () {
|
||||
if (this.event.recurrent) {
|
||||
return `${this.event.id}_${this.event.start_datetime}`
|
||||
}
|
||||
return this.event.id
|
||||
}
|
||||
...mapState(['settings'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<template lang="pug">
|
||||
<template lang='pug'>
|
||||
div#list
|
||||
el-divider(v-if='title') {{title}}
|
||||
el-timeline
|
||||
|
@ -6,17 +6,15 @@ div#list
|
|||
v-for='event in events'
|
||||
:key='`${event.id}_${event.start_datetime}`'
|
||||
:timestamp='event|when'
|
||||
placement='top' icon='el-icon-arrow-down' size='large'
|
||||
)
|
||||
placement='top' icon='el-icon-arrow-down' size='large')
|
||||
|
||||
div.float-right
|
||||
small @{{event.place.name}}
|
||||
|
||||
a(:href='"/event/" + link(event)' target='_blank') {{event.title}}
|
||||
a(:href='`/event/${event.id}`' target='_blank') {{event.title}}
|
||||
hr
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
|
@ -51,17 +49,6 @@ export default {
|
|||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return { }
|
||||
},
|
||||
methods: {
|
||||
link (event) {
|
||||
if (event.recurrent) {
|
||||
return `${event.id}_${event.start_datetime}`
|
||||
}
|
||||
return event.id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -72,7 +72,9 @@
|
|||
"title": "Titolo",
|
||||
"user": "Utente",
|
||||
"filter": "Filtra",
|
||||
"event": "Evento"
|
||||
"event": "Evento",
|
||||
"pause": "Pausa",
|
||||
"start": "Avvia"
|
||||
},
|
||||
"login": {
|
||||
"description": "Entrando puoi pubblicare nuovi eventi.",
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
"author": "lesion",
|
||||
"scripts": {
|
||||
"dev:nuxt": "cross-env NODE_ENV=development nuxt dev",
|
||||
"dev": "cross-env DEBUG=*,-babel*,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=development node server/index.js",
|
||||
"dev": "cross-env DEBUG=*,-babel*,-preview-email,-i18n:debug,-email-templates,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=development node server/index.js",
|
||||
"build": "nuxt build",
|
||||
"start": "cross-env DEBUG=*,-babel*,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=production node server/cli.js",
|
||||
"start": "cross-env DEBUG=*,-babel*,-preview-email,-i18n:debug,-email-templates,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=production node server/cli.js",
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
|
||||
"doc": "cd docs && bundle exec jekyll b",
|
||||
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
||||
|
@ -82,7 +82,7 @@
|
|||
"multer": "^1.4.2",
|
||||
"nuxt": "^2.11.0",
|
||||
"nuxt-express-module": "^0.0.11",
|
||||
"pg": "^7.17.1",
|
||||
"pg": "^7.18.1",
|
||||
"sanitize-html": "^1.21.1",
|
||||
"sequelize": "^5.21.3",
|
||||
"sequelize-cli": "^5.5.1",
|
||||
|
|
|
@ -52,10 +52,10 @@ export default {
|
|||
async register () {
|
||||
this.loading = true
|
||||
try {
|
||||
const { user } = await this.$axios.$post('/user/register', this.user)
|
||||
await this.$axios.$post('/user/register', this.user)
|
||||
Message({
|
||||
showClose: true,
|
||||
message: this.$t(`register.${user.is_admin ? 'admin_' : ''}complete`),
|
||||
message: this.$t('register.complete'),
|
||||
type: 'success'
|
||||
})
|
||||
this.close()
|
||||
|
|
|
@ -135,7 +135,7 @@ export default {
|
|||
data.event.type = 'multidate'
|
||||
} else if (event.recurrent) {
|
||||
data.event.type = 'recurrent'
|
||||
data.event.recurrent = JSON.parse(event.recurrent)
|
||||
data.event.recurrent = event.recurrent
|
||||
} else {
|
||||
data.event.type = 'normal'
|
||||
data.date = moment.unix(event.start_datetime)
|
||||
|
@ -184,15 +184,15 @@ export default {
|
|||
if (!dates || !dates.length) { return '' }
|
||||
|
||||
const freq = this.event.recurrent.frequency
|
||||
const weekDays = _(dates).map(date => moment(date).format('dddd')).uniq()
|
||||
const weekDays = _(dates).map(date => moment(date).format('dddd')).uniq().value()
|
||||
if (freq === '1w' || freq === '2w') {
|
||||
return this.$t(`event.recurrent_${freq}_days`, { days: weekDays.join(', ') })
|
||||
} else if (freq === '1m' || freq === '2m') {
|
||||
const days = _(dates).map(date => moment(date).date()).uniq()
|
||||
const days = _(dates).map(date => moment(date).date()).uniq().value()
|
||||
const n = Math.floor((days[0] - 1) / 7) + 1
|
||||
return [
|
||||
{ label: this.$tc(`event.recurrent_${freq}_days`, days.length, { days }), key: 'ordinal' },
|
||||
{ label: this.$tc(`event.recurrent_${freq}_ordinal`, days.length, { n: this.$t(`ordinal.${n}`), days: weekDays.join(', ') }), key: 'weekday' }
|
||||
{ label: this.$tc(`event.recurrent_${freq}_ordinal`, days.length, { n, days: weekDays.join(', ') }), key: 'weekday' }
|
||||
]
|
||||
} else if (freq === '1d') {
|
||||
return this.$t('event.recurrent_each_day')
|
||||
|
@ -231,7 +231,6 @@ export default {
|
|||
.filter(e => !e.multidate && (!e.recurrent || this.event.type === 'recurrent'))
|
||||
.map(e => ({ key: e.id, dot: { color: this.event.type === 'recurrent' ? 'orange' : 'green' }, dates: moment.unix(e.start_datetime).toDate() })))
|
||||
|
||||
console.error(this.event.type)
|
||||
if (this.event.type === 'recurrent' && this.date && this.date.length) {
|
||||
attributes.push({
|
||||
key: 'recurrent',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template lang="pug">
|
||||
nuxt-link.embed_event(:to='`/event/${link}`' target='_blank' :class='{ withImg: event.image_path }')
|
||||
nuxt-link.embed_event(:to='`/event/${id}`' target='_blank' :class='{ withImg: event.image_path }')
|
||||
|
||||
//- image
|
||||
img.float-left(v-if='event.image_path' :src='`/media/thumb/${event.image_path}`')
|
||||
|
@ -14,7 +14,6 @@
|
|||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import Event from '../../components/Event'
|
||||
|
||||
export default {
|
||||
|
@ -22,10 +21,8 @@ export default {
|
|||
components: { Event },
|
||||
async asyncData ({ $axios, params, error, store }) {
|
||||
try {
|
||||
const [id, start_datetime] = params.event_id.split('_')
|
||||
const event = await $axios.$get(`/event/${id}`)
|
||||
event.start_datetime = start_datetime ? Number(start_datetime) : event.start_datetime
|
||||
return { event, id: Number(id) }
|
||||
const event = await $axios.$get(`/event/${params.event_id}`)
|
||||
return { event, id: Number(params.event_id) }
|
||||
} catch (e) {
|
||||
error({ statusCode: 404, message: 'Event not found' })
|
||||
}
|
||||
|
@ -34,31 +31,10 @@ export default {
|
|||
return {
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
date () {
|
||||
return new Date(this.event.start_datetime).getDate()
|
||||
},
|
||||
link () {
|
||||
if (this.event.recurrent) {
|
||||
return `${this.event.id}_${this.event.start_datetime}`
|
||||
}
|
||||
return this.event.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <style>
|
||||
.embedded_gancio {
|
||||
border: none;
|
||||
width: 450px;
|
||||
height: 220px;
|
||||
float: left;
|
||||
}</style>
|
||||
<iframe src='http://localhost:13120/embed/1' class='embedded_gancio'></iframe>
|
||||
*/
|
||||
// <iframe src='http://localhost:13120/embed/1' class='embedded_gancio'></iframe>
|
||||
</script>
|
||||
<style lang='less'>
|
||||
.embed_event {
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
//- info & actions for desktop
|
||||
el-col.menu(:sm='6' :xs='24')
|
||||
el-menu.menu.mt-2(router)
|
||||
p <i class='el-icon-time'></i> <b>{{event|when}}</b> <br/><small>{{event|to}}</small>
|
||||
p <i class='el-icon-map-location'></i> <b>{{event.place.name}}</b> - {{event.place.address}}
|
||||
p <i class='el-icon-date'></i> <b>{{event|when}}</b> <br/><small>{{event|to}}</small>
|
||||
p <i class='el-icon-location-outline'></i> <b>{{event.place.name}}</b> - {{event.place.address}}
|
||||
el-divider {{$t('common.actions')}}
|
||||
el-menu-item(
|
||||
v-clipboard:success='copyLink'
|
||||
|
@ -80,7 +80,7 @@ import EmbedEvent from './embedEvent'
|
|||
import FollowMe from './followMe'
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
|
||||
import moment from 'dayjs'
|
||||
import moment from 'moment-timezone'
|
||||
|
||||
export default {
|
||||
name: 'Event',
|
||||
|
@ -88,17 +88,8 @@ export default {
|
|||
components: { EventAdmin, EmbedEvent, FollowMe },
|
||||
async asyncData ({ $axios, params, error, store }) {
|
||||
try {
|
||||
const [id, start_datetime] = params.id.split('_')
|
||||
const event = await $axios.$get(`/event/${id}`)
|
||||
event.start_datetime = start_datetime
|
||||
? Number(start_datetime)
|
||||
: event.start_datetime
|
||||
// const now = new Date()
|
||||
// const events = await $axios.$get(
|
||||
// `/event/${now.getMonth()}/${now.getFullYear()}`
|
||||
// )
|
||||
// store.commit('setEvents', events)
|
||||
return { event, id: Number(id) }
|
||||
const event = await $axios.$get(`/event/${params.id}`)
|
||||
return { event, id: Number(params.id) }
|
||||
} catch (e) {
|
||||
error({ statusCode: 404, message: 'Event not found' })
|
||||
}
|
||||
|
@ -201,9 +192,6 @@ export default {
|
|||
if (!event) {
|
||||
return false
|
||||
}
|
||||
if (event.recurrent) {
|
||||
return `${event.id}_${event.start_datetime}`
|
||||
}
|
||||
return event.id
|
||||
},
|
||||
prev () {
|
||||
|
@ -220,9 +208,6 @@ export default {
|
|||
if (!event) {
|
||||
return false
|
||||
}
|
||||
if (event.recurrent) {
|
||||
return `${event.id}_${event.start_datetime}`
|
||||
}
|
||||
return event.id
|
||||
},
|
||||
imgPath () {
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
<template lang='pug'>
|
||||
el-menu.menu
|
||||
div
|
||||
el-divider {{$t('common.admin')}}
|
||||
el-menu.menu
|
||||
el-menu-item
|
||||
div(@click.prevents='toggle') {{$t(event.is_visible?'common.hide':'common.confirm')}}
|
||||
div(v-if='event.is_visible' @click='toggle(false)') <i class='el-icon-open'/> {{$t('common.hide')}}
|
||||
div(v-else @click='toggle(false)') <i class='el-icon-turn-off'/> {{$t('common.confirm')}}
|
||||
el-menu-item
|
||||
div(@click.prevent='remove') {{$t('common.remove')}}
|
||||
el-menu-item(@click='$router.replace(`/add/${event.id}`)') {{$t('common.edit')}}
|
||||
div(@click='remove(false)') <i class='el-icon-delete'/> {{$t('common.remove')}}
|
||||
el-menu-item(@click='$router.replace(`/add/${event.id}`)') <i class='el-icon-edit'/> {{$t('common.edit')}}
|
||||
div(v-if='event.parentId')
|
||||
el-divider {{$t('event.recurrent')}}
|
||||
el-menu-item(v-if='event.parent.is_visible' @click='toggle(true)') <i class='el-icon-video-pause'/> {{$t('common.pause')}}
|
||||
el-menu-item(v-else @click='toggle(true)') <i class='el-icon-video-play'/> {{$t('common.start')}}
|
||||
el-menu-item(@click='remove(true)') <i class='el-icon-delete'/> {{$t('common.remove')}}
|
||||
el-menu-item(@click='$router.replace(`/add/${event.parentId}`)') <i class='el-icon-edit'/> {{$t('common.edit')}}
|
||||
</template>
|
||||
<script>
|
||||
import { MessageBox } from 'element-ui'
|
||||
|
@ -13,30 +21,39 @@ import { mapActions } from 'vuex'
|
|||
|
||||
export default {
|
||||
name: 'EventAdmin',
|
||||
props: ['event'],
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['delEvent']),
|
||||
async remove () {
|
||||
async remove (parent = false) {
|
||||
try {
|
||||
await MessageBox.confirm(this.$t('event.remove_confirmation'), this.$t('common.confirm'), {
|
||||
confirmButtonText: this.$t('common.ok'),
|
||||
cancelButtonText: this.$t('common.cancel'),
|
||||
type: 'error' })
|
||||
await this.$axios.delete(`/user/event/${this.event.id}`)
|
||||
this.delEvent(Number(this.event.id))
|
||||
type: 'error'
|
||||
})
|
||||
const id = parent ? this.event.parentId : this.event.id
|
||||
await this.$axios.delete(`/user/event/${id}`)
|
||||
this.delEvent(Number(id))
|
||||
this.$router.replace('/')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
async toggle () {
|
||||
async toggle (parent = false) {
|
||||
const id = parent ? this.event.parentId : this.event.id
|
||||
const is_visible = parent ? this.event.parent.is_visible : this.event.is_visible
|
||||
const method = is_visible ? 'unconfirm' : 'confirm'
|
||||
try {
|
||||
if (this.event.is_visible) {
|
||||
await this.$axios.$get(`/event/unconfirm/${this.event.id}`)
|
||||
this.event.is_visible = false
|
||||
await this.$axios.$get(`/event/${method}/${id}`)
|
||||
if (parent) {
|
||||
this.event.parent.is_visible = !is_visible
|
||||
} else {
|
||||
await this.$axios.$get(`/event/confirm/${this.event.id}`)
|
||||
this.event.is_visible = true
|
||||
this.event.is_visible = !is_visible
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Vue from 'vue'
|
||||
import moment from 'moment-timezone'
|
||||
import url from 'url'
|
||||
|
||||
export default ({ app, store }) => {
|
||||
// set timezone to instance_timezone!!
|
||||
|
@ -28,7 +27,7 @@ export default ({ app, store }) => {
|
|||
const normal = `${start.format('dddd, D MMMM (HH:mm-')}${end.format('HH:mm) ')}`
|
||||
// recurrent event
|
||||
if (event.recurrent && where !== 'home') {
|
||||
const { frequency, days, type } = JSON.parse(event.recurrent)
|
||||
const { frequency, days, type } = event.recurrent
|
||||
if (frequency === '1w' || frequency === '2w') {
|
||||
const recurrent = app.i18n.tc(`event.recurrent_${frequency}_days`, days.length, { days: days.map(d => moment().day(d - 1).format('dddd')) })
|
||||
return `${normal} - ${recurrent}`
|
||||
|
|
|
@ -6,6 +6,7 @@ const { event: Event, resource: Resource, tag: Tag, place: Place, notification:
|
|||
const Sequelize = require('sequelize')
|
||||
const exportController = require('./export')
|
||||
const debug = require('debug')('controller:event')
|
||||
// const { Task, TaskManager } = require('../../taskManager')
|
||||
|
||||
const eventController = {
|
||||
|
||||
|
@ -76,17 +77,23 @@ const eventController = {
|
|||
const format = req.params.format || 'json'
|
||||
const is_admin = req.user && req.user.is_admin
|
||||
const id = Number(req.params.event_id)
|
||||
let event = await Event.findByPk(id, {
|
||||
let event
|
||||
try {
|
||||
event = await Event.findByPk(id, {
|
||||
attributes: {
|
||||
exclude: ['createdAt', 'updatedAt']
|
||||
},
|
||||
include: [
|
||||
{ model: Tag, attributes: ['tag', 'weigth'], through: { attributes: [] } },
|
||||
{ model: Place, attributes: ['name', 'address'] },
|
||||
{ model: Resource, where: !is_admin && { hidden: false }, required: false }
|
||||
{ model: Resource, where: !is_admin && { hidden: false }, required: false },
|
||||
{ model: Event, required: false, as: 'parent' }
|
||||
],
|
||||
order: [[Resource, 'id', 'DESC']]
|
||||
})
|
||||
} catch (e) {
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
if (event && (event.is_visible || is_admin)) {
|
||||
event = event.toJSON()
|
||||
|
@ -139,8 +146,7 @@ const eventController = {
|
|||
}
|
||||
|
||||
try {
|
||||
event.is_visible = false
|
||||
await event.save()
|
||||
await event.update({ is_visible: false })
|
||||
res.sendStatus(200)
|
||||
} catch (e) {
|
||||
res.sendStatus(404)
|
||||
|
@ -185,112 +191,14 @@ const eventController = {
|
|||
res.sendStatus(200)
|
||||
},
|
||||
|
||||
// async addRecurrent (start, places, where_tags, limit) {
|
||||
// const where = {
|
||||
// is_visible: true,
|
||||
// recurrent: { [Op.ne]: null }
|
||||
// // placeId: places
|
||||
// }
|
||||
|
||||
// const events = await Event.findAll({
|
||||
// where,
|
||||
// limit,
|
||||
// attributes: {
|
||||
// exclude: ['slug', 'likes', 'boost', 'userId', 'is_visible', 'description', 'createdAt', 'updatedAt', 'placeId']
|
||||
// },
|
||||
// order: ['start_datetime', [Tag, 'weigth', 'DESC']],
|
||||
// include: [
|
||||
// { model: Resource, required: false, attributes: ['id'] },
|
||||
// { model: Tag, ...where_tags, attributes: ['tag'], through: { attributes: [] } },
|
||||
// { model: Place, required: false, attributes: ['id', 'name', 'address'] }
|
||||
// ]
|
||||
// })
|
||||
|
||||
// let allEvents = []
|
||||
// _.forEach(events, e => {
|
||||
// allEvents = allEvents.concat(eventController.createEventsFromRecurrent(e.get(), start))
|
||||
// })
|
||||
|
||||
// return allEvents
|
||||
// },
|
||||
|
||||
// // build singular events from a recurrent pattern
|
||||
// createEventsFromRecurrent (e, start, dueTo = null) {
|
||||
// const events = []
|
||||
// const recurrent = JSON.parse(e.recurrent)
|
||||
// if (!recurrent.frequency) { return false }
|
||||
// if (!dueTo) {
|
||||
// dueTo = start.add(2, 'month')
|
||||
// }
|
||||
// let cursor = start.startOf('week')
|
||||
// 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') {
|
||||
// cursor.add(days[0] - 1, 'day')
|
||||
// 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())
|
||||
// }
|
||||
|
||||
// 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
|
||||
// while (true) {
|
||||
// const first_event_of_week = cursor.clone()
|
||||
// days.forEach(d => {
|
||||
// if (type === 'ordinal') {
|
||||
// cursor.date(d)
|
||||
// } else {
|
||||
// cursor.day(d - 1)
|
||||
// }
|
||||
// if (cursor.isAfter(dueTo) || cursor.isBefore(start)) { return }
|
||||
// e.start_datetime = cursor.unix()
|
||||
// e.end_datetime = e.start_datetime + duration
|
||||
// events.push(Object.assign({}, e))
|
||||
// })
|
||||
// if (cursor.isAfter(dueTo)) { break }
|
||||
// cursor = first_event_of_week.add(toAdd.n, toAdd.unit)
|
||||
// cursor.set('hour', start_date.hour()).set('minute', start_date.minutes())
|
||||
// }
|
||||
|
||||
// return events
|
||||
// },
|
||||
|
||||
async _select (start = moment.unix(), limit = 100, show_recurrent = true) {
|
||||
async _select (start = moment.unix(), limit = 100) {
|
||||
const where = {
|
||||
// confirmed event only
|
||||
recurrent: null,
|
||||
is_visible: true,
|
||||
start_datetime: { [Op.gt]: start }
|
||||
}
|
||||
|
||||
if (!show_recurrent) {
|
||||
where.recurrent = null
|
||||
}
|
||||
|
||||
const events = await Event.findAll({
|
||||
where,
|
||||
limit,
|
||||
|
@ -319,59 +227,104 @@ const eventController = {
|
|||
async select (req, res) {
|
||||
const start = req.query.start || moment().unix()
|
||||
const limit = req.query.limit || 100
|
||||
const show_recurrent = req.query.show_recurrent || true
|
||||
res.json(await eventController._select(start, limit, show_recurrent))
|
||||
// const filter_tags = req.query.tags || ''
|
||||
// const filter_places = req.query.places || ''
|
||||
res.json(await eventController._select(start, limit))
|
||||
},
|
||||
|
||||
// debug(`select limit:${limit} rec:${show_recurrent} tags:${filter_tags} places:${filter_places}`)
|
||||
// let where_tags = {}
|
||||
// const where = {
|
||||
// // confirmed event only
|
||||
// is_visible: true,
|
||||
// start_datetime: { [Op.gt]: start },
|
||||
// recurrent: null
|
||||
// }
|
||||
|
||||
// if (filter_tags) {
|
||||
// where_tags = { where: { tag: filter_tags.split(',') } }
|
||||
// }
|
||||
|
||||
// if (filter_places) {
|
||||
// where.placeId = filter_places.split(',')
|
||||
// }
|
||||
|
||||
// let events = await Event.findAll({
|
||||
// where,
|
||||
// limit,
|
||||
// attributes: {
|
||||
// exclude: ['slug', 'likes', 'boost', 'userId', 'is_visible', 'description', 'createdAt', 'updatedAt', 'placeId']
|
||||
// // include: [[Sequelize.fn('COUNT', Sequelize.col('activitypub_id')), 'ressources']]
|
||||
// },
|
||||
// order: ['start_datetime', [Tag, 'weigth', 'DESC']],
|
||||
// include: [
|
||||
// { model: Resource, required: false, attributes: ['id'] },
|
||||
// { model: Tag, ...where_tags, attributes: ['tag'], through: { attributes: [] } },
|
||||
// { model: Place, required: false, attributes: ['id', 'name', 'address'] }
|
||||
// ]
|
||||
// })
|
||||
|
||||
// let recurrentEvents = []
|
||||
// events = _.map(events, e => e.get())
|
||||
// if (show_recurrent) {
|
||||
// recurrentEvents = await eventController.addRecurrent(moment.unix(start), where.placeId, where_tags, limit)
|
||||
// events = _.concat(events, recurrentEvents)
|
||||
// }
|
||||
|
||||
// // flat tags
|
||||
// events = _(events).map(e => {
|
||||
// e.tags = e.tags.map(t => t.tag)
|
||||
// return e
|
||||
// })
|
||||
|
||||
// res.json(events.sort((a, b) => a.start_datetime - b.start_datetime))
|
||||
/**
|
||||
* Ensure we have at least 3 instances of recurrent events
|
||||
*/
|
||||
_createRecurrentOccurrence (e) {
|
||||
const event = {
|
||||
parentId: e.id,
|
||||
title: e.title,
|
||||
description: e.description,
|
||||
image_path: e.image_path,
|
||||
is_visible: e.is_visible,
|
||||
userId: e.userId,
|
||||
placeId: e.placeId
|
||||
}
|
||||
|
||||
const recurrent = e.recurrent
|
||||
let left = 3 - e.child.length
|
||||
const start = e.child.length ? moment.unix(e.child[e.child.length - 1].start_datetime) : moment()
|
||||
let cursor = start.startOf('week')
|
||||
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') {
|
||||
cursor.add(days[0] - 1, 'day')
|
||||
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
|
||||
while (true) {
|
||||
if (!left) { break }
|
||||
left -= 1
|
||||
const first_event_of_week = cursor.clone()
|
||||
days.forEach(d => {
|
||||
debug(cursor)
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create instances of recurrent events
|
||||
* Remove old
|
||||
* @param {*} start_datetime
|
||||
*/
|
||||
async _createRecurrent (start_datetime = moment().unix()) {
|
||||
// select recurrent events
|
||||
const events = await Event.findAll({
|
||||
where: { is_visible: true, recurrent: { [Op.ne]: null } },
|
||||
include: [{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gt]: start_datetime } } }],
|
||||
order: ['start_datetime']
|
||||
})
|
||||
|
||||
const creations = []
|
||||
events
|
||||
.filter(e => e.child && e.child.length < 3)
|
||||
.forEach(e => {
|
||||
eventController._createRecurrentOccurrence(e)
|
||||
})
|
||||
|
||||
return Promise.all(creations)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = eventController
|
||||
|
|
|
@ -7,6 +7,7 @@ const config = require('config')
|
|||
const mail = require('../mail')
|
||||
const { user: User, event: Event, tag: Tag, place: Place } = require('../models')
|
||||
const settingsController = require('./settings')
|
||||
const eventController = require('./event')
|
||||
const debug = require('debug')('user:controller')
|
||||
|
||||
const userController = {
|
||||
|
@ -89,6 +90,12 @@ const userController = {
|
|||
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)
|
||||
|
||||
|
@ -155,7 +162,7 @@ const userController = {
|
|||
if (!user) { return res.sendStatus(200) }
|
||||
|
||||
user.recover_code = crypto.randomBytes(16).toString('hex')
|
||||
mail.send(user.email, 'recover', { user, config })
|
||||
mail.send(user.email, 'recover', { user, config }, req.settings.locale)
|
||||
|
||||
await user.save()
|
||||
res.sendStatus(200)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const Email = require('email-templates')
|
||||
const path = require('path')
|
||||
const moment = require('moment')
|
||||
const moment = require('moment-timezone')
|
||||
const config = require('config')
|
||||
const settings = require('./controller/settings')
|
||||
const debug = require('debug')('email')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const config = require('config')
|
||||
const moment = require('moment')
|
||||
const moment = require('moment-timezone')
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Event = sequelize.define('event', {
|
||||
|
@ -24,7 +24,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||
image_path: DataTypes.STRING,
|
||||
is_visible: DataTypes.BOOLEAN,
|
||||
recurrent: DataTypes.JSON,
|
||||
// parent: DataTypes.INTEGER
|
||||
// parent: DataTypes.INTEGER,
|
||||
likes: { type: DataTypes.JSON, defaultValue: [] },
|
||||
boost: { type: DataTypes.JSON, defaultValue: [] }
|
||||
}, {})
|
||||
|
@ -35,6 +35,8 @@ module.exports = (sequelize, DataTypes) => {
|
|||
Event.belongsToMany(models.tag, { through: 'event_tags' })
|
||||
Event.belongsToMany(models.notification, { through: 'event_notification' })
|
||||
Event.hasMany(models.resource)
|
||||
Event.hasMany(Event, { as: 'child', foreignKey: 'parentId' })
|
||||
Event.belongsTo(models.event, { as: 'parent' })
|
||||
}
|
||||
|
||||
Event.prototype.toNoteAP = function (username, follower = []) {
|
||||
|
|
|
@ -21,7 +21,6 @@ module.exports = (sequelize, DataTypes) => {
|
|||
|
||||
Notification.associate = function (models) {
|
||||
Notification.belongsToMany(models.event, { through: 'event_notification' })
|
||||
// associations can be defined here
|
||||
}
|
||||
return Notification
|
||||
}
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
const { Nuxt, Builder } = require('nuxt')
|
||||
|
||||
// Import and Set Nuxt.js options
|
||||
const nuxt_config = require('../nuxt.config.js')
|
||||
const nuxtConfig = require('../nuxt.config.js')
|
||||
const config = require('config')
|
||||
const consola = require('consola')
|
||||
const { TaskManager } = require('./taskManager')
|
||||
|
||||
async function main () {
|
||||
nuxt_config.server = config.server
|
||||
nuxtConfig.server = config.server
|
||||
|
||||
// Init Nuxt.js
|
||||
const nuxt = new Nuxt(nuxt_config)
|
||||
const nuxt = new Nuxt(nuxtConfig)
|
||||
|
||||
// Build only in dev mode
|
||||
if (nuxt_config.dev) {
|
||||
if (nuxtConfig.dev) {
|
||||
const builder = new Builder(nuxt)
|
||||
await builder.build()
|
||||
} else {
|
||||
|
@ -20,9 +21,11 @@ async function main () {
|
|||
}
|
||||
nuxt.listen()
|
||||
consola.info('Listen on %s:%d , visit me here => %s', config.server.host, config.server.port, config.baseurl)
|
||||
TaskManager.start()
|
||||
|
||||
// close connections/port/unix socket
|
||||
function shutdown () {
|
||||
TaskManager.stop()
|
||||
nuxt.close(async () => {
|
||||
const db = require('./api/models')
|
||||
await db.sequelize.close()
|
||||
|
|
19
server/migrations/20200127101541-event_parent.js
Normal file
19
server/migrations/20200127101541-event_parent.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
// return Promise.resolve(1)
|
||||
return queryInterface.addColumn('events', 'parentId', {
|
||||
type: Sequelize.INTEGER,
|
||||
references: {
|
||||
model: 'events',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.removeColumn('events', 'parentId')
|
||||
}
|
||||
}
|
19
server/migrations/20200127113535-fix_recurrent.js
Normal file
19
server/migrations/20200127113535-fix_recurrent.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
const { event: Event } = require('../api/models')
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
const events = await Event.findAll({})
|
||||
const promises = events.map(e => {
|
||||
return e.update({ recurrent: JSON.parse(e.recurrent) })
|
||||
})
|
||||
return Promise.all(promises)
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
const events = await Event.findAll({})
|
||||
const promises = events.map(e => {
|
||||
return e.update({ recurrent: JSON.stringify(e.recurrent) })
|
||||
})
|
||||
return Promise.all(promises)
|
||||
}
|
||||
}
|
|
@ -8,12 +8,6 @@ module.exports = {
|
|||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
/*
|
||||
Add reverting commands here.
|
||||
Return a promise to correctly handle asynchronicity.
|
||||
|
||||
Example:
|
||||
return queryInterface.dropTable('users');
|
||||
*/
|
||||
return queryInterface.removeIndex('users', ['email'])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
123
server/taskManager.js
Normal file
123
server/taskManager.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
const debug = require('debug')('TaskManager')
|
||||
const eventController = require('./api/controller/event')
|
||||
// const notifier = require('./notifier')
|
||||
|
||||
class Task {
|
||||
constructor ({ name, removable = false, repeatEach = 1, method, args = [] }) {
|
||||
this.name = name
|
||||
this.removable = removable
|
||||
this.repeatEach = repeatEach
|
||||
this.processInNTick = repeatEach
|
||||
this.method = method
|
||||
this.args = args
|
||||
}
|
||||
|
||||
process () {
|
||||
--this.processInNTick
|
||||
if (this.processInNTick > 0) {
|
||||
return
|
||||
}
|
||||
this.processInNTick = this.repeatEach
|
||||
try {
|
||||
const ret = this.method.apply(this, this.args)
|
||||
if (ret && typeof ret.then === 'function') {
|
||||
ret.catch(e => debug('TASK ERROR ', this.name, e))
|
||||
}
|
||||
} catch (e) {
|
||||
debug('TASK ERROR ', this.name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage tasks:
|
||||
* - Send emails
|
||||
* - Send AP notifications
|
||||
* - Create recurrent events
|
||||
* - Sync AP federation profiles
|
||||
*/
|
||||
|
||||
class TaskManager {
|
||||
constructor () {
|
||||
this.interval = 60 * 100
|
||||
this.tasks = []
|
||||
}
|
||||
|
||||
start (interval = 60 * 100) {
|
||||
this.interval = interval
|
||||
this.timeout = setTimeout(this.tick.bind(this), interval)
|
||||
}
|
||||
|
||||
stop () {
|
||||
if (this.timeout) {
|
||||
debug('STOP')
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = false
|
||||
}
|
||||
}
|
||||
|
||||
add (task) {
|
||||
debug('ADD TASK ', task.name)
|
||||
this.tasks.push(task)
|
||||
}
|
||||
|
||||
process () {
|
||||
if (!this.tasks.length) {
|
||||
return
|
||||
}
|
||||
|
||||
this.tasks = this.tasks
|
||||
.filter(async task => {
|
||||
if (task.removable) {
|
||||
await task.process()
|
||||
} else {
|
||||
return task
|
||||
}
|
||||
})
|
||||
|
||||
return Promise.all(this.tasks.map(task => task.process()))
|
||||
}
|
||||
|
||||
async tick () {
|
||||
debug('TICK')
|
||||
await this.process()
|
||||
this.timeout = setTimeout(this.tick.bind(this), this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
const TS = new TaskManager()
|
||||
|
||||
// create and clean recurrent events
|
||||
TS.add(new Task({
|
||||
name: 'RECURRENT_EVENT',
|
||||
method: eventController._createRecurrent,
|
||||
repeatEach: 10
|
||||
}))
|
||||
|
||||
// daily morning notification
|
||||
// TS.add(new Task({
|
||||
// name: 'NOTIFICATION',
|
||||
// method: notifier._daily,
|
||||
// repeatEach: 1
|
||||
// }))
|
||||
|
||||
// AP users profile sync
|
||||
// TaskManager.add(new Task({
|
||||
// name: 'AP_PROFILE_SYNC',
|
||||
// method: federation._sync,
|
||||
// repeatEach: 60 * 24
|
||||
// }))
|
||||
|
||||
// Search for places position via nominatim
|
||||
// TaskManager.add(new Task({
|
||||
// name: 'NOMINATIM_QUERY',
|
||||
// method: places._nominatimQuery,
|
||||
// repeatEach: 60
|
||||
// }))
|
||||
|
||||
// TS.start()
|
||||
// TS.add(new Task({ name: 'removable #1', method: daje, args: ['removable #1'], removable: true }))
|
||||
// TS.add(new Task({ name: 'non removable #2', method: daje, args: ['non removable #2'] }))
|
||||
// TS.add(new Task({ name: 'non removable and repeat each #2', method: daje, args: ['nn rm and rpt #5'], repeatEach: 5 }))
|
||||
|
||||
module.exports = { Task, TaskManager: TS }
|
|
@ -44,7 +44,7 @@ export const getters = {
|
|||
if (!state.filters.show_past_events && e.past) { return false }
|
||||
|
||||
// filter recurrent events
|
||||
if (!state.filters.show_recurrent_events && e.recurrent) { return false }
|
||||
if (!state.filters.show_recurrent_events && e.parentId) { return false }
|
||||
|
||||
if (search_for_places) {
|
||||
if (search_place_ids.includes(e.place.id)) {
|
||||
|
@ -72,7 +72,7 @@ export const getters = {
|
|||
const match = false
|
||||
|
||||
// filter recurrent events
|
||||
if (!state.filters.show_recurrent_events && e.recurrent) { return false }
|
||||
if (!state.filters.show_recurrent_events && e.parentId) { return false }
|
||||
|
||||
if (!match && search_for_places) {
|
||||
if (find(state.filters.places, p => p.id === e.place.id)) { return true }
|
||||
|
|
Loading…
Reference in a new issue