[mega] settings, timezone

This commit is contained in:
les 2019-10-20 14:22:55 +02:00
parent 4a03d60667
commit 66aa6a8692
24 changed files with 214 additions and 85 deletions

View file

@ -1,14 +1,29 @@
@background: #222C32;
@home_background: #222C32;
@background: white;
@success: #c7ffbc;
// @info
#__nuxt, #__layout {
height: 100%;
}
#home {
background-color: @home_background;
min-height: 100%;
}
a, a:hover {
text-decoration: none;
}
html, body {
margin: 0px;
background-color: @background !important;
background-color: @background;
width: 100%;
height: 100%;
overflow-x: hidden;
box-sizing: border-box;
font-family: BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif !important;
font-size: 15px;
}
* {
@ -18,14 +33,11 @@ html, body {
.el-form-item {
margin-bottom: 5px;
}
// .el-divider__text {
// background-color: @background;
// color: white;
// border-radius: 5px;
// }
.el-main,
.el-card {
max-width: 660px;
max-width: 1000px;
border-radius: 0px;
margin: 30px auto;
}
@ -54,7 +66,7 @@ html, body {
.page-enter, .page-leave-active {
transition: opacity .3s, transform .2s;
opacity: 0;
// transform: translateY(30px);
transform: translateX(30px);
}
pre {
@ -69,12 +81,20 @@ pre {
}
@media only screen and (max-width: 768px) {
html {
font-size: 10px;
}
.el-card {
margin-top: 0px !important;
border-radius: 0px;
padding: 0px;
}
.el-main{
padding: 5px 0px;
}
.el-menu-item {
padding: 0px 17px;
}

View file

@ -4,6 +4,10 @@
"content": "Abbiamo ricevuto la richiesta di registrazione. La confermeremo quanto prima.\n Ciao"
},
"confirm": {
"subject": "Puoi iniziare a pubblicare eventi",
"content": "Ciao, il tuo account su <a href='{{config.baseurl}}'>{{config.title}}</a> è stato creato."
},
"user_confirm": {
"subject": "Puoi iniziare a pubblicare eventi",
"content": "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

@ -63,6 +63,7 @@
"less": "^3.10.3",
"lodash": "^4.17.14",
"mkdirp": "^0.5.1",
"moment-timezone": "^0.5.26",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"node-fetch": "^2.6.0",

View file

@ -1,5 +1,5 @@
<template lang="pug">
el-card
el-main
nuxt-link.float-right(to='/')
el-button(circle icon='el-icon-close' type='danger' size='small' plain)
@ -390,8 +390,8 @@ export default {
formData.append('place_address', this.event.place.address)
formData.append('description', this.event.description)
formData.append('multidate', this.event.type === 'multidate')
formData.append('start_datetime', start_datetime.utc(true).unix())
formData.append('end_datetime', end_datetime.utc(true).unix())
formData.append('start_datetime', start_datetime.unix())
formData.append('end_datetime', end_datetime.unix())
if (this.edit) {
formData.append('id', this.event.id)

View file

@ -31,7 +31,7 @@
el-table-column(:label='$t("common.name")' width='300')
template(slot-scope='data') {{data.row.title}}
el-table-column(:label='$t("common.where")' width='250')
template(slot-scope='data') {{dperPageata.row.place.name}}
template(slot-scope='data') {{data.row.place.name}}
el-table-column(:label='$t("common.confirm")' width='250')
template(slot-scope='data')
el-button(type='primary' @click='confirm(data.row.id)' size='mini') {{$t('common.confirm')}}

View file

@ -7,11 +7,18 @@
<script>
import Home from '~/components/Home.vue'
import Nav from '~/components/Nav.vue'
import moment from 'moment-timezone'
import { mapState } from 'vuex'
export default {
name: 'Index',
computed: mapState(['settings']),
mounted (ctx) {
moment.tz.setDefault(this.settings.instance_timezone)
},
async fetch ({ store, $axios }) {
try {
moment.tz.setDefault(store.state.settings.instance_timezone)
const now = new Date()
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
store.commit('setEvents', events)

View file

@ -1,5 +1,5 @@
<template lang="pug">
el-card
el-main
nuxt-link.float-right(to='/')
el-button(circle icon='el-icon-close' type='danger' size='small' plain)
h5 {{$t('common.settings')}}

View file

@ -3,27 +3,52 @@
nuxt-link.float-right(to='/')
el-button(circle icon='el-icon-close' type='danger' size='small' plain)
h5 <img src='/favicon.ico'/> {{$t('confirm.title')}}
p(v-if='valid' v-html='$t("confirm.valid")')
p(v-else) {{$t('confirm.not_valid')}}
h5 <img src='/favicon.ico'/> {{$t('common.set_password')}}
div(v-if='valid')
el-form
el-form-item {{$t('common.new_password')}}
el-input(type='password', v-model='new_password')
el-button(plain type="success" icon='el-icon-send'
:disabled='!new_password' @click='change_password') {{$t('common.send')}}
div(v-else) {{$t('recover.not_valid_code')}}
</template>
<script>
import { Message } from 'element-ui'
export default {
name: 'Confirm',
name: 'Recover',
data () {
return { valid: true }
return { new_password: '' }
},
async asyncData ({ params, $axios }) {
const recover_code = params.code
const code = params.code
try {
const valid = await $axios.$post('/user/check_recover_code', { recover_code })
return { valid }
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>
</script>

View file

@ -0,0 +1,53 @@
<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.set_password')}}
div(v-if='valid')
el-form
el-form-item {{$t('common.new_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

@ -1,7 +1,8 @@
import Vue from 'vue'
import { Button, Select, Tag, Option, Table, FormItem, Card, Row, Col, Upload, Checkbox, RadioButton, RadioGroup,
Form, Tabs, TabPane, Switch, Input, Loading, TimeSelect, Badge, ButtonGroup, Divider, Step, Steps, Radio,
TableColumn, ColorPicker, Pagination, Popover, Tooltip, Dialog, Image, Backtop, Collapse, CollapseItem,
Form, Tabs, TabPane, Switch, Input, Loading, TimeSelect, Badge, ButtonGroup, Divider, Step, Steps, Radio, Main,
TableColumn, ColorPicker, Pagination, Popover, Tooltip, Dialog, Image, Backtop, Collapse, CollapseItem, Link,
Dropdown, DropdownMenu, DropdownItem, Submenu, PageHeader, Header,
Container, Footer, Timeline, TimelineItem, Menu, MenuItem } from 'element-ui'
import locale from 'element-ui/lib/locale'
@ -14,6 +15,14 @@ const locales = {
export default ({ app, store }) => {
locale.use(locales[store.state.locale])
Vue.use(Button)
Vue.use(Dropdown)
Vue.use(Header)
Vue.use(PageHeader)
Vue.use(Submenu)
Vue.use(DropdownItem)
Vue.use(DropdownMenu)
Vue.use(Main)
Vue.use(Link)
Vue.use(RadioButton)
Vue.use(RadioGroup)
Vue.use(Radio)

View file

@ -1,32 +1,32 @@
import Vue from 'vue'
import moment from 'moment'
// import 'dayjs/locale/it'
// import 'dayjs/locale/es'
import moment from 'moment-timezone'
export default ({ app, store }) => {
// set timezone to instance_timezone!!
// to show local time relative to event's place
// not where in the worlds I'm looking at the page from
moment.tz.setDefault(store.state.settings.instance_timezone)
// 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">$2</a>'))
Vue.filter('url2host', url => url.match(/^https?:\/\/(.[^/:]+)/i)[1])
Vue.filter('datetime', value => moment(value).utc(false).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'))
Vue.filter('datetime', value => moment(value).locale(store.state.locale).format('ddd, D MMMM HH:mm'))
// shown in mobile homepage
Vue.filter('day', value => moment.unix(value).utc(false).locale(store.state.locale).format('dddd, D MMM'))
// Vue.filter('month', value => moment(value).locale(store.state.locale).format('MMM'))
Vue.filter('day', value => moment.unix(value).locale(store.state.locale).format('dddd, D MMM'))
Vue.filter('to', value => moment().to(value.start_datetime*1000))
// format event start/end datetime based on page
Vue.filter('when', (event, where) => {
moment.locale(store.state.locale)
// show local time relative to event's place
// (not where in the worlds I'm looking at the page from)
const start = moment.unix(event.start_datetime).utc(false)
const end = moment.unix(event.end_datetime).utc(false)
const start = moment.unix(event.start_datetime)
const end = moment.unix(event.end_datetime)
const normal = `${start.format('dddd, D MMMM (HH:mm-')}${end.format('HH:mm)')}`
const normal = `${start.format('dddd, D MMMM (HH:mm-')}${end.format('HH:mm) z Z ')}`
// recurrent event
if (event.recurrent && where !== 'home') {
const { frequency, days, type } = JSON.parse(event.recurrent)

View file

@ -31,7 +31,7 @@ import 'vue-awesome/icons/envelope'
import 'vue-awesome/icons/calendar-day'
import 'vue-awesome/icons/calendar-week'
import 'vue-awesome/icons/calendar-alt'
import 'vue-awesome/icons/circle-notch'
import 'vue-awesome/icons/network-wired'
import Icon from 'vue-awesome/components/Icon'

View file

@ -1,10 +1,11 @@
const crypto = require('crypto')
const moment = require('moment')
const moment = require('moment-timezone')
const { Op } = require('sequelize')
const lodash = require('lodash')
const { event: Event, comment: Comment, tag: Tag, place: Place,
user: User, notification: Notification, event_notification: EventNotification } = require('../models')
const Sequelize = require('sequelize')
const exportController = require('./export')
const debug = require('debug')('controller:event')
const eventController = {
@ -90,6 +91,7 @@ const eventController = {
// 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;
async get (req, res) {
const format = req.params.format || 'json'
const is_admin = req.user && req.user.is_admin
const id = req.params.event_id
const event = await Event.findByPk(id, {
@ -107,7 +109,11 @@ const eventController = {
})
if (event && (event.is_visible || is_admin)) {
res.json(event)
if (format === 'json') {
res.json(event)
} else if (format === 'ics') {
exportController.ics(req, res, [event])
}
} else {
res.sendStatus(404)
}
@ -190,13 +196,11 @@ const eventController = {
.month(req.params.month)
.startOf('month')
.startOf('week')
.utc(false)
let end = moment()
.year(req.params.year)
.month(req.params.month)
.endOf('month')
.utc(false)
const shownDays = end.diff(start, 'days')
if (shownDays <= 35) { end = end.add(1, 'week') }
@ -235,7 +239,7 @@ const eventController = {
if (!recurrent.frequency) { return false }
let cursor = moment(start).startOf('week')
const start_date = moment.unix(e.start_datetime).utc(false)
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
@ -280,7 +284,7 @@ const eventController = {
cursor.day(d - 1)
}
if (cursor.isAfter(dueTo) || cursor.isBefore(start)) { return }
e.start_datetime = cursor.utc(true).unix()
e.start_datetime = cursor.unix()
e.end_datetime = e.start_datetime + duration
events.push(Object.assign({}, e))
})

View file

@ -1,7 +1,6 @@
const { event: Event, place: Place, tag: Tag } = require('../models')
const { Op } = require('sequelize')
const moment = require('moment')
const config = require('config')
const moment = require('moment-timezone')
const ics = require('ics')
const exportController = {
@ -36,31 +35,38 @@ const exportController = {
switch (type) {
case 'rss':
case 'feed':
return exportController.feed(res, events.slice(0, 20))
return exportController.feed(req, res, events.slice(0, 20))
case 'ics':
return exportController.ics(res, events)
return exportController.ics(req, res, events)
case 'json':
return res.json(events)
}
},
feed (res, events) {
feed (req, res, events) {
res.type('application/rss+xml; charset=UTF-8')
res.render('feed/rss.pug', { events, config, moment })
res.render('feed/rss.pug', { events, settings: req.settings, moment })
},
ics (res, events) {
ics (req, res, events) {
const eventsMap = events.map(e => {
const tmpStart = moment.unix(e.start_datetime).utc(false)
const tmpEnd = moment.unix(e.end_datetime).utc(false)
const start = [tmpStart.year(), tmpStart.month() + 1, tmpStart.date(), tmpStart.hour(), tmpStart.minute()]
const end = [tmpEnd.year(), tmpEnd.month() + 1, tmpEnd.date(), tmpEnd.hour(), tmpEnd.minute()]
const tmpStart = moment.unix(e.start_datetime)
const tmpEnd = moment.unix(e.end_datetime)
const start = tmpStart.utc(true).format('YYYY-M-D-H-m').split('-')
const end = tmpEnd.utc(true).format('YYYY-M-D-H-m').split('-')
return {
start,
// startOutputType: 'utc',
end,
title: e.title,
// endOutputType: 'utc',
title: `[1${req.settings.title}] ${e.title}`,
description: e.description,
location: e.place.name + ' ' + e.place.address
location: `${e.place.name} - ${e.place.address}`,
url: `${req.settings.baseurl}/event/${e.id}`,
alarms: [{
action: 'display',
trigger: {hours: 1, before: true}
}]
}
})
res.type('text/calendar; charset=UTF-8')

View file

@ -249,8 +249,10 @@ const userController = {
}
req.body.recover_code = crypto.randomBytes(16).toString('hex')
debug('Register user ', req.body.email)
const user = await User.create(req.body)
try {
debug(`Sending registration email to ${user.email}`)
mail.send(user.email, 'register', { user, config })
mail.send(config.admin_email, 'admin_register', { user, config })
} catch (e) {

View file

@ -24,14 +24,6 @@ api.use(bodyParser.json())
const jwt = expressJwt({
secret: config.secret,
credentialsRequired: false,
// getToken: function fromHeaderOrQuerystring (req) {
// if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
// return req.headers.authorization.split(' ')[1]
// } else if (req.cookies && req.cookies['auth._token.local']) {
// const [ prefix, token ] = req.cookies['auth._token.local'].split(' ')
// if (prefix === 'Bearer') { return token }
// }
// }
})
// api.use(jwt)
@ -95,7 +87,7 @@ api.get('/event/confirm/:event_id', isAuth, isAdmin, eventController.confirm)
api.get('/event/unconfirm/:event_id', isAuth, isAdmin, eventController.unconfirm)
// get event
api.get('/event/:event_id', fillUser, eventController.get)
api.get('/event/:event_id.:format?', fillUser, eventController.get)
// export events (rss/ics)
api.get('/export/:type', exportController.export)

View file

@ -46,7 +46,7 @@ const mail = {
...locals,
locale: 'it', // TOFIX
config: { title: config.title, baseurl: config.baseurl, description: config.description },
datetime: datetime => moment.unix(datetime).utc(false).format('ddd, D MMMM HH:mm')
datetime: datetime => moment.unix(datetime).format('ddd, D MMMM HH:mm')
}
}
return email.send(msg)

View file

@ -37,11 +37,14 @@ module.exports = (sequelize, DataTypes) => {
}
event.prototype.toAP = function (username, follower = []) {
const tags = this.tags && this.tags.map(t => `<a href='/tags/${t.tag}' class='mention hashtag' rel='tag'>#${t.tag}</a>`).join(' ')
const tags = this.tags && this.tags.map(t => {
const tag = t.tag.replace(/[ #]/g, '_')
return `<a href='/tags/${t.tag}' class='mention hashtag status-link' rel='tag'><span>#${t.tag}</span></a>`
}).join(' ')
const content = `<a href='${config.baseurl}/event/${this.id}'>${this.title}</a><br/>
📍${this.place.name}<br/>
${moment.unix(this.start_datetime).utc(false).format('dddd, D MMMM (HH:mm)')}<br/><br/>
${moment.unix(this.start_datetime).format('dddd, D MMMM (HH:mm)')}<br/><br/>
${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description}<br/>
${tags} <br/>`

View file

@ -1,3 +1,3 @@
extends ../layout.pug
block content
p !{t('confirm.content', { config, user })}
p !{t('user_confirm.content', { config, user })}

View file

@ -1 +1 @@
| [#{config.title}] #{t('confirm.subject')}
| [#{config.title}] #{t('user_confirm.subject')}

View file

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

View file

@ -2,6 +2,7 @@ const settingsController = require('./api/controller/settings')
const acceptLanguage = require('accept-language')
const expressJwt = require('express-jwt')
const debug = require('debug')
const moment = require('moment-timezone')
const config = require('config')
const package = require('../package.json')
@ -39,6 +40,7 @@ module.exports = {
acceptLanguage.languages(supportedLanguages)
req.settings.locale = acceptLanguage.get(acceptedLanguages)
req.settings.user_locale = settingsController.user_locale[req.settings.locale]
moment.locale(req.settings.locale)
// auth
jwt(req, res, () => {

View file

@ -9,6 +9,7 @@ export const state = () => ({
tags: [],
places: [],
settings: {
instance_timezone: 'Europe/Rome',
allow_registration: true,
allow_anon_event: true,
allow_recurrent_event: true,

View file

@ -1,22 +1,22 @@
doctype xml
rss(version='2.0' xmlns:atom="http://www.w3.org/2005/Atom")
channel
atom:link(href="#{config.baseurl}/feed/rss" rel="self" type="application/rss+xml")
title #{config.title}
link #{config.baseurl}
description #{config.description}
atom:link(href="#{settings.baseurl}/feed/rss" rel="self" type="application/rss+xml")
title #{settings.title}
link #{settings.baseurl}
description #{settings.description}
each event in events
item
title [#{moment.unix(event.start_datetime).utc(false).format("YY-MM-DD")}] #{event.title} @#{event.place.name}
link #{config.baseurl}/event/#{event.id}
title [#{moment.unix(event.start_datetime).format("YY-MM-DD")}] #{event.title} @#{event.place.name}
link #{settings.baseurl}/event/#{event.id}
description
| <![CDATA[
| <h4>#{event.title}</h4>
| <strong>#{event.place.name} - #{event.place.address}</strong>
| <small>(#{moment.unix(event.start_datetime).utc(false).format("dddd, D MMMM HH:mm")})</small><br/>
| <small>(#{moment.unix(event.start_datetime).format("dddd, D MMMM HH:mm")})</small><br/>
if (event.image_path)
| <img src="#{config.apiurl}/media/#{event.image_path}"/>
| <img src="#{settings.baseurl}/media/#{event.image_path}"/>
| <pre>!{event.description}</pre>
| ]]>
pubDate= new Date(event.createdAt).toUTCString()
guid(isPermaLink='false') #{config.baseurl}/event/#{event.id}
guid(isPermaLink='false') #{settings.baseurl}/event/#{event.id}