diff --git a/components/Event.vue b/components/Event.vue
index 85a3677c..daa890bb 100644
--- a/components/Event.vue
+++ b/components/Event.vue
@@ -1,5 +1,5 @@
- 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
img(v-if='showImage && event.image_path' :src='`/media/thumb/${event.image_path}`')
@@ -11,7 +11,7 @@
//- date / place
.date
- div {{event|event_when}}
+ div {{event|when('home')}}
div {{event.place.name}}
//- p(v-if='showDescription') {{event.description}}
diff --git a/components/List.vue b/components/List.vue
index 29b77d81..958701b5 100644
--- a/components/List.vue
+++ b/components/List.vue
@@ -5,14 +5,14 @@ div#list
el-timeline-item(
v-for='event in events'
:key='event.id'
- :timestamp='event|event_when'
+ :timestamp='event|when'
placement='top' icon='el-icon-arrow-down' size='large'
)
div.float-right
small @{{event.place.name}}
- a(:href='"/event/" + event.id' target='_blank') {{event.title}}
+ a(:href='"/event/" + link(event)' target='_blank') {{event.title}}
hr
\ No newline at end of file
+
diff --git a/pages/admin.vue b/pages/admin.vue
index 8ce03d69..055a811e 100644
--- a/pages/admin.vue
+++ b/pages/admin.vue
@@ -11,18 +11,21 @@
template(slot='label')
v-icon(name='users')
span.ml-1 {{$t('common.users')}}
+
+ //- ADD NEW USER
el-collapse
el-collapse-item
template(slot='title')
- p {{$t('common.new_user')}}
+ h4 {{$t('common.new_user')}}
el-form(inline)
el-form-item(:label="$t('common.email')")
el-input(v-model='new_user.email')
- el-form-item(:label="$t('common.password')")
- el-input(v-model='new_user.password' type='password')
+ //- el-form-item(:label="$t('common.password')")
+ //- el-input(v-model='new_user.password' type='password')
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-table(:data='paginatedUsers' small)
el-table-column(label='Email')
template(slot-scope='data')
@@ -141,7 +144,6 @@ export default {
loading: false,
new_user: {
email: '',
- password: '',
admin: false,
},
tab: "0",
@@ -266,7 +268,7 @@ export default {
try {
this.loading = true
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({
showClose: true,
type: 'success',
diff --git a/pages/event/_id.vue b/pages/event/_id.vue
index 7a356e29..06a00193 100644
--- a/pages/event/_id.vue
+++ b/pages/event/_id.vue
@@ -8,21 +8,21 @@
h5 {{$t('event.not_found')}}
div(v-else)
- //- title, where, when
+ //- title
h5.text-center {{event.title}}
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')
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')
v-icon(name='chevron-right')
-
+
//- image
img.main(:src='imgPath' v-if='event.image_path')
.info
- div {{event|event_when}}
+ div {{event|when}}
div {{event.place.name}} - {{event.place.address}}
//- description and tags
@@ -61,7 +61,8 @@ import { MessageBox } from 'element-ui'
export default {
name: 'Event',
// 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'],
// Key for (transitions)
// key: to => to.fullPath,
@@ -77,10 +78,12 @@ export default {
title: this.event.title,
meta: [
// 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: 'og-description', name: 'og:description', 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}` },
+ { hid: 'description', name: 'description',
+ content: this.event.description.slice(0, 1000) },
+ { hid: 'og-description', name: 'og:description',
+ 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:image', content: this.imgPath }
]
@@ -97,8 +100,10 @@ export default {
},
async asyncData ( { $axios, params, error }) {
try {
- const event = await $axios.$get(`/event/${params.id}`)
- return { event, id: params.id }
+ const [ id, start_datetime ] = params.id.split('_')
+ const event = await $axios.$get(`/event/${id}`)
+ event.start_datetime = start_datetime*1000
+ return { event, id }
} catch(e) {
error({ statusCode: 404, message: 'Event not found'})
}
@@ -108,18 +113,27 @@ export default {
...mapState(['settings']),
next () {
let found = false
- return this.filteredEvents.find(e => {
+ const event = this.filteredEvents.find(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 () {
- let prev = false
+ let event = false
this.filteredEvents.find(e => {
- if (e.id === this.event.id) return true
- prev = e
+ if (e.start_datetime === this.event.start_datetime && e.id === this.event.id) return true
+ event = e
})
- return prev
+ if (!event) return false
+ if (event.recurrent) {
+ return `${event.id}_${event.start_datetime/1000}`
+ }
+ return event.id
},
imgPath () {
return this.event.image_path && '/media/' + this.event.image_path
diff --git a/pages/user_confirm/_code.vue b/pages/user_confirm/_code.vue
new file mode 100644
index 00000000..c618afdd
--- /dev/null
+++ b/pages/user_confirm/_code.vue
@@ -0,0 +1,57 @@
+
+ el-card
+ nuxt-link.float-right(to='/')
+ el-button(circle icon='el-icon-close' type='danger' size='small' plain)
+
+ h5
{{$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')}}
+
+
+
+
+
+
diff --git a/plugins/filters.js b/plugins/filters.js
index bf607d0b..3440bfbd 100644
--- a/plugins/filters.js
+++ b/plugins/filters.js
@@ -3,22 +3,52 @@ import moment from 'dayjs'
import 'dayjs/locale/it'
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, '$1'))
- 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('hour', value => moment(value).format('HH:mm'))
- Vue.filter('day', value => moment(value).format('dddd, D MMMM'))
- Vue.filter('month', value => moment(value).format('MMM'))
- Vue.filter('event_when', event => {
-
+
+ // Vue.filter('datetime', value => moment(value).locale(store.state.locale).format('ddd, D MMMM HH:mm'))
+ // Vue.filter('short_datetime', value => moment(value).locale(store.state.locale).format('D/MM HH:mm'))
+ // Vue.filter('hour', value => moment(value).locale(store.state.locale).format('HH:mm'))
+
+ // 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 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) {
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
- return start.format('dddd, D MMMM (HH:mm)')
+ }
+
+ // normal event
+ 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)')
})
}
diff --git a/plugins/i18n.js b/plugins/i18n.js
index 9deaa908..00c35d39 100644
--- a/plugins/i18n.js
+++ b/plugins/i18n.js
@@ -1,7 +1,7 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
-import it from '@/locales/it.js'
-import en from '@/locales/en.js'
+import it from '../locales/it.js'
+import en from '../locales/en.js'
Vue.use(VueI18n)
diff --git a/server/api/controller/event.js b/server/api/controller/event.js
index a4bd63d8..419e7f2f 100644
--- a/server/api/controller/event.js
+++ b/server/api/controller/event.js
@@ -89,7 +89,13 @@ const eventController = {
const id = req.params.event_id
let event = await Event.findByPk(id, {
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: [
{ model: Tag, attributes: ['tag', 'weigth'], through: { attributes: [] } },
{ model: Place, attributes: ['name', 'address'] },
@@ -106,7 +112,6 @@ const eventController = {
},
async confirm(req, res) {
- console.error('confirm event')
const id = Number(req.params.event_id)
const event = await Event.findByPk(id)
if (!event) return res.sendStatus(404)
@@ -181,7 +186,8 @@ const eventController = {
.year(req.params.year)
.month(req.params.month)
.startOf('month')
- .startOf('isoWeek')
+ .startOf('week')
+ console.error('start ', start)
let end = moment()
.year(req.params.year)
@@ -190,7 +196,7 @@ const eventController = {
const shownDays = end.diff(start, 'days')
if (shownDays <= 35) end = end.add(1, 'week')
- end = end.endOf('isoWeek')
+ end = end.endOf('week')
let events = await Event.findAll({
where: {
@@ -225,7 +231,7 @@ const eventController = {
const recurrent = JSON.parse(e.recurrent)
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 duration = moment(e.end_datetime).diff(start_date, 's')
const frequency = recurrent.frequency
@@ -234,7 +240,8 @@ const eventController = {
// default frequency is '1d' => each 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)
if (frequency === '1w' || frequency === '2w') {
cursor.add(days[0]-1, 'day')
@@ -244,23 +251,31 @@ const eventController = {
}
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
- // if (type) {
-
- // }
- // }
+ 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) {
let first_event_of_week = cursor.clone()
days.forEach(d => {
- cursor.day(d-1)
- if (cursor.isAfter(dueTo)) return
+ if (type === 'ordinal') {
+ cursor.date(d)
+ } else {
+ cursor.day(d-1)
+ }
+ if (cursor.isAfter(dueTo) || cursor.isBefore(start)) return
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
events.push( Object.assign({}, e) )
diff --git a/server/api/controller/user.js b/server/api/controller/user.js
index 15f1445d..b7d3da21 100644
--- a/server/api/controller/user.js
+++ b/server/api/controller/user.js
@@ -33,7 +33,7 @@ const userController = {
},
config.secret
)
-
+ res.cookie('auth._token.local', 'Bearer ' + accessToken)
res.json({ token: accessToken })
}
}
@@ -259,7 +259,9 @@ const userController = {
async create(req, res) {
try {
req.body.is_active = true
+ req.body.recover_code = crypto.randomBytes(16).toString('hex')
const user = await User.create(req.body)
+ mail.send(user.email, 'user_confirm', { user, config })
res.json(user)
} catch (e) {
res.status(404).json(e)
diff --git a/server/api/index.js b/server/api/index.js
index 49171d89..c32701f8 100644
--- a/server/api/index.js
+++ b/server/api/index.js
@@ -12,13 +12,12 @@ const userController = require('./controller/user')
const settingsController = require('./controller/settings')
const storage = require('./storage')
-
const upload = multer({ storage })
+
const api = express.Router()
api.use(cookieParser())
api.use(bodyParser.urlencoded({ extended: false }))
api.use(bodyParser.json())
-// api.use(settingsController.init)
const jwt = expressJwt({
secret: config.secret,
diff --git a/server/emails/user_confirm/html.pug b/server/emails/user_confirm/html.pug
new file mode 100644
index 00000000..4228d6d8
--- /dev/null
+++ b/server/emails/user_confirm/html.pug
@@ -0,0 +1 @@
+p !{t('email.user_confirm', { config, user })}
diff --git a/server/emails/user_confirm/subject.pug b/server/emails/user_confirm/subject.pug
new file mode 100644
index 00000000..6d0067a6
--- /dev/null
+++ b/server/emails/user_confirm/subject.pug
@@ -0,0 +1 @@
+= `[Gancio] Richiesta password recovery`
diff --git a/store/index.js b/store/index.js
index 1394569c..7700851b 100644
--- a/store/index.js
+++ b/store/index.js
@@ -1,7 +1,5 @@
import moment from 'dayjs'
import intersection from 'lodash/intersection'
-import map from 'lodash/map'
-import filter from 'lodash/filter'
import find from 'lodash/find'
export const state = () => ({
@@ -147,10 +145,8 @@ export const actions = {
commit('setSettings', 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) {
const events = await this.$axios.$get(`/event/${page.month - 1}/${page.year}`)