major on recurrent events

This commit is contained in:
lesion 2019-07-23 01:31:43 +02:00
parent beab52d349
commit f9e0883eaf
19 changed files with 210 additions and 76 deletions

View file

@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
nuxt-link.event(:to='`event/${event.id}`' :class='{ withImg: event.image_path }') nuxt-link.event(:to='`event/${event.id}${event.recurrent && "_" + event.start_datetime/1000}`' :class='{ withImg: event.image_path }')
//- image //- image
img(v-if='showImage && event.image_path' :src='`/media/thumb/${event.image_path}`') img(v-if='showImage && event.image_path' :src='`/media/thumb/${event.image_path}`')
@ -11,7 +11,7 @@
//- date / place //- date / place
.date .date
div <v-icon name='clock'/> {{event|event_when}} div <v-icon name='clock'/> {{event|when('home')}}
div <v-icon name='map-marker-alt' /> {{event.place.name}} div <v-icon name='map-marker-alt' /> {{event.place.name}}
//- p(v-if='showDescription') {{event.description}} //- p(v-if='showDescription') {{event.description}}

View file

@ -5,14 +5,14 @@ div#list
el-timeline-item( el-timeline-item(
v-for='event in events' v-for='event in events'
:key='event.id' :key='event.id'
:timestamp='event|event_when' :timestamp='event|when'
placement='top' icon='el-icon-arrow-down' size='large' placement='top' icon='el-icon-arrow-down' size='large'
) )
div.float-right div.float-right
small @{{event.place.name}} small @{{event.place.name}}
a(:href='"/event/" + event.id' target='_blank') {{event.title}} a(:href='"/event/" + link(event)' target='_blank') {{event.title}}
hr hr
</template> </template>
<script> <script>
@ -23,6 +23,14 @@ export default {
data () { data () {
return { } return { }
}, },
methods: {
link (event) {
if (event.recurrent) {
return `${event.id}_${event.start_datetime/1000}`
}
return event.id
}
},
props: { props: {
title: { title: {
type: String, type: String,

View file

@ -7,14 +7,14 @@
//- v-model='onlyMine' //- v-model='onlyMine'
//- ) //- )
el-switch.mt-1.mb-1.ml-2.d-block( el-switch.mt-1.mb-1.ml-2.d-block(
v-if='pastFilter' v-if='recurrentFilter && settings.allow_recurrent_event'
inactive-text='' inactive-text=''
active-text='anche appuntamenti fissi' active-text='anche appuntamenti fissi'
inactive-color='lightgreen' inactive-color='lightgreen'
v-model='showRecurrent' v-model='showRecurrent'
) )
el-switch.mt-1.mb-1.ml-2.d-block( el-switch.mt-1.mb-1.ml-2.d-block(
v-if='recurrentFilter' v-if='pastFilter'
inactive-text='solo futuri' inactive-text='solo futuri'
active-text='anche passati' active-text='anche passati'
inactive-color='lightgreen' inactive-color='lightgreen'
@ -45,7 +45,7 @@ export default {
}, },
methods: mapActions(['setSearchPlaces', 'setSearchTags', 'showPastEvents', 'showRecurrentEvents']), methods: mapActions(['setSearchPlaces', 'setSearchTags', 'showPastEvents', 'showRecurrentEvents']),
computed: { computed: {
...mapState(['tags', 'places', 'filters']), ...mapState(['tags', 'places', 'filters', 'settings']),
// TOFIX: optimize // TOFIX: optimize
keywords () { keywords () {
const tags = this.tags.map( t => ({ value: 't' + t.tag, label: t.tag, weigth: t.weigth })) const tags = this.tags.map( t => ({ value: 't' + t.tag, label: t.tag, weigth: t.weigth }))

View file

@ -4,5 +4,6 @@
"email.confirm": "Il tuo account su gancio è stato attivato e quindi puoi cominciare a pubblicare eventi", "email.confirm": "Il tuo account su gancio è stato attivato e quindi puoi cominciare a pubblicare eventi",
"email.recover": "Ciao, hai richiesto un recupero della password su gancio.", "email.recover": "Ciao, hai richiesto un recupero della password su gancio.",
"email.press_here": "Premi qui", "email.press_here": "Premi qui",
"email.confirm.subject": "Registrazione confermata" "email.confirm.subject": "Registrazione confermata",
"email.user_confirm": "Ciao, il tuo account su <a href='{{config.baseurl}}'>{{config.title}}<a/> è stato creato. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Confermalo</a>."
} }

View file

@ -127,7 +127,7 @@ const it = {
recurrent_1m_days: '|Il giorno {days} di ogni mese|I giorni {days} di ogni mese', recurrent_1m_days: '|Il giorno {days} di ogni mese|I giorni {days} di ogni mese',
recurrent_2m_days: '|Il giorno {days} ogni due mesi|I giorni {days} ogni due mesi', recurrent_2m_days: '|Il giorno {days} ogni due mesi|I giorni {days} ogni due mesi',
recurrent_1m_ordinal: 'Il {n} {days} di ogni mese', recurrent_1m_ordinal: 'Il {n} {days} di ogni mese',
recurrent_2m_ordinal: 'Il {n} {days} un mese sì e uno no', recurrent_2m_ordinal: '|Il {n} {days} un mese sì e uno no|Il {n} {days} un mese sì e uno no',
due: 'alle', due: 'alle',
from: 'Dalle', from: 'Dalle',
image_too_big: 'Immagine troppo grande! Massimo 4M' image_too_big: 'Immagine troppo grande! Massimo 4M'

View file

@ -32,6 +32,7 @@ module.exports = {
'element-ui/lib/theme-chalk/index.css' 'element-ui/lib/theme-chalk/index.css'
], ],
/* /*
** Plugins to load before mounting the App ** Plugins to load before mounting the App
*/ */
@ -59,6 +60,9 @@ module.exports = {
axios: { axios: {
prefix: '/api' prefix: '/api'
}, },
router: {
middleware: 'i18n'
},
auth: { auth: {
strategies: { strategies: {
local: { local: {
@ -66,7 +70,9 @@ module.exports = {
login: { url: '/auth/login', method: 'post', propertyName: 'token' }, login: { url: '/auth/login', method: 'post', propertyName: 'token' },
logout: false, logout: false,
user: { url: '/auth/user', method: 'get', propertyName: false } user: { url: '/auth/user', method: 'get', propertyName: false }
} },
tokenRequired: false,
tokenType: 'Bearer'
} }
} }
}, },

View file

@ -61,7 +61,6 @@
"morgan": "^1.9.1", "morgan": "^1.9.1",
"multer": "^1.4.2", "multer": "^1.4.2",
"nuxt": "^2.8.1", "nuxt": "^2.8.1",
"nuxt-i18n": "^5.12.4",
"pg": "^7.11.0", "pg": "^7.11.0",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"sequelize": "^5.10.1", "sequelize": "^5.10.1",
@ -71,6 +70,7 @@
"v-calendar": "^1.0.0-beta.14", "v-calendar": "^1.0.0-beta.14",
"vue-awesome": "^3.5.3", "vue-awesome": "^3.5.3",
"vue-clipboard2": "^0.3.0", "vue-clipboard2": "^0.3.0",
"vue-i18n": "^8.12.0",
"yargs": "^13.3.0" "yargs": "^13.3.0"
}, },
"devDependencies": { "devDependencies": {

View file

@ -60,11 +60,11 @@
br br
span {{$t(`event.${event.type}_description`)}} span {{$t(`event.${event.type}_description`)}}
el-select.ml-2(v-if='event.type==="recurrent"' v-model='event.recurrent.frequency' placeholder='Frequenza') el-select.ml-2(v-if='event.type==="recurrent"' v-model='event.recurrent.frequency' placeholder='Frequenza')
el-option(label='Tutti i giorni' value='1d' key='1d') //- el-option(label='Tutti i giorni' value='1d' key='1d')
el-option(label='Ogni settimana' value='1w' key='1w') el-option(label='Ogni settimana' value='1w' key='1w')
el-option(label='Ogni due settimane' value='2w' key='2w') el-option(label='Ogni due settimane' value='2w' key='2w')
el-option(label='Ogni mese' value='1m' key='1m') el-option(label='Ogni mese' value='1m' key='1m')
el-option(label='Ogni due mesi' value='2m' key='2m') //- el-option(label='Ogni due mesi' value='2m' key='2m')
v-date-picker.mb-2.mt-3( v-date-picker.mb-2.mt-3(
:mode='event.type === "multidate" ? "range" : event.type === "recurrent" ? "multiple" : "single"' :mode='event.type === "multidate" ? "range" : event.type === "recurrent" ? "multiple" : "single"'
@ -118,6 +118,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'
import uniq from 'lodash/uniq' import uniq from 'lodash/uniq'
import map from 'lodash/map' import map from 'lodash/map'
import moment from 'dayjs' import moment from 'dayjs'
import List from '@/components/List' import List from '@/components/List'
import { Message } from 'element-ui' import { Message } from 'element-ui'
@ -170,8 +171,9 @@ export default {
} catch(e) { } catch(e) {
console.error('Error ', e) console.error('Error ', e)
} }
moment.locale(store.state.locale)
}, },
async asyncData ( { params, $axios, error }) { async asyncData ( { params, $axios, error, store }) {
if (params.edit) { if (params.edit) {
const data = { time: {}, event: { place: {} }} const data = { time: {}, event: { place: {} }}
data.id = params.edit data.id = params.edit
@ -439,4 +441,4 @@ export default {
} }
} }
} }
</script> </script>

View file

@ -11,18 +11,21 @@
template(slot='label') template(slot='label')
v-icon(name='users') v-icon(name='users')
span.ml-1 {{$t('common.users')}} span.ml-1 {{$t('common.users')}}
//- ADD NEW USER
el-collapse el-collapse
el-collapse-item el-collapse-item
template(slot='title') template(slot='title')
p {{$t('common.new_user')}} h4 <v-icon name='plus'/> {{$t('common.new_user')}}
el-form(inline) el-form(inline)
el-form-item(:label="$t('common.email')") el-form-item(:label="$t('common.email')")
el-input(v-model='new_user.email') el-input(v-model='new_user.email')
el-form-item(:label="$t('common.password')") //- el-form-item(:label="$t('common.password')")
el-input(v-model='new_user.password' type='password') //- el-input(v-model='new_user.password' type='password')
el-form-item(:label="$t('common.admin')") el-form-item(:label="$t('common.admin')")
el-switch(v-model='new_user.admin') el-switch(v-model='new_user.is_admin')
el-button.float-right(@click='create_user' type='success' plain) {{$t('common.send')}} el-button.float-right(@click='create_user' type='success' plain) {{$t('common.send')}}
el-table(:data='paginatedUsers' small) el-table(:data='paginatedUsers' small)
el-table-column(label='Email') el-table-column(label='Email')
template(slot-scope='data') template(slot-scope='data')
@ -141,7 +144,6 @@ export default {
loading: false, loading: false,
new_user: { new_user: {
email: '', email: '',
password: '',
admin: false, admin: false,
}, },
tab: "0", tab: "0",
@ -266,7 +268,7 @@ export default {
try { try {
this.loading = true this.loading = true
const user = await this.$axios.$post('/user', this.new_user) const user = await this.$axios.$post('/user', this.new_user)
this.new_user = { email: '', password: '', is_admin: false } this.new_user = { email: '', is_admin: false }
Message({ Message({
showClose: true, showClose: true,
type: 'success', type: 'success',

View file

@ -8,21 +8,21 @@
h5 {{$t('event.not_found')}} h5 {{$t('event.not_found')}}
div(v-else) div(v-else)
//- title, where, when //- title
h5.text-center {{event.title}} h5.text-center {{event.title}}
div.nextprev div.nextprev
nuxt-link(v-if='prev' :to='`/event/${prev.id}`') nuxt-link(v-if='prev' :to='`/event/${prev}`')
el-button( type='success' size='mini') el-button( type='success' size='mini')
v-icon(name='chevron-left') v-icon(name='chevron-left')
nuxt-link.float-right(v-if='next' :to='`/event/${next.id}`') nuxt-link.float-right(v-if='next' :to='`/event/${next}`')
el-button(type='success' size='mini') el-button(type='success' size='mini')
v-icon(name='chevron-right') v-icon(name='chevron-right')
//- image //- image
img.main(:src='imgPath' v-if='event.image_path') img.main(:src='imgPath' v-if='event.image_path')
.info .info
div {{event|event_when}} div {{event|when}}
div {{event.place.name}} - {{event.place.address}} div {{event.place.name}} - {{event.place.address}}
//- description and tags //- description and tags
@ -61,7 +61,8 @@ import { MessageBox } from 'element-ui'
export default { export default {
name: 'Event', name: 'Event',
// transition: null, // transition: null,
// Watch for $route.query.page to call Component methods (asyncData, fetch, validate, layout, etc.) // Watch for $route.query.page to call
// Component methods (asyncData, fetch, validate, layout, etc.)
// watchQuery: ['id'], // watchQuery: ['id'],
// Key for <NuxtChild> (transitions) // Key for <NuxtChild> (transitions)
// key: to => to.fullPath, // key: to => to.fullPath,
@ -77,10 +78,12 @@ export default {
title: this.event.title, title: this.event.title,
meta: [ meta: [
// hid is used as unique identifier. Do not use `vmid` for it as it will not work // hid is used as unique identifier. Do not use `vmid` for it as it will not work
{ hid: 'description', name: 'description', content: this.event.description.slice(0, 1000) }, { hid: 'description', name: 'description',
{ hid: 'og-description', name: 'og:description', content: this.event.description.slice(0, 100) }, content: this.event.description.slice(0, 1000) },
{ hid: 'og-title', property: 'og:title', content: this.event.title }, { hid: 'og-description', name: 'og:description',
{ hid: 'og-url', property: 'og:url', content: `event/${this.event.id}` }, content: this.event.description.slice(0, 100) },
{ hid: 'og-title', property: 'og:title', content: this.event.title },
{ hid: 'og-url', property: 'og:url', content: `event/${this.event.id}` },
{ property: 'og:type', content: 'event'}, { property: 'og:type', content: 'event'},
{ property: 'og:image', content: this.imgPath } { property: 'og:image', content: this.imgPath }
] ]
@ -97,8 +100,10 @@ export default {
}, },
async asyncData ( { $axios, params, error }) { async asyncData ( { $axios, params, error }) {
try { try {
const event = await $axios.$get(`/event/${params.id}`) const [ id, start_datetime ] = params.id.split('_')
return { event, id: params.id } const event = await $axios.$get(`/event/${id}`)
event.start_datetime = start_datetime*1000
return { event, id }
} catch(e) { } catch(e) {
error({ statusCode: 404, message: 'Event not found'}) error({ statusCode: 404, message: 'Event not found'})
} }
@ -108,18 +113,27 @@ export default {
...mapState(['settings']), ...mapState(['settings']),
next () { next () {
let found = false let found = false
return this.filteredEvents.find(e => { const event = this.filteredEvents.find(e => {
if (found) return e if (found) return e
if (e.id === this.event.id) found = true if (e.start_datetime === this.event.start_datetime && e.id === this.event.id) found = true
}) })
if (!event) return false
if (event.recurrent) {
return `${event.id}_${event.start_datetime/1000}`
}
return event.id
}, },
prev () { prev () {
let prev = false let event = false
this.filteredEvents.find(e => { this.filteredEvents.find(e => {
if (e.id === this.event.id) return true if (e.start_datetime === this.event.start_datetime && e.id === this.event.id) return true
prev = e event = e
}) })
return prev if (!event) return false
if (event.recurrent) {
return `${event.id}_${event.start_datetime/1000}`
}
return event.id
}, },
imgPath () { imgPath () {
return this.event.image_path && '/media/' + this.event.image_path return this.event.image_path && '/media/' + this.event.image_path

View file

@ -0,0 +1,57 @@
<template lang="pug">
el-card
nuxt-link.float-right(to='/')
el-button(circle icon='el-icon-close' type='danger' size='small' plain)
h5 <img src='/favicon.ico'/> {{$t('common.activate_user')}}
div(v-if='valid')
el-form
el-form-item {{$t('common.password')}}
el-input(type='password', v-model='new_password')
el-button(plain type="success" icon='el-icon-send', @click='change_password') {{$t('common.send')}}
div(v-else) {{$t('recover.not_valid_code')}}
</template>
<script>
import { Message } from 'element-ui'
export default {
name: 'Recover',
data () {
return { new_password: '' }
},
async asyncData({ params, $axios }) {
const code = params.code
try {
const valid = await $axios.$post('/user/check_recover_code', { recover_code: code })
return { valid, code }
}
catch (e) {
return { valid: false }
}
},
methods: {
async change_password () {
try {
const res = await this.$axios.$post('/user/recover_password', { recover_code: this.code, password: this.new_password })
Message({
showClose: true,
type: 'success',
message: this.$t('common.password_updated')
})
this.$router.replace('/login')
} catch(e) {
Message({
showClose: true,
type: 'warning',
message: e
})
}
}
}
}
</script>

View file

@ -3,22 +3,52 @@ import moment from 'dayjs'
import 'dayjs/locale/it' import 'dayjs/locale/it'
export default ({ app, store }) => { export default ({ app, store }) => {
moment.locale(store.state.locale)
// replace links with anchors
// TODO: remove fb tracking id
Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>')) Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>'))
Vue.filter('datetime', value => moment(value).format('ddd, D MMMM HH:mm'))
Vue.filter('short_datetime', value => moment(value).format('D/MM HH:mm')) // Vue.filter('datetime', value => moment(value).locale(store.state.locale).format('ddd, D MMMM HH:mm'))
Vue.filter('hour', value => moment(value).format('HH:mm')) // Vue.filter('short_datetime', value => moment(value).locale(store.state.locale).format('D/MM HH:mm'))
Vue.filter('day', value => moment(value).format('dddd, D MMMM')) // Vue.filter('hour', value => moment(value).locale(store.state.locale).format('HH:mm'))
Vue.filter('month', value => moment(value).format('MMM'))
Vue.filter('event_when', event => { // shown in mobile homepage
Vue.filter('day', value => moment(value).locale(store.state.locale).format('dddd, D MMMM'))
// Vue.filter('month', value => moment(value).locale(store.state.locale).format('MMM'))
// format event start/end datetime based on page
Vue.filter('when', (event, where) => {
moment.locale(store.state.locale)
//{start,end}_datetime are unix timestamp
const start = moment(event.start_datetime) const start = moment(event.start_datetime)
const end = moment(event.end_datetime) const end = moment(event.end_datetime)
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)
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}`
} else if (frequency === '1m' || frequency === '2m') {
const d = type === 'ordinal' ? days : days.map(d => moment().day(d-1).format('dddd'))
const recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, days.length, {days: d})
return `${normal} - ${recurrent}`
}
return 'recurrent '
}
// multidate
if (event.multidate) { if (event.multidate) {
return `${start.format('ddd, D MMMM (HH:mm)')} - ${end.format('ddd, D MMMM')}` return `${start.format('ddd, D MMMM (HH:mm)')} - ${end.format('ddd, D MMMM')}`
} else if (event.end_datetime && event.end_datetime !== event.start_datetime) }
return `${start.format('ddd, D MMMM (HH:mm-')}${end.format('HH:mm)')}`
else // normal event
return start.format('dddd, D MMMM (HH:mm)') if (event.end_datetime && event.end_datetime !== event.start_datetime) {
return `${start.format('ddd, D MMMM (HH:mm-')}${end.format('HH:mm)')}`
}
return start.format('dddd, D MMMM (HH:mm)')
}) })
} }

View file

@ -1,7 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import VueI18n from 'vue-i18n' import VueI18n from 'vue-i18n'
import it from '@/locales/it.js' import it from '../locales/it.js'
import en from '@/locales/en.js' import en from '../locales/en.js'
Vue.use(VueI18n) Vue.use(VueI18n)

View file

@ -89,7 +89,13 @@ const eventController = {
const id = req.params.event_id const id = req.params.event_id
let event = await Event.findByPk(id, { let event = await Event.findByPk(id, {
plain: true, plain: true,
attributes: { exclude: ['createdAt', 'updatedAt'] }, attributes: {
exclude: ['createdAt', 'updatedAt', 'start_datetime', 'end_datetime'],
include: [
[Sequelize.literal('start_datetime*1000'), 'start_datetime'],
[Sequelize.literal('end_datetime*1000'), 'end_datetime']
]
},
include: [ include: [
{ model: Tag, attributes: ['tag', 'weigth'], through: { attributes: [] } }, { model: Tag, attributes: ['tag', 'weigth'], through: { attributes: [] } },
{ model: Place, attributes: ['name', 'address'] }, { model: Place, attributes: ['name', 'address'] },
@ -106,7 +112,6 @@ const eventController = {
}, },
async confirm(req, res) { async confirm(req, res) {
console.error('confirm event')
const id = Number(req.params.event_id) const id = Number(req.params.event_id)
const event = await Event.findByPk(id) const event = await Event.findByPk(id)
if (!event) return res.sendStatus(404) if (!event) return res.sendStatus(404)
@ -181,7 +186,8 @@ const eventController = {
.year(req.params.year) .year(req.params.year)
.month(req.params.month) .month(req.params.month)
.startOf('month') .startOf('month')
.startOf('isoWeek') .startOf('week')
console.error('start ', start)
let end = moment() let end = moment()
.year(req.params.year) .year(req.params.year)
@ -190,7 +196,7 @@ const eventController = {
const shownDays = end.diff(start, 'days') const shownDays = end.diff(start, 'days')
if (shownDays <= 35) end = end.add(1, 'week') if (shownDays <= 35) end = end.add(1, 'week')
end = end.endOf('isoWeek') end = end.endOf('week')
let events = await Event.findAll({ let events = await Event.findAll({
where: { where: {
@ -225,7 +231,7 @@ const eventController = {
const recurrent = JSON.parse(e.recurrent) const recurrent = JSON.parse(e.recurrent)
if (!recurrent.frequency) return false if (!recurrent.frequency) return false
let cursor = moment(start).startOf('isoWeek') let cursor = moment(start).startOf('week')
const start_date = moment(e.start_datetime) const start_date = moment(e.start_datetime)
const duration = moment(e.end_datetime).diff(start_date, 's') const duration = moment(e.end_datetime).diff(start_date, 's')
const frequency = recurrent.frequency const frequency = recurrent.frequency
@ -234,7 +240,8 @@ const eventController = {
// default frequency is '1d' => each day // default frequency is '1d' => each day
const toAdd = { n: 1, unit: 'day'} const toAdd = { n: 1, unit: 'day'}
cursor.set('hour', start_date.hour()).set('minute', start_date.minutes())
// each week or 2 (search for the first specified day) // each week or 2 (search for the first specified day)
if (frequency === '1w' || frequency === '2w') { if (frequency === '1w' || frequency === '2w') {
cursor.add(days[0]-1, 'day') cursor.add(days[0]-1, 'day')
@ -244,23 +251,31 @@ const eventController = {
} }
toAdd.n = Number(frequency[0]) toAdd.n = Number(frequency[0])
toAdd.unit = 'week'; 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 // each month or 2
// if (frequency === '1m' || frequency === '2m') { if (frequency === '1m' || frequency === '2m') {
// // find first match // find first match
// if (type) { toAdd.n = 1
toAdd.unit = 'month'
// } if (type === 'weekday') {
// }
} else if (type === 'ordinal') {
}
}
// add event at specified frequency // add event at specified frequency
while (true) { while (true) {
let first_event_of_week = cursor.clone() let first_event_of_week = cursor.clone()
days.forEach(d => { days.forEach(d => {
cursor.day(d-1) if (type === 'ordinal') {
if (cursor.isAfter(dueTo)) return cursor.date(d)
} else {
cursor.day(d-1)
}
if (cursor.isAfter(dueTo) || cursor.isBefore(start)) return
e.start_datetime = cursor.unix()*1000 e.start_datetime = cursor.unix()*1000
e.end_datetime = e.start_datetime+(duration*1000)// cursor.clone().hour(end_datetime.hour()).minute(end_datetime.minute()).unix()*1000 e.end_datetime = e.start_datetime+(duration*1000)// cursor.clone().hour(end_datetime.hour()).minute(end_datetime.minute()).unix()*1000
events.push( Object.assign({}, e) ) events.push( Object.assign({}, e) )

View file

@ -33,7 +33,7 @@ const userController = {
}, },
config.secret config.secret
) )
res.cookie('auth._token.local', 'Bearer ' + accessToken)
res.json({ token: accessToken }) res.json({ token: accessToken })
} }
} }
@ -259,7 +259,9 @@ const userController = {
async create(req, res) { async create(req, res) {
try { try {
req.body.is_active = true req.body.is_active = true
req.body.recover_code = crypto.randomBytes(16).toString('hex')
const user = await User.create(req.body) const user = await User.create(req.body)
mail.send(user.email, 'user_confirm', { user, config })
res.json(user) res.json(user)
} catch (e) { } catch (e) {
res.status(404).json(e) res.status(404).json(e)

View file

@ -12,13 +12,12 @@ const userController = require('./controller/user')
const settingsController = require('./controller/settings') const settingsController = require('./controller/settings')
const storage = require('./storage') const storage = require('./storage')
const upload = multer({ storage }) const upload = multer({ storage })
const api = express.Router() const api = express.Router()
api.use(cookieParser()) api.use(cookieParser())
api.use(bodyParser.urlencoded({ extended: false })) api.use(bodyParser.urlencoded({ extended: false }))
api.use(bodyParser.json()) api.use(bodyParser.json())
// api.use(settingsController.init)
const jwt = expressJwt({ const jwt = expressJwt({
secret: config.secret, secret: config.secret,

View file

@ -0,0 +1 @@
p !{t('email.user_confirm', { config, user })}

View file

@ -0,0 +1 @@
= `[Gancio] Richiesta password recovery`

View file

@ -1,7 +1,5 @@
import moment from 'dayjs' import moment from 'dayjs'
import intersection from 'lodash/intersection' import intersection from 'lodash/intersection'
import map from 'lodash/map'
import filter from 'lodash/filter'
import find from 'lodash/find' import find from 'lodash/find'
export const state = () => ({ export const state = () => ({
@ -147,10 +145,8 @@ export const actions = {
commit('setSettings', settings) commit('setSettings', settings)
// apply settings // apply settings
commit('showRecurrentEvents', settings.recurrent_event_visible) commit('showRecurrentEvents', settings.allow_recurrent_event && settings.recurrent_event_visible)
const lang = req.acceptsLanguages('en', 'it')
commit('setLocale', lang || 'it')
}, },
async updateEvents({ commit }, page) { async updateEvents({ commit }, page) {
const events = await this.$axios.$get(`/event/${page.month - 1}/${page.year}`) const events = await this.$axios.$get(`/event/${page.month - 1}/${page.year}`)