diff --git a/assets/style.less b/assets/style.less
index 7199a8df..00bd4bef 100644
--- a/assets/style.less
+++ b/assets/style.less
@@ -25,7 +25,7 @@ html, body {
// }
.el-card {
- max-width: 630px;
+ max-width: 660px;
margin: 30px auto;
}
diff --git a/components/Calendar.vue b/components/Calendar.vue
index 9b46f014..e67533ba 100644
--- a/components/Calendar.vue
+++ b/components/Calendar.vue
@@ -19,10 +19,10 @@ import { intersection, sample, take, get } from 'lodash'
export default {
name: 'Calendar',
data () {
- const month = moment().month()+1
+ const month = moment().month() + 1
const year = moment().year()
return {
- page: { month, year},
+ page: { month, year }
}
},
watch: {
@@ -35,8 +35,8 @@ export default {
...mapActions(['updateEvents']),
click (day) {
const element = document.getElementById(day.day)
- if (element) element.scrollIntoView(); //Even IE6 supports this
- },
+ if (element) { element.scrollIntoView() } // Even IE6 supports this
+ }
},
computed: {
...mapGetters(['filteredEventsWithPast']),
@@ -45,17 +45,17 @@ export default {
// TODO: should be better
attributes () {
const colors = ['green', 'orange', 'yellow', 'teal', 'indigo', 'blue', 'red', 'purple', 'pink', 'grey']
- const tags = take(this.tags, 10).map(t=>t.tag)
+ const tags = take(this.tags, 10).map(t => t.tag)
let attributes = []
- attributes.push ({ key: 'today', dates: new Date(), highlight: { color: 'green' }})
+ attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green' } })
const that = this
- function getColor(event) {
+ function getColor (event) {
const color = { class: event.past && !that.filters.show_past_events ? 'past-event vc-rounded-full' : 'vc-rounded-full', color: 'blue' }
const tag = get(event, 'tags[0]')
- if (!tag) return color
+ if (!tag) { return color }
const idx = tags.indexOf(tag)
- if (idx<0) return color
+ if (idx < 0) { return color }
color.color = colors[idx]
return color
}
@@ -65,16 +65,19 @@ export default {
.map(e => {
const color = getColor(e)
return {
- key: e.id,
+ key: e.id,
dot: color,
- dates: new Date(e.start_datetime*1000)
- }}))
+ dates: new Date(e.start_datetime * 1000)
+ }
+ }))
attributes = attributes.concat(this.filteredEventsWithPast
.filter(e => e.multidate)
- .map( e => ({ key: e.id, highlight: getColor(e), dates: {
- start: new Date(e.start_datetime*1000), end: new Date(e.end_datetime*1000) }})))
-
+ .map(e => ({ key: e.id,
+ highlight: getColor(e),
+ dates: {
+ start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) } })))
+
return attributes
}
}
diff --git a/components/Event.vue b/components/Event.vue
index 68f42907..655b89c7 100644
--- a/components/Event.vue
+++ b/components/Event.vue
@@ -11,7 +11,7 @@
h2 {{event.title}}
//- date / place
- .date
+ .date
div {{event|when('home')}}
div {{event.place.name}}
@@ -32,10 +32,10 @@ export default {
showImage: {
type: Boolean,
default: true
- },
+ }
},
computed: {
- date () {
+ date () {
return new Date(this.event.start_datetime).getDate()
},
link () {
@@ -104,7 +104,7 @@ export default {
font-weight: 400;
font-size: 1rem;
color: white;
- }
+ }
}
.tags {
diff --git a/components/Home.vue b/components/Home.vue
index 511ccc39..32f853c4 100644
--- a/components/Home.vue
+++ b/components/Home.vue
@@ -36,16 +36,16 @@ export default {
// hid is used as unique identifier. Do not use `vmid` for it as it will not work
{ hid: 'description', name: 'description', content: this.settings.description },
{ hid: 'og-description', name: 'og:description', content: this.settings.description },
- { hid: 'og-title', property: 'og:title', content: this.settings.title },
- { hid: 'og-url', property: 'og:url', content: this.settings.baseurl },
+ { hid: 'og-title', property: 'og:title', content: this.settings.title },
+ { hid: 'og-url', property: 'og:url', content: this.settings.baseurl },
{ property: 'og:image', content: this.settings.baseurl + '/favicon.ico' }
]
}
},
+ components: { Calendar, Event },
data () {
return { }
},
- components: { Calendar, Event },
computed: {
...mapGetters(['filteredEvents']),
...mapState(['events', 'settings'])
diff --git a/components/List.vue b/components/List.vue
index 76c00033..32821651 100644
--- a/components/List.vue
+++ b/components/List.vue
@@ -20,17 +20,6 @@ import { mapGetters } from 'vuex'
export default {
name: 'List',
- data () {
- return { }
- },
- methods: {
- link (event) {
- if (event.recurrent) {
- return `${event.id}_${event.start_datetime}`
- }
- return event.id
- }
- },
props: {
title: {
type: String,
@@ -52,17 +41,28 @@ export default {
},
showTags: {
type: Boolean,
- default: true,
+ default: true
},
showImage: {
type: Boolean,
- default: true,
+ default: true
},
showDescription: {
type: Boolean,
default: true
}
},
+ data () {
+ return { }
+ },
+ methods: {
+ link (event) {
+ if (event.recurrent) {
+ return `${event.id}_${event.start_datetime}`
+ }
+ return event.id
+ }
+ }
}
-
diff --git a/pages/export.vue b/pages/export.vue
index d620b346..a9ab2bff 100644
--- a/pages/export.vue
+++ b/pages/export.vue
@@ -15,14 +15,14 @@
//- el-form(@submit.native.prevent)
//- //- el-switch(v-model='notification.notify_on_add' :active-text="$t('notify_on_insert')")
//- //- br
- //- //- el-switch.mt-2(v-model='notification.send_notification' :active-text="$t('send_notification')")
+ //- //- el-switch.mt-2(v-model='notification.send_notification' :active-text="$t('send_notification')")
//- el-input.mt-2(v-model='notification.email' :placeholder="$t('export.insert_your_address')" ref='email')
//- el-button.mt-2.float-right(native-type= 'submit' type='success' @click='add_notification') {{$t('common.send')}}
el-tab-pane.pt-1(label='feed rss' name='feed')
span(v-html='$t(`export.feed_description`)')
el-input(v-model='link')
- el-button(slot='append' plain
+ el-button(slot='append' plain
v-clipboard:copy='link'
type="primary" icon='el-icon-document' ) {{$t("common.copy")}}
@@ -45,7 +45,6 @@
el-input.mb-1(type='textarea' v-model='listScript' readonly )
el-button.float-right(plain v-clipboard:copy='listScript' type='primary' icon='el-icon-document') {{$t('common.copy')}}
-
//- TOFIX
//- el-tab-pane.pt-1(label='calendar' name='calendar')
//- p(v-html='$t(`export.calendar_description`)')
@@ -61,7 +60,7 @@ import Calendar from '@/components/Calendar'
import List from '@/components/List'
import Search from '@/components/Search'
-import {intersection} from 'lodash'
+import { intersection } from 'lodash'
import { Message } from 'element-ui'
export default {
@@ -76,25 +75,25 @@ export default {
return {
type: 'feed',
notification: { email: '' },
- list: { title: 'Gancio' },
+ list: { title: 'Gancio' }
}
},
methods: {
copy (msg) {
- this.$copyText(msg).then(e => console.error('ok ', e)).catch(e => console.error('err ',e))
+ this.$copyText(msg).then(e => console.error('ok ', e)).catch(e => console.error('err ', e))
},
async add_notification () {
- if (!this.notification.email){
- Message({message:'Inserisci una mail', showClose: true, type: 'error'})
+ if (!this.notification.email) {
+ Message({ message: 'Inserisci una mail', showClose: true, type: 'error' })
// return this.$refs.email.focus()
}
// await api.addNotification({ ...this.notification, filters: this.filters})
// this.$refs.modal.hide()
- Message({message: this.$t('email_notification_activated'), showClose: true, type: 'success'})
+ Message({ message: this.$t('email_notification_activated'), showClose: true, type: 'success' })
},
imgPath (event) {
return event.image_path && event.image_path
- },
+ }
},
computed: {
...mapState(['filters', 'events', 'settings']),
@@ -119,9 +118,9 @@ export default {
const tags = this.filters.tags.join(',')
const places = this.filters.places.join(',')
let query = ''
- if (tags || places) {
+ if (tags || places) {
query = '?'
- if (tags) {
+ if (tags) {
query += 'tags=' + tags
if (places) { query += '&places=' + places }
} else {
@@ -132,8 +131,8 @@ export default {
return `${this.settings.baseurl}/api/export/${this.type}${query}`
},
showLink () {
- return (['feed', 'ics'].indexOf(this.type)>-1)
- },
+ return (['feed', 'ics'].includes(this.type))
+ }
}
}
@@ -143,5 +142,3 @@ export default {
overflow-y: auto;
}
-
-
diff --git a/pages/index.vue b/pages/index.vue
index a74231c1..01f10dca 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -18,12 +18,11 @@ export default {
store.commit('setEvents', events)
const { tags, places } = await $axios.$get('/event/meta')
store.commit('update', { tags, places })
- } catch(e) {
+ } catch (e) {
console.error(e)
}
},
computed: mapState(['events']),
- components: { Nav, Home },
+ components: { Nav, Home }
}
-
diff --git a/pages/login.vue b/pages/login.vue
index 81c43d1c..f4efbdfe 100644
--- a/pages/login.vue
+++ b/pages/login.vue
@@ -41,7 +41,7 @@ export default {
computed: {
...mapState(['settings']),
disabled () {
- if (process.server) return false
+ if (process.server) { return false }
return !this.email || !this.password
}
},
@@ -49,7 +49,7 @@ export default {
...mapActions(['login']),
async forgot () {
if (!this.email) {
- Message({ message: this.$t('login.insert_email'), showClose:true, type: 'error' })
+ Message({ message: this.$t('login.insert_email'), showClose: true, type: 'error' })
this.$refs.email.focus()
return
}
diff --git a/pages/recover/_code.vue b/pages/recover/_code.vue
index 8bb61df0..0d3e89f0 100644
--- a/pages/recover/_code.vue
+++ b/pages/recover/_code.vue
@@ -11,8 +11,7 @@
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/pages/register.vue b/pages/register.vue
index a4f8f67f..a8b3c094 100644
--- a/pages/register.vue
+++ b/pages/register.vue
@@ -44,13 +44,13 @@ export default {
title: this.settings.title + ' - ' + this.$t('common.register')
}
},
- validate ({store}) {
+ validate ({ store }) {
return store.state.settings.allow_registration
},
computed: {
...mapState(['settings']),
disabled () {
- if (process.server) return false
+ if (process.server) { return false }
return !this.user.password || !this.user.email || !this.user.description
}
},
@@ -65,7 +65,7 @@ export default {
message: this.$t(`register.${user.is_admin ? 'admin_' : ''}complete`),
type: 'success'
})
- this.$router.replace("/")
+ this.$router.replace('/')
} catch (e) {
const error = get(e, 'response.data.errors[0].message', String(e))
Message({
diff --git a/pages/settings.vue b/pages/settings.vue
index 81380793..8826745d 100644
--- a/pages/settings.vue
+++ b/pages/settings.vue
@@ -1,14 +1,32 @@
el-card
nuxt-link.float-right(to='/')
- v-icon(name='times' color='red')
+ el-button(circle icon='el-icon-close' type='danger' size='small' plain)
h5 {{$t('common.settings')}}
+ hr
- el-form(action='/api/user' method='PUT' @submit.native.prevent='change_password')
- el-form-item {{$t('settings.change_password')}}
+ el-form(action='/api/user' method='PUT' @submit.native.prevent='update_settings' inline label-width='200px')
+
+ el-form-item(:label="$t('settings.change_password')")
el-input(v-model='password' type='password')
- el-button(type='success' native-type='submit') {{$t('common.send')}}
-
+
+ //- allow federation
+ div(v-if='settings.enable_federation')
+ el-form-item(:label="$t('admin.enable_federation')")
+ el-switch(v-model='user.settings.enable_federation')
+
+ div(v-if='user.settings.enable_federation')
+ el-form-item(:label="$t('common.username')")
+ el-input(v-if='user.username.length==0' type='text' name='username' v-model='user.username')
+ template(slot='suffix') @{{baseurl}}
+ span(v-else) {{user.username}}@{{baseurl}}
+ //- el-button(slot='append') {{$t('common.save')}}
+
+ el-form-item(:label="$t('common.displayname')")
+ el-input(type='text' v-model='user.display_name')
+
+ el-button(type='success' native-type='submit') {{$t('common.save')}}
+
el-divider {{$t('settings.danger_section')}}
p {{$t('settings.remove_account')}}
el-button(type='danger' @click='remove_account') {{$t('common.remove')}}
@@ -16,15 +34,16 @@
-
diff --git a/pages/user_confirm/_code.vue b/pages/user_confirm/_code.vue
index c53de7c9..bc62a80a 100644
--- a/pages/user_confirm/_code.vue
+++ b/pages/user_confirm/_code.vue
@@ -6,7 +6,7 @@
h5
{{$t('confirm.title')}}
p(v-if='valid' v-html='$t("confirm.valid")')
p(v-else) {{$t('confirm.not_valid')}}
-
+
-
-
diff --git a/plugins/axios.js b/plugins/axios.js
index 78a17eda..dba773fc 100644
--- a/plugins/axios.js
+++ b/plugins/axios.js
@@ -1,5 +1,5 @@
-export default function({ $axios, store }) {
+export default function ({ $axios, store }) {
if (process.client) {
$axios.defaults.baseURL = window.location.origin + '/api'
}
-}
\ No newline at end of file
+}
diff --git a/plugins/filters.js b/plugins/filters.js
index 9c9187cf..f48c9162 100644
--- a/plugins/filters.js
+++ b/plugins/filters.js
@@ -4,7 +4,6 @@ import 'dayjs/locale/it'
import 'dayjs/locale/es'
export default ({ app, store }) => {
-
// replace links with anchors
// TODO: remove fb tracking id
Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '$1'))
@@ -14,14 +13,14 @@ export default ({ app, store }) => {
// Vue.filter('hour', value => moment(value).locale(store.state.locale).format('HH:mm'))
// shown in mobile homepage
- Vue.filter('day', value => moment.unix(value).locale(store.state.locale).format('dddd, D MMMM'))
+ Vue.filter('day', value => moment.unix(value).locale(store.state.locale).format('dddd, D MMM'))
// 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
+ // {start,end}_datetime are unix timestamp
const start = moment.unix(event.start_datetime)
const end = moment.unix(event.end_datetime)
@@ -30,12 +29,12 @@ export default ({ app, store }) => {
// 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'))})
+ 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})
+ 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 '
@@ -44,7 +43,7 @@ export default ({ app, store }) => {
// multidate
if (event.multidate) {
return `${start.format('ddd, D MMMM (HH:mm)')} - ${end.format('ddd, D MMMM')}`
- }
+ }
// normal event
if (event.end_datetime && event.end_datetime !== event.start_datetime) {
diff --git a/plugins/i18n.js b/plugins/i18n.js
index 4f5b69a6..340d6f96 100644
--- a/plugins/i18n.js
+++ b/plugins/i18n.js
@@ -10,8 +10,8 @@ export default async ({ app, store }) => {
// This way we can use it in middleware and pages asyncData/fetch
const user_locale = await app.$axios.$get('/settings/user_locale')
- for(let lang in user_locale) {
- if (locales[lang]) merge(locales[lang], user_locale[lang])
+ for (const lang in user_locale) {
+ if (locales[lang]) { merge(locales[lang], user_locale[lang]) }
}
app.i18n = new VueI18n({
diff --git a/server/api/auth.js b/server/api/auth.js
index c88c9333..10a669b8 100644
--- a/server/api/auth.js
+++ b/server/api/auth.js
@@ -2,8 +2,8 @@ const { Op } = require('sequelize')
const { user: User } = require('./models')
const Auth = {
- async fillUser(req, res, next) {
- if (!req.user) return next()
+ async fillUser (req, res, next) {
+ if (!req.user) { return next() }
req.user = await User.findOne({
where: { id: { [Op.eq]: req.user.id }, is_active: true }
}).catch(e => {
@@ -12,7 +12,7 @@ const Auth = {
})
next()
},
- async isAuth(req, res, next) {
+ async isAuth (req, res, next) {
if (!req.user) {
return res
.status(403)
@@ -29,15 +29,15 @@ const Auth = {
}
next()
},
- isAdmin(req, res, next) {
+ isAdmin (req, res, next) {
if (!req.user) {
return res
.status(403)
.send({ message: 'Failed to authenticate token ' })
}
- if (req.user.is_admin && req.user.is_active) return next()
+ if (req.user.is_admin && req.user.is_active) { return next() }
return res.status(403).send({ message: 'Admin needed' })
- },
+ }
}
diff --git a/server/api/controller/event.js b/server/api/controller/event.js
index 44c47cdf..db9cb6b7 100644
--- a/server/api/controller/event.js
+++ b/server/api/controller/event.js
@@ -9,7 +9,7 @@ const federation = require('../../federation/helpers')
const eventController = {
- async addComment(req, res) {
+ async addComment (req, res) {
// comment could be added to an event or to another comment
let event = await Event.findOne({ where: { activitypub_id: { [Op.eq]: req.body.id } } })
if (!event) {
@@ -21,11 +21,11 @@ const eventController = {
res.json(comment)
},
- async getMeta(req, res) {
+ async getMeta (req, res) {
const places = await Place.findAll({
order: [[Sequelize.literal('weigth'), 'DESC']],
attributes: {
- include: [[Sequelize.fn('count', Sequelize.col('events.placeId')) , 'weigth']],
+ include: [[Sequelize.fn('count', Sequelize.col('events.placeId')), 'weigth']],
exclude: ['weigth', 'createdAt', 'updatedAt']
},
include: [{ model: Event, attributes: [] }],
@@ -36,25 +36,25 @@ const eventController = {
order: [['weigth', 'DESC']],
attributes: {
exclude: ['createdAt', 'updatedAt']
- },
+ }
})
res.json({ tags, places })
},
- async getNotifications(event) {
- function match(event, filters) {
+ async getNotifications (event) {
+ function match (event, filters) {
// matches if no filter specified
- if (!filters) return true
+ if (!filters) { return true }
// check for visibility
- if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) return false
+ if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false }
- if (!filters.tags && !filters.places) return true
- if (!filters.tags.length && !filters.places.length) return true
+ if (!filters.tags && !filters.places) { return true }
+ if (!filters.tags.length && !filters.places.length) { return true }
if (filters.tags.length) {
const m = lodash.intersection(event.tags.map(t => t.tag), filters.tags)
- if (m.length > 0) return true
+ if (m.length > 0) { return true }
}
if (filters.places.length) {
if (filters.places.find(p => p === event.place.name)) {
@@ -68,7 +68,7 @@ const eventController = {
return notifications.filter(notification => match(event, notification.filters))
},
- async updateTag(req, res) {
+ async updateTag (req, res) {
const tag = await Tag.findByPk(req.body.tag)
if (tag) {
res.json(await tag.update(req.body))
@@ -77,7 +77,7 @@ const eventController = {
}
},
- async updatePlace(req, res) {
+ async updatePlace (req, res) {
const place = await Place.findByPk(req.body.id)
await place.update(req.body)
res.json(place)
@@ -85,12 +85,12 @@ 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) {
+ async get (req, res) {
const is_admin = req.user && req.user.is_admin
const id = req.params.event_id
- let event = await Event.findByPk(id, {
+ const event = await Event.findByPk(id, {
plain: true,
- attributes: {
+ attributes: {
exclude: ['createdAt', 'updatedAt']
},
include: [
@@ -109,29 +109,29 @@ const eventController = {
}
},
- async confirm(req, res) {
+ async confirm (req, res) {
const id = Number(req.params.event_id)
const event = await Event.findByPk(id)
- if (!event) return res.sendStatus(404)
+ if (!event) { return res.sendStatus(404) }
try {
event.is_visible = true
await event.save()
res.sendStatus(200)
-
+
// send notification
- //notifier.notifyEvent(event.id)
- //federation.sendEvent(event, req.user)
+ // notifier.notifyEvent(event.id)
+ // federation.sendEvent(event, req.user)
} catch (e) {
res.sendStatus(404)
}
},
- async unconfirm(req, res) {
+ async unconfirm (req, res) {
const id = Number(req.params.event_id)
const event = await Event.findByPk(id)
- if (!event) return sendStatus(404)
+ if (!event) { return sendStatus(404) }
try {
event.is_visible = false
@@ -142,7 +142,7 @@ const eventController = {
}
},
- async getUnconfirmed(req, res) {
+ async getUnconfirmed (req, res) {
const events = await Event.findAll({
where: {
is_visible: false
@@ -153,7 +153,7 @@ const eventController = {
res.json(events)
},
- async addNotification(req, res) {
+ async addNotification (req, res) {
try {
const notification = {
filters: { is_visible: true },
@@ -168,7 +168,7 @@ const eventController = {
}
},
- async delNotification(req, res) {
+ async delNotification (req, res) {
const remove_code = req.params.code
try {
const notification = await Notification.findOne({ where: { remove_code: { [Op.eq]: remove_code } } })
@@ -179,7 +179,7 @@ const eventController = {
res.sendStatus(200)
},
- async getAll(req, res) {
+ async getAll (req, res) {
// this is due how v-calendar shows dates
const start = moment()
.year(req.params.year)
@@ -193,7 +193,7 @@ const eventController = {
.endOf('month')
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('week')
let events = await Event.findAll({
@@ -202,10 +202,10 @@ const eventController = {
is_visible: true,
[Op.or]: [
// return all recurrent events
- {recurrent: { [Op.ne]: null }},
+ { recurrent: { [Op.ne]: null } },
// and events in specified range
- { start_datetime: { [Op.between]: [start.unix(), end.unix()] }}
+ { start_datetime: { [Op.between]: [start.unix(), end.unix()] } }
]
},
attributes: { exclude: ['createdAt', 'updatedAt', 'placeId' ] },
@@ -223,10 +223,10 @@ const eventController = {
})
// build singular events from a recurrent pattern
- function createEventsFromRecurrent(e, dueTo=null) {
+ function createEventsFromRecurrent (e, dueTo = null) {
const events = []
const recurrent = JSON.parse(e.recurrent)
- if (!recurrent.frequency) return false
+ if (!recurrent.frequency) { return false }
let cursor = moment(start).startOf('week')
const start_date = moment.unix(e.start_datetime)
@@ -236,18 +236,18 @@ const eventController = {
const type = recurrent.type
// 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)
if (frequency === '1w' || frequency === '2w') {
- cursor.add(days[0]-1, 'day')
+ cursor.add(days[0] - 1, 'day')
if (frequency === '2w') {
- const nWeeks = cursor.diff(e.start_datetime, 'w')%2
- if (!nWeeks) cursor.add(1, 'week')
+ const nWeeks = cursor.diff(e.start_datetime, 'w') % 2
+ if (!nWeeks) { cursor.add(1, 'week') }
}
toAdd.n = Number(frequency[0])
- toAdd.unit = 'week';
+ toAdd.unit = 'week'
// cursor.set('hour', start_date.hour()).set('minute', start_date.minutes())
}
@@ -263,37 +263,35 @@ const eventController = {
}
}
- // add event at specified frequency
+ // add event at specified frequency
while (true) {
- let first_event_of_week = cursor.clone()
+ const first_event_of_week = cursor.clone()
days.forEach(d => {
if (type === 'ordinal') {
cursor.date(d)
} else {
- cursor.day(d-1)
+ cursor.day(d - 1)
}
- if (cursor.isAfter(dueTo) || cursor.isBefore(start)) return
+ 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
+ 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)
}
-
return events
}
let allEvents = events.filter(e => !e.recurrent)
events.filter(e => e.recurrent).forEach(e => {
const events = createEventsFromRecurrent(e, end)
- if (events)
- allEvents = allEvents.concat(events)
+ if (events) { allEvents = allEvents.concat(events) }
})
// allEvents.sort((a,b) => a.start_datetime-b.start_datetime)
- res.json(allEvents.sort((a,b) => a.start_datetime-b.start_datetime))
+ res.json(allEvents.sort((a, b) => a.start_datetime - b.start_datetime))
}
}
diff --git a/server/api/controller/export.js b/server/api/controller/export.js
index f664899e..eef46214 100644
--- a/server/api/controller/export.js
+++ b/server/api/controller/export.js
@@ -5,7 +5,7 @@ const ics = require('ics')
const exportController = {
- async export(req, res) {
+ async export (req, res) {
const type = req.params.type
const tags = req.query.tags
const places = req.query.places
@@ -40,12 +40,12 @@ const exportController = {
}
},
- feed(res, events) {
+ feed (res, events) {
res.type('application/rss+xml; charset=UTF-8')
res.render('feed/rss.pug', { events, config: process.env, moment })
},
- ics(res, events) {
+ ics (res, events) {
const eventsMap = events.map(e => {
const tmpStart = moment.unix(e.start_datetime)
const tmpEnd = moment.unix(e.end_datetime)
diff --git a/server/api/controller/user.js b/server/api/controller/user.js
index 72a875db..0402b04f 100644
--- a/server/api/controller/user.js
+++ b/server/api/controller/user.js
@@ -11,9 +11,14 @@ const settingsController = require('./settings')
const federation = require('../../federation/helpers')
const userController = {
- async login(req, res) {
+ async login (req, res) {
// find the user
- const user = await User.findOne({ where: { email: { [Op.eq]: req.body && req.body.email } } })
+ const user = await User.findOne({ where: {
+ [Op.or]: [
+ { email: req.body.email },
+ { username: req.body.email }
+ ]
+ } })
if (!user) {
res.status(403).json({ success: false, message: 'auth.fail' })
} else if (user) {
@@ -39,13 +44,7 @@ const userController = {
}
},
- async setToken(req, res) {
- req.user.mastodon_auth = req.body
- await req.user.save()
- res.json(req.user)
- },
-
- async delEvent(req, res) {
+ async delEvent (req, res) {
const event = await Event.findByPk(req.params.id)
// check if event is mine (or user is admin)
if (event && (req.user.is_admin || req.user.id === event.userId)) {
@@ -69,7 +68,7 @@ const userController = {
},
// ADD EVENT
- async addEvent(req, res) {
+ async addEvent (req, res) {
const body = req.body
const eventDetails = {
@@ -89,9 +88,9 @@ const userController = {
eventDetails.image_path = req.file.filename
}
- let event = await Event.create(eventDetails)
+ const event = await Event.create(eventDetails)
- // create place if needs to
+ // create place if needed
let place
try {
place = await Place.findOrCreate({ where: { name: body.place_name },
@@ -106,7 +105,7 @@ const userController = {
if (body.tags) {
await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true })
const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } })
- await Promise.all(tags.map(t => t.update({weigth: Number(t.weigth)+1})))
+ await Promise.all(tags.map(t => t.update({ weigth: Number(t.weigth) + 1 })))
await event.addTags(tags)
event.tags = tags
}
@@ -119,17 +118,15 @@ const userController = {
// send response to client
res.json(event)
- if (req.user)
- federation.sendEvent(event, req.user)
+ if (req.user) { federation.sendEvent(event, req.user) }
// res.sendStatus(200)
// send notification (mastodon/email/confirmation)
// notifier.notifyEvent(event.id)
-
},
- async updateEvent(req, res) {
+ async updateEvent (req, res) {
const body = req.body
const event = await Event.findByPk(body.id)
if (!req.user.is_admin && event.userId !== req.user.id) {
@@ -169,10 +166,10 @@ const userController = {
res.json(newEvent)
},
- async forgotPassword(req, res) {
+ async forgotPassword (req, res) {
const email = req.body.email
const user = await User.findOne({ where: { email: { [Op.eq]: email } } })
- if (!user) return res.sendStatus(200)
+ if (!user) { return res.sendStatus(200) }
user.recover_code = crypto.randomBytes(16).toString('hex')
mail.send(user.email, 'recover', { user, config })
@@ -181,25 +178,25 @@ const userController = {
res.sendStatus(200)
},
- async checkRecoverCode(req, res) {
+ async checkRecoverCode (req, res) {
const recover_code = req.body.recover_code
- if (!recover_code) return res.sendStatus(400)
+ if (!recover_code) { return res.sendStatus(400) }
const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } })
- if (!user) return res.sendStatus(400)
+ if (!user) { return res.sendStatus(400) }
try {
- await user.update({ recover_code: ''})
+ await user.update({ recover_code: '' })
res.sendStatus(200)
} catch (e) {
res.sendStatus(400)
}
},
- async updatePasswordWithRecoverCode(req, res) {
+ async updatePasswordWithRecoverCode (req, res) {
const recover_code = req.body.recover_code
const password = req.body.password
- if (!recover_code || !password) return res.sendStatus(400)
+ if (!recover_code || !password) { return res.sendStatus(400) }
const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } })
- if (!user) return res.sendStatus(400)
+ if (!user) { return res.sendStatus(400) }
user.recover_code = ''
user.password = password
try {
@@ -210,33 +207,42 @@ const userController = {
}
},
- current(req, res) {
+ current (req, res) {
if (req.user) { res.json(req.user) } else { res.sendStatus(404) }
},
- async getAll(req, res) {
+ async getAll (req, res) {
const users = await User.findAll({
order: [['createdAt', 'DESC']]
})
res.json(users)
},
- async update(req, res) {
- const user = await User.findByPk(req.body.id)
- if (user) {
- if (!user.is_active && req.body.is_active && user.recover_code) {
- mail.send(user.email, 'confirm', { user, config })
- }
- await user.update(req.body)
- res.json(user)
- } else {
- res.sendStatus(400)
+ async update (req, res) {
+ // user to modify
+ user = await User.findByPk(req.body.id)
+
+ if (!user) { return res.status(404).json({ success: false, message: 'User not found!' }) }
+
+ if (req.body.id !== req.user.id && !req.user.is_admin) {
+ return res.status(400).json({ succes: false, message: 'Not allowed' })
}
+
+ // ensure username to not change if not empty
+ req.body.username = user.username ? user.username : req.body.username
+
+ if (!req.body.password) { delete req.body.password }
+
+ await user.update(req.body)
+
+ if (!user.is_active && req.body.is_active && user.recover_code) {
+ mail.send(user.email, 'confirm', { user, config })
+ }
+ res.json(user)
},
-
- async register(req, res) {
- if (!settingsController.settings.allow_registration) return res.sendStatus(404)
+ async register (req, res) {
+ if (!settingsController.settings.allow_registration) { return res.sendStatus(404) }
const n_users = await User.count()
try {
// the first registered user will be an active admin
@@ -266,7 +272,7 @@ const userController = {
}
},
- async create(req, res) {
+ async create (req, res) {
try {
req.body.is_active = true
req.body.recover_code = crypto.randomBytes(16).toString('hex')
@@ -278,7 +284,7 @@ const userController = {
}
},
- async remove(req, res) {
+ async remove (req, res) {
try {
const user = await User.findByPk(req.params.id)
user.destroy()
diff --git a/server/api/index.js b/server/api/index.js
index 67c54145..3c556447 100644
--- a/server/api/index.js
+++ b/server/api/index.js
@@ -14,6 +14,8 @@ const settingsController = require('./controller/settings')
const storage = require('./storage')
const upload = multer({ storage })
+const debug = require('debug')('api')
+
const api = express.Router()
api.use(cookieParser())
api.use(bodyParser.urlencoded({ extended: false }))
@@ -24,10 +26,10 @@ const jwt = expressJwt({
credentialsRequired: false,
getToken: function fromHeaderOrQuerystring (req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
- return req.headers.authorization.split(' ')[1];
+ 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
+ if (prefix === 'Bearer') { return token }
}
}
})
@@ -44,13 +46,13 @@ api.post('/user/recover_password', userController.updatePasswordWithRecoverCode)
api.post('/user/register', userController.register)
api.post('/user', jwt, isAuth, isAdmin, userController.create)
-// update user (disable/)
-api.put('/user', jwt, isAuth, isAdmin, userController.update)
+// update user
+api.put('/user', jwt, isAuth, userController.update)
-//delete user
+// delete user
api.delete('/user/:id', jwt, isAuth, isAdmin, userController.remove)
-//
+//
// api.delete('/user', userController.remove)
// get all users
@@ -64,7 +66,7 @@ api.put('/place', jwt, isAuth, isAdmin, eventController.updatePlace)
// add event
api.post('/user/event', jwt, fillUser, upload.single('image'), userController.addEvent)
-
+
// update event
api.put('/user/event', jwt, isAuth, upload.single('image'), userController.updateEvent)
@@ -100,4 +102,16 @@ api.get('/export/:type', exportController.export)
api.get('/event/:month/:year', eventController.getAll)
+// Handle 404
+api.use((req, res) => {
+ debug('404 Page not found: %s', req.path)
+ res.status(404).send('404: Page not Found')
+})
+
+// Handle 500
+api.use((error, req, res, next) => {
+ debug(error)
+ res.status(500).send('500: Internal Server Error')
+})
+
module.exports = api
diff --git a/server/api/mail.js b/server/api/mail.js
index aa29674c..9dcedcec 100644
--- a/server/api/mail.js
+++ b/server/api/mail.js
@@ -7,7 +7,7 @@ const debug = require('debug')('email')
moment.locale('it')
const mail = {
- send(addresses, template, locals) {
+ send (addresses, template, locals) {
debug(`Send ${template} email to ${addresses}`)
const email = new Email({
views: { root: path.join(__dirname, '..', 'emails') },
@@ -30,7 +30,7 @@ const mail = {
updateFiles: false,
defaultLocale: settings.locale,
locale: settings.locale,
- locales: ['it', 'es'],
+ locales: ['it', 'es']
},
transport: config.smtp
})
diff --git a/server/api/models/comment.js b/server/api/models/comment.js
index 1eefb43a..7b836812 100644
--- a/server/api/models/comment.js
+++ b/server/api/models/comment.js
@@ -1,10 +1,10 @@
'use strict'
- module.exports = (sequelize, DataTypes) => {
+module.exports = (sequelize, DataTypes) => {
const comment = sequelize.define('comment', {
activitypub_id: {
type: DataTypes.STRING(18),
index: true,
- unique: true,
+ unique: true
},
data: DataTypes.JSON
}, {})
@@ -12,4 +12,4 @@
comment.belongsTo(models.event)
}
return comment
-};
+}
diff --git a/server/api/models/event.js b/server/api/models/event.js
index fbfe85f1..29944938 100644
--- a/server/api/models/event.js
+++ b/server/api/models/event.js
@@ -6,7 +6,7 @@ module.exports = (sequelize, DataTypes) => {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
- autoIncrement: true,
+ autoIncrement: true
},
title: DataTypes.STRING,
slug: DataTypes.STRING,
@@ -36,26 +36,48 @@ module.exports = (sequelize, DataTypes) => {
event.hasMany(models.comment)
}
- //
- event.prototype.toAP = function (username, follower) {
- const tags = this.tags && '-' + this.tags.map(t => '#' + t.tag).join(' ')
- const content = `${this.title} @${this.place.name}
- ${moment.unix(this.start_datetime).format('dddd, D MMMM (HH:mm)')}
- ${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description} ${tags}
`
+ //
+ event.prototype.toAP = function (username, follower = []) {
+ const tags = this.tags && this.tags.map(t => '#' + t.tag).join(' ')
+ const content = `${this.title}
+ 📍${this.place.name}
+ ⏰ ${moment.unix(this.start_datetime).format('dddd, D MMMM (HH:mm)')}
+ ${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description}
+ ${tags}
`
+
+ const attachment = []
+ if (this.image_path) {
+ attachment.push({
+ type: 'Document',
+ mediaType: 'image/jpeg',
+ url: `${config.baseurl}/media/${this.image_path}`,
+ name: null,
+ blurHash: null
+ })
+ }
return {
- id: `${config.baseurl}/federation/m/c_${this.id}`,
- type: 'Create',
- actor: `${config.baseurl}/federation/u/${username}`,
- object: {
- id: `${config.baseurl}/federation/m/${this.id}`,
- type: 'Note',
- published: this.createdAt,
- attributedTo: `${config.baseurl}/federation/u/${username}`,
- to: 'https://www.w3.org/ns/activitystreams#Public',
- cc: follower ? follower: [],
- content
- }
+ // id: `${config.baseurl}/federation/m/c_${this.id}`,
+ // type: 'Create',
+ // actor: `${config.baseurl}/federation/u/${username}`,
+ // url: `${config.baseurl}/federation/m/${this.id}`,
+ // object: {
+ type: 'Note',
+ id: `${config.baseurl}/federation/m/${this.id}`,
+ url: `${config.baseurl}/federation/m/${this.id}`,
+ attachment,
+ tag: this.tags.map(tag => ({
+ type: 'Hashtag',
+ name: '#' + tag.tag
+ })),
+ published: this.createdAt,
+ attributedTo: `${config.baseurl}/federation/u/${username}`,
+ to: ['https://www.w3.org/ns/activitystreams#Public'],
+ cc: follower || [],
+ content,
+ summary: null,
+ sensitive: false,
+ // }
}
}
diff --git a/server/api/models/index.js b/server/api/models/index.js
index 4a02d06d..a25c09f1 100644
--- a/server/api/models/index.js
+++ b/server/api/models/index.js
@@ -7,7 +7,7 @@ const consola = require('consola')
const db = {}
const sequelize = new Sequelize(config.db)
-sequelize.authenticate().catch( e => {
+sequelize.authenticate().catch(e => {
consola.error('Error connecting to DB: ', String(e))
process.exit(-1)
})
@@ -21,15 +21,14 @@ fs
const model = sequelize.import(path.join(__dirname, file))
db[model.name] = model
})
-
- Object.keys(db).forEach(modelName => {
- if (db[modelName].associate) {
- db[modelName].associate(db)
- }
- })
-
- db.sequelize = sequelize
- db.Sequelize = Sequelize
-
- module.exports = db
-
\ No newline at end of file
+
+Object.keys(db).forEach(modelName => {
+ if (db[modelName].associate) {
+ db[modelName].associate(db)
+ }
+})
+
+db.sequelize = sequelize
+db.Sequelize = Sequelize
+
+module.exports = db
diff --git a/server/api/models/place.js b/server/api/models/place.js
index 712cf96f..5dbf7ecf 100644
--- a/server/api/models/place.js
+++ b/server/api/models/place.js
@@ -3,7 +3,8 @@ module.exports = (sequelize, DataTypes) => {
const place = sequelize.define('place', {
name: {
type: DataTypes.STRING,
- unique: true, index: true,
+ unique: true,
+ index: true,
allowNull: false
},
address: DataTypes.STRING
diff --git a/server/api/models/tag.js b/server/api/models/tag.js
index 0f5465fe..59dd7465 100644
--- a/server/api/models/tag.js
+++ b/server/api/models/tag.js
@@ -15,4 +15,4 @@ module.exports = (sequelize, DataTypes) => {
}
return tag
-};
+}
diff --git a/server/api/models/user.js b/server/api/models/user.js
index d60aa2dd..809d9430 100644
--- a/server/api/models/user.js
+++ b/server/api/models/user.js
@@ -2,6 +2,7 @@
const bcrypt = require('bcryptjs')
const crypto = require('crypto')
const util = require('util')
+const debug = require('debug')('model:user')
const generateKeyPair = util.promisify(crypto.generateKeyPair)
@@ -14,9 +15,10 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false
},
display_name: DataTypes.STRING,
+ settings: DataTypes.JSON,
email: {
type: DataTypes.STRING,
- unique: { msg: 'error.email_taken' },
+ unique: { msg: 'error.email_taken' },
index: true,
allowNull: false
},
@@ -44,13 +46,14 @@ module.exports = (sequelize, DataTypes) => {
}
user.prototype.comparePassword = async function (pwd) {
- if (!this.password) return false
+ if (!this.password) { return false }
const ret = await bcrypt.compare(pwd, this.password)
return ret
}
user.beforeSave(async (user, options) => {
if (user.changed('password')) {
+ debug('Password for %s modified', user.username)
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(user.password, salt)
user.password = hash
@@ -58,6 +61,7 @@ module.exports = (sequelize, DataTypes) => {
})
user.beforeCreate(async (user, options) => {
+ debug('Create a new user => %s', user.username)
// generate rsa keys
const rsa = await generateKeyPair('rsa', {
modulusLength: 4096,
@@ -74,4 +78,4 @@ module.exports = (sequelize, DataTypes) => {
})
return user
-};
+}
diff --git a/server/api/storage.js b/server/api/storage.js
index 70f84d9e..3ec61c00 100644
--- a/server/api/storage.js
+++ b/server/api/storage.js
@@ -13,7 +13,7 @@ try {
}
const DiskStorage = {
- _handleFile(req, file, cb) {
+ _handleFile (req, file, cb) {
const filename = crypto.randomBytes(16).toString('hex') + '.jpg'
const finalPath = path.resolve(config.upload_path, filename)
const thumbPath = path.resolve(config.upload_path, 'thumb', filename)
@@ -36,7 +36,7 @@ const DiskStorage = {
})
})
},
- _removeFile(req, file, cb) {
+ _removeFile (req, file, cb) {
delete file.destination
delete file.filename
delete file.path
diff --git a/server/federation/comments.js b/server/federation/comments.js
index 15ef55d0..203902ad 100644
--- a/server/federation/comments.js
+++ b/server/federation/comments.js
@@ -5,17 +5,22 @@ const debug = require('debug')('fediverse:comment')
module.exports = {
async create (req, res) {
const body = req.body
- //search for related event
+ // search for related event
const inReplyTo = body.object.inReplyTo
- const match = inReplyTo.match(`${config.baseurl}/federation/m/(.*)`)
- if (!match || match.length<2) return res.status(404).send('Event not found!')
+ const match = inReplyTo.match('.*\/federation\/m\/(.*)')
+ console.error('inReplyTo', inReplyTo)
+ console.error('match', match)
+ if (!match || match.length < 2) {
+ debug('Comment not found %s', inReplyTo)
+ return res.status(404).send('Event not found!')
+ }
let event = await Event.findByPk(Number(match[1]))
debug('comment coming for %s', inReplyTo)
if (!event) {
// in reply to another comment...
- const comment = await Comment.findByPk(inReplyTo, { include: [Event] })
- if (!comment) return res.status(404).send('Not found')
+ const comment = await Comment.findOne({ where: { activitypub_id: inReplyTo }, include: [Event] })
+ if (!comment) { return res.status(404).send('Not found') }
event = comment.event
}
debug('comment from %s to "%s"', req.body.actor, event.title)
@@ -27,6 +32,16 @@ module.exports = {
})
res.sendStatus(201)
+ },
+ async remove (req, res) {
+ const comment = await Comment.findOne({ where: { activitypub_id: req.body.object.id } })
+ if (!comment) {
+ debug('Comment %s not found', req.body.object.id)
+ return res.status(404).send('Not found')
+ }
+ await comment.destroy()
+ debug('Comment %s removed!', req.body.object.id)
+ return res.sendStatus(201)
}
}
diff --git a/server/federation/ego.js b/server/federation/ego.js
index c951bdcd..431dd5b6 100644
--- a/server/federation/ego.js
+++ b/server/federation/ego.js
@@ -5,21 +5,21 @@ const debug = require('debug')('fediverse:ego')
module.exports = {
async boost (req, res) {
const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`)
- if (!match || match.length<2) return res.status(404).send('Event not found!')
+ if (!match || match.length < 2) { return res.status(404).send('Event not found!') }
debug('boost %s', match[1])
const event = await Event.findByPk(Number(match[1]))
- if (!event) return res.status(404).send('Event not found!')
- await event.update({ boost: [...event.boost, req.body.actor]})
+ if (!event) { return res.status(404).send('Event not found!') }
+ await event.update({ boost: [...event.boost, req.body.actor] })
res.sendStatus(201)
},
async bookmark (req, res) {
const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`)
- if (!match || match.length<2) return res.status(404).send('Event not found!')
+ if (!match || match.length < 2) { return res.status(404).send('Event not found!') }
const event = await Event.findByPk(Number(match[1]))
debug('%s bookmark %s (%d)', req.body.actor, event.title, event.likes.length)
- if (!event) return res.status(404).send('Event not found!')
- await event.update({ likes: [...event.likes, req.body.actor]})
+ if (!event) { return res.status(404).send('Event not found!') }
+ await event.update({ likes: [...event.likes, req.body.actor] })
res.sendStatus(201)
},
@@ -27,11 +27,11 @@ module.exports = {
const body = req.body
const object = body.object
const match = object.object.match(`${config.baseurl}/federation/m/(.*)`)
- if (!match || match.length<2) return res.status(404).send('Event not found!')
+ if (!match || match.length < 2) { return res.status(404).send('Event not found!') }
const event = await Event.findByPk(Number(match[1]))
debug('%s unbookmark %s (%d)', body.actor, event.title, event.likes.length)
- if (!event) return res.status(404).send('Event not found!')
- await event.update({ likes: [...event.likes.filter(actor => actor!==body.actor)]})
+ if (!event) { return res.status(404).send('Event not found!') }
+ await event.update({ likes: [...event.likes.filter(actor => actor !== body.actor)] })
res.sendStatus(201)
}
-}
\ No newline at end of file
+}
diff --git a/server/federation/follows.js b/server/federation/follows.js
index 31b7bad0..4fe8facf 100644
--- a/server/federation/follows.js
+++ b/server/federation/follows.js
@@ -2,38 +2,50 @@ const config = require('config')
const Helpers = require('./helpers')
const { user: User } = require('../api/models')
const crypto = require('crypto')
-const debug = require('debug')('follows')
+const debug = require('debug')('federation:follows')
module.exports = {
// follow request from fediverse
async follow (req, res) {
const body = req.body
- if (typeof body.object !== 'string') return
+ if (typeof body.object !== 'string') { return }
const username = body.object.replace(`${config.baseurl}/federation/u/`, '')
- const user = await User.findOne({ where: { username }})
- if (!user) return res.status(404).send('User not found')
+ const user = await User.findOne({ where: { username } })
+ if (!user) { return res.status(404).send('User not found') }
// check for duplicate
- if (user.followers.indexOf(body.actor) === -1) {
- debug('%s followed by %s (%d)', username, body.actor, user.followers.length)
+ if (!user.followers.includes(body.actor)) {
await user.update({ followers: [...user.followers, body.actor] })
+ debug('%s followed by %s (%d)', username, body.actor, user.followers.length)
} else {
debug('duplicate %s followed by %s', username, body.actor)
}
const guid = crypto.randomBytes(16).toString('hex')
- let message = {
+ const message = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `${config.baseurl}/federation/${guid}`,
'type': 'Accept',
'actor': `${config.baseurl}/federation/u/${user.username}`,
- 'object': body,
- }
+ 'object': body
+ }
Helpers.signAndSend(message, user, body.actor)
res.sendStatus(200)
},
// unfollow request from fediverse
- unfollow () {
- console.error('inside unfollow')
+ async unfollow (req, res) {
+ const body = req.body
+ const username = body.object.object.replace(`${config.baseurl}/federation/u/`, '')
+ const user = await User.findOne({ where: { username } })
+ if (!user) { return res.status(404).send('User not found') }
+
+ if (body.actor !== body.object.actor) {
+ debug('Unfollow an user created by a different actor !?!?')
+ return res.status(400).send('Bad things')
+ }
+ const followers = user.followers.filter(follower => follower !== body.actor)
+ await user.update({ followers })
+ debug('%s unfollowed by %s (%d)', username, body.actor, user.followers.length)
+ res.sendStatus(200)
}
}
diff --git a/server/federation/helpers.js b/server/federation/helpers.js
index 35fc7a0f..c780c6d0 100644
--- a/server/federation/helpers.js
+++ b/server/federation/helpers.js
@@ -3,33 +3,30 @@ const fetch = require('node-fetch')
const crypto = require('crypto')
const config = require('config')
const httpSignature = require('http-signature')
-const debug = require('debug')('fediverse:helpers')
+const debug = require('debug')('federation:helpers')
+const { user: User } = require('../api/models')
+const url = require('url')
const actorCache = []
const Helpers = {
- async signAndSend(message, user, to) {
-
+ async signAndSend (message, user, to) {
// get the URI of the actor object and append 'inbox' to it
const toInbox = to + '/inbox'
- const toOrigin = new URL(to)
- const toPath = toInbox.replace(toOrigin.origin, '')
+ const toOrigin = url.parse(to)
+ const toPath = toOrigin.path + '/inbox'
// get the private key
const privkey = user.rsa.privateKey
const signer = crypto.createSign('sha256')
const d = new Date()
const stringToSign = `(request-target): post ${toPath}\nhost: ${toOrigin.hostname}\ndate: ${d.toUTCString()}`
- console.error('stringToSign ', stringToSign)
signer.update(stringToSign)
signer.end()
const signature = signer.sign(privkey)
const signature_b64 = signature.toString('base64')
const header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"`
- console.error('header ', header)
- console.error('requestTo ', toInbox)
- console.error('host ', toOrigin.hostname)
- const response = await fetch(toInbox, {
+ const ret = await fetch(toInbox, {
headers: {
'Host': toOrigin.hostname,
'Date': d.toUTCString(),
@@ -39,31 +36,71 @@ const Helpers = {
},
method: 'POST',
body: JSON.stringify(message) })
-
- console.log('Response:', response.body, response.statusCode, response.status, response.statusMessage)
+ debug('sign %s => %s', ret.status, await ret.text())
},
- async sendEvent(event, user) {
- const followers = user.followers
- for(let follower of followers) {
- debug('Notify %s with event %s', follower, event.title)
- const body = event.toAP(user.username, follower)
+
+ async sendEvent (event, user) {
+ // TODO: has to use sharedInbox!
+ // event is sent by user that published it and by the admin instance
+ const instanceAdmin = await User.findOne({ where: { email: config.admin } })
+ if (!instanceAdmin || !instanceAdmin.username) {
+ debug('Instance admin not found (there is no user with email => %s)', config.admin)
+ return
+ }
+
+ for (const follower of instanceAdmin.followers) {
+ debug('Notify %s with event %s (from admin user %s)', follower, event.title, instanceAdmin.username)
+ const body = {
+ id: `${config.baseurl}/federation/m/${event.id}#create`,
+ type: 'Create',
+ to: ['https://www.w3.org/ns/activitystreams#Public'],
+ cc: [`${config.baseurl}/federation/u/${instanceAdmin.username}/followers`, follower],
+ actor: `${config.baseurl}/federation/u/${instanceAdmin.username}`,
+ object: event.toAP(instanceAdmin.username, [`${config.baseurl}/federation/u/${instanceAdmin.username}/followers`, follower])
+ }
+ body['@context'] = 'https://www.w3.org/ns/activitystreams'
+ Helpers.signAndSend(body, instanceAdmin, follower)
+ }
+
+ // in case the event is published by the Admin itself do not republish
+ if (instanceAdmin.id === user.id) {
+ debug('Event published by instance Admin')
+ return
+ }
+
+ if (!user.settings.enable_federation || !user.username) {
+ debug('Federation disabled for user %d (%s)', user.id, user.username)
+ return
+ }
+
+ for (const follower of user.followers) {
+ debug('Notify %s with event %s (from user %s)', follower, event.title, user.username)
+ const body = {
+ id: `${config.baseurl}/federation/m/${event.id}#create`,
+ type: 'Create',
+ to: ['https://www.w3.org/ns/activitystreams#Public'],
+ cc: [`${config.baseurl}/federation/u/${user.username}/followers`, follower],
+ published: event.createdAt,
+ actor: `${config.baseurl}/federation/u/${user.username}`,
+ object: event.toAP(user.username, [`${config.baseurl}/federation/u/${user.username}/followers`, follower])
+ }
body['@context'] = 'https://www.w3.org/ns/activitystreams'
Helpers.signAndSend(body, user, follower)
- }
+ }
},
- async getFederatedUser(address) {
+ async getFederatedUser (address) {
address = address.trim()
const [ username, host ] = address.split('@')
const url = `https://${host}/.well-known/webfinger?resource=acct:${username}@${host}`
return Helpers.getActor(url)
},
-
+
// TODO: cache
- async getActor(url, force=false) {
+ async getActor (url, force = false) {
// try with cache first
- if (!force && actorCache[url]) return actorCache[url]
- const user = await fetch(url, { headers: {'Accept': 'application/jrd+json, application/json'} })
+ if (!force && actorCache[url]) { return actorCache[url] }
+ const user = await fetch(url, { headers: { 'Accept': 'application/jrd+json, application/json' } })
.then(res => {
if (!res.ok) {
debug('[ERR] Actor %s => %s', url, res.statusText)
@@ -76,25 +113,26 @@ const Helpers = {
},
// ref: https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/
- async verifySignature(req, res, next) {
+ async verifySignature (req, res, next) {
let user = await Helpers.getActor(req.body.actor)
- if (!user) return res.status(401).send('Actor not found')
+ if (!user) { return res.status(401).send('Actor not found') }
// little hack -> https://github.com/joyent/node-http-signature/pull/83
req.headers.authorization = 'Signature ' + req.headers.signature
- // another little hack :/
+ // another little hack :/
// https://github.com/joyent/node-http-signature/issues/87
req.url = '/federation' + req.url
const parsed = httpSignature.parseRequest(req)
- if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) return next()
-
+ if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) { return next() }
+
// signature not valid, try without cache
user = await Helpers.getActor(req.body.actor, true)
- if (!user) return res.status(401).send('Actor not found')
- if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) return next()
+ if (!user) { return res.status(401).send('Actor not found') }
+ if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) { return next() }
// still not valid
+ debug('Invalid signature from user %s', req.body.actor)
res.send('Request signature could not be verified', 401)
}
}
diff --git a/server/federation/index.js b/server/federation/index.js
index 9560694a..e2c4c3a5 100644
--- a/server/federation/index.js
+++ b/server/federation/index.js
@@ -4,35 +4,34 @@ const config = require('config')
const cors = require('cors')
const Follows = require('./follows')
const Users = require('./users')
-const { event: Event, user: User } = require('../api/models')
+const { event: Event, user: User, tag: Tag, place: Place } = require('../api/models')
const Comments = require('./comments')
const Helpers = require('./helpers')
const Ego = require('./ego')
+const debug = require('debug')('federation')
/**
* Federation is calling!
* ref: https://www.w3.org/TR/activitypub/#Overview
*/
router.use(cors())
-router.use(express.json({type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']}))
-
+router.use(express.json({ type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'] }))
router.get('/m/:event_id', async (req, res) => {
const event_id = req.params.event_id
- if (req.accepts('html')) return res.redirect(301, `/event/${event_id}`)
+ // if (req.accepts('html')) return res.redirect(301, `/event/${event_id}`)
- const event = await Event.findByPk(req.params.event_id, { include: [ User ] })
- if (!event) return res.status(404).send('Not found')
+ const event = await Event.findByPk(req.params.event_id, { include: [ User, Tag, Place ] })
+ if (!event) { return res.status(404).send('Not found') }
return res.json(event.toAP(event.user.username))
})
// get any message coming from federation
// Federation is calling!
router.post('/u/:name/inbox', Helpers.verifySignature, async (req, res) => {
-
const b = req.body
-
- switch(b.type) {
+ debug(b.type)
+ switch (b.type) {
case 'Follow':
Follows.follow(req, res)
break
@@ -56,7 +55,7 @@ router.post('/u/:name/inbox', Helpers.verifySignature, async (req, res) => {
Ego.bookmark(req, res)
break
case 'Delete':
- console.error('Delete ?!?!')
+ await Comments.remove(req, res)
break
case 'Create':
// this is a reply
diff --git a/server/federation/nodeinfo.js b/server/federation/nodeinfo.js
index c5878266..445eb78c 100644
--- a/server/federation/nodeinfo.js
+++ b/server/federation/nodeinfo.js
@@ -18,7 +18,7 @@ router.get('/', async (req, res) => {
},
protocols: ['activitypub'],
openRegistrations: settingsController.settings.allow_registration,
- usage:{
+ usage: {
users: {
total: 10
}
diff --git a/server/federation/users.js b/server/federation/users.js
index 29f70c00..4e9a1df4 100644
--- a/server/federation/users.js
+++ b/server/federation/users.js
@@ -1,6 +1,7 @@
-const { user: User, event: Event } = require('../api/models')
+const { user: User, event: Event, place: Place, tag: Tag } = require('../api/models')
const config = require('config')
const get = require('lodash/get')
+const debug = require('debug')('fediverse:user')
module.exports = {
async get (req, res) {
@@ -15,10 +16,21 @@ module.exports = {
],
id: `${config.baseurl}/federation/u/${name}`,
type: 'Person',
- preferredUsername: name,
+ name: user.display_name || user.username,
+ preferredUsername: user.username,
inbox: `${config.baseurl}/federation/u/${name}/inbox`,
outbox: `${config.baseurl}/federation/u/${name}/outbox`,
followers: `${config.baseurl}/federation/u/${name}/followers`,
+ attachment: [{
+ type: 'PropertyValue',
+ name: 'Website',
+ value: `${config.baseurl}`
+ }],
+ icon: {
+ type: 'Image',
+ mediaType: 'image/x-icon',
+ url: config.baseurl + '/favicon.ico'
+ },
publicKey: {
id: `${config.baseurl}/federation/u/${name}#main-key`,
owner: `${config.baseurl}/federation/u/${name}`,
@@ -30,63 +42,79 @@ module.exports = {
},
async followers (req, res) {
const name = req.params.name
+ const page = req.query.page
+ debug('Retrieve %s followers', name)
if (!name) return res.status(400).send('Bad request.')
const user = await User.findOne({where: { username: name }})
if (!user) return res.status(404).send(`No record found for ${name}`)
- const ret = {
- '@context': [ 'https://www.w3.org/ns/activitystreams' ],
- id: `${config.baseurl}/federation/u/${name}/followers`,
- type: 'OrderedCollection',
- totalItems: user.followers.length,
- first: {
- id: `${config.baseurl}/federation/u/${name}/followers?page=1`,
- type: 'OrderedCollectionPage',
+
+ res.type('application/activity+json; charset=utf-8')
+
+ if (!page) {
+ debug('No pagination')
+ return res.json({
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ id: `${config.baseurl}/federation/u/${name}/followers`,
+ type: 'OrderedCollection',
totalItems: user.followers.length,
- partOf: `${config.baseurl}/federation/u/${name}/followers`,
- orderedItems: user.followers,
- }
+ first: `${config.baseurl}/federation/u/${name}/followers?page=true`,
+ last: `${config.baseurl}/federation/u/${name}/followers?page=true`,
+ })
}
- res.json(ret)
+ return res.json({
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ id: `${config.baseurl}/federation/u/${name}/followers?page=${page}`,
+ type: 'OrderedCollectionPage',
+ totalItems: user.followers.length,
+ partOf: `${config.baseurl}/federation/u/${name}/followers` ,
+ orderedItems: user.followers
+ })
},
+
async outbox (req, res) {
const name = req.params.name
const page = req.query.page
-
+
if (!name) return res.status(400).send('Bad request.')
const user = await User.findOne({
- include: [ Event ],
+ include: [ { model: Event, include: [ Place, Tag ] } ],
where: { username: name }
})
- if (!user) return res.status(404).send(`No record found for ${name}`)
+ if (!user) return res.status(404).send(`No record found for ${name}`)
+
+ debug('Inside outbox, should return all events from this user')
- console.error('Inside outbox, should return all events from this user')
// https://www.w3.org/TR/activitypub/#outbox
+ res.type('application/activity+json; charset=utf-8')
if (!page) {
- const ret = {
+ debug('Without pagination ')
+ return res.json({
'@context': 'https://www.w3.org/ns/activitystreams',
id: `${config.baseurl}/federation/u/${name}/outbox`,
type: 'OrderedCollection',
totalItems: user.events.length,
- first: {
- id: `${config.baseurl}/federation/u/${name}/outbox?page=true`,
- type: 'OrderedCollectionPage',
- totalItem: user.events.length,
- partOf: `${config.baseurl}/federation/u/${name}/outbox`,
- orderedItems: user.events.map(e => e.toAP(user.username))
- }
- }
- res.type('application/activity+json; charset=utf-8')
- return res.json(ret)
+ first: `${config.baseurl}/federation/u/${name}/outbox?page=true`,
+ last: `${config.baseurl}/federation/u/${name}/outbox?page=true`
+ })
}
- const ret = {
+
+ debug('With pagination %s', page)
+ return res.json({
'@context': 'https://www.w3.org/ns/activitystreams',
- id: `${config.baseurl}/federation/u/${name}/outbox?page=true`,
+ id: `${config.baseurl}/federation/u/${name}/outbox?page=${page}`,
type: 'OrderedCollectionPage',
- partOf: `${config.baseurl}/federation/u/${name}/outbox`,
- orderedItems: user.events.map(e => e.toAP(user.username))
- }
- res.type('application/activity+json; charset=utf-8')
- res.json(ret)
+ totalItems: user.followers.length,
+ partOf: `${config.baseurl}/federation/u/${name}/outbox` ,
+ orderedItems: user.events.map(e => ({
+ id: `${config.baseurl}/federation/m/${e.id}#create`,
+ type: 'Create',
+ to: ['https://www.w3.org/ns/activitystreams#Public'],
+ cc: [`${config.baseurl}/federation/u/${user.username}/followers`],
+ published: e.createdAt,
+ actor: `${config.baseurl}/federation/u/${user.username}`,
+ object: e.toAP(user.username)
+ }))
+ })
}
}
diff --git a/server/federation/webfinger.js b/server/federation/webfinger.js
index 2a970bcb..015b3559 100644
--- a/server/federation/webfinger.js
+++ b/server/federation/webfinger.js
@@ -5,18 +5,31 @@ const cors = require('cors')
const settingsController = require('../api/controller/settings')
const config = require('config')
const version = require('../../package.json').version
+const url = require('url')
+const debug = require('debug')('webfinger')
router.use(cors())
router.get('/webfinger', async (req, res) => {
const resource = req.query.resource
if (!resource || !resource.includes('acct:')) {
+ debug('Bad webfinger request => %s', resource.query)
return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.')
}
- const name = resource.match(/acct:(.*)@/)[1]
- const domain = new URL(config.baseurl).host
- const user = await User.findOne({where: { username: name } })
- if (!user) return res.status(404).send(`No record found for ${name}`)
+ const domain = url.parse(config.baseurl).host
+ const [, name, req_domain] = resource.match(/acct:(.*)@(.*)/)
+
+ if (domain !== req_domain) {
+ debug('Bad webfinger request, requested domain "%s" instead of "%s"', req_domain, domain)
+ return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.')
+ }
+
+ const user = await User.findOne({ where: { username: name } })
+ if (!user) {
+ debug('User not found: %s', name)
+ return res.status(404).send(`No record found for ${name}`)
+ }
+
const ret = {
subject: `acct:${name}@${domain}`,
links: [
@@ -37,24 +50,24 @@ router.get('/nodeinfo/:nodeinfo_version', async (req, res) => {
nodeDescription: 'Gancio instance',
nodeName: config.title
},
- openRegistrations : settingsController.settings.allow_registration,
- protocols :['activitypub'],
- services: { inbound: [], outbound :["atom1.0"]},
+ openRegistrations: settingsController.settings.allow_registration,
+ protocols: ['activitypub'],
+ services: { inbound: [], outbound: ['atom1.0'] },
software: {
name: 'gancio',
version
},
- usage: {
+ usage: {
localComments: 0,
- localPosts:0,
+ localPosts: 0,
users: {
- total:3
+ total: 3
}
},
version: req.params.nodeinfo_version
}
- if(req.params.nodeinfo_version === '2.1') {
+ if (req.params.nodeinfo_version === '2.1') {
ret.software.repository = 'https://git.lattuga.net/cisti/gancio'
}
res.json(ret)
@@ -71,7 +84,7 @@ router.get('/x-nodeinfo2', async (req, res) => {
},
protocols: ['activitypub'],
openRegistrations: settingsController.settings.allow_registration,
- usage:{
+ usage: {
users: {
total: 10
}
@@ -82,18 +95,16 @@ router.get('/x-nodeinfo2', async (req, res) => {
res.json(ret)
})
-
router.get('/nodeinfo', async (req, res) => {
const ret = {
links: [
{ href: `${config.baseurl}/.well-known/nodeinfo/2.0`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.0` },
- { href: `${config.baseurl}/.well-known/nodeinfo/2.1`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.1` },
+ { href: `${config.baseurl}/.well-known/nodeinfo/2.1`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.1` }
]
}
res.json(ret)
})
-
router.use('/host-meta', (req, res) => {
res.type('application/xml')
res.send(`
@@ -103,12 +114,14 @@ router.use('/host-meta', (req, res) => {
})
// Handle 404
-router.use(function(req, res) {
+router.use((req, res) => {
+ debug('404 Page not found: %s', req.path)
res.status(404).send('404: Page not Found')
})
// Handle 500
-router.use(function(error, req, res, next) {
+router.use((error, req, res, next) => {
+ debug(error)
res.status(500).send('500: Internal Server Error')
})
diff --git a/server/firstrun.js b/server/firstrun.js
index b8d6fa09..4f9b1a28 100644
--- a/server/firstrun.js
+++ b/server/firstrun.js
@@ -28,7 +28,7 @@ module.exports = {
await db.user.findAll()
consola.warn(`⚠️ Non empty db! Please move your current db elsewhere than retry.`)
return false
- } catch(e) { }
+ } catch (e) { }
consola.info(`Create tables schema`)
await db.sequelize.sync().catch(e => {
diff --git a/server/index.js b/server/index.js
index 3ad41b7a..b6136aa6 100644
--- a/server/index.js
+++ b/server/index.js
@@ -4,7 +4,7 @@ const { Nuxt, Builder } = require('nuxt')
const nuxt_config = require('../nuxt.config.js')
const config = require('config')
-async function main() {
+async function main () {
nuxt_config.server = config.server
// Init Nuxt.js
@@ -20,7 +20,7 @@ async function main() {
nuxt.listen()
// close connections/port/unix socket
- function shutdown() {
+ function shutdown () {
nuxt.close(async () => {
const db = require('./api/models')
await db.sequelize.close()
diff --git a/server/migrations/20190728213923-add_username.js b/server/migrations/20190728213923-add_username.js
index 8c1950a8..4376e432 100644
--- a/server/migrations/20190728213923-add_username.js
+++ b/server/migrations/20190728213923-add_username.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
@@ -26,4 +26,4 @@ module.exports = {
return queryInterface.dropTable('users');
*/
}
-};
+}
diff --git a/server/migrations/20190728214848-add_displayname.js b/server/migrations/20190728214848-add_displayname.js
index 073d611e..2bdd4d79 100644
--- a/server/migrations/20190728214848-add_displayname.js
+++ b/server/migrations/20190728214848-add_displayname.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
@@ -23,4 +23,4 @@ module.exports = {
return queryInterface.dropTable('users');
*/
}
-};
+}
diff --git a/server/migrations/20190729103119-add_rsa.js b/server/migrations/20190729103119-add_rsa.js
index f01a81ca..858fd928 100644
--- a/server/migrations/20190729103119-add_rsa.js
+++ b/server/migrations/20190729103119-add_rsa.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
@@ -23,4 +23,4 @@ module.exports = {
return queryInterface.dropTable('users');
*/
}
-};
+}
diff --git a/server/migrations/20190729192753-add_followers.js b/server/migrations/20190729192753-add_followers.js
index 9621bd26..d7818968 100644
--- a/server/migrations/20190729192753-add_followers.js
+++ b/server/migrations/20190729192753-add_followers.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
@@ -23,4 +23,4 @@ module.exports = {
return queryInterface.dropTable('users');
*/
}
-};
+}
diff --git a/server/migrations/20190801105908-likes.js b/server/migrations/20190801105908-likes.js
index 8804d9c4..6b9640ef 100644
--- a/server/migrations/20190801105908-likes.js
+++ b/server/migrations/20190801105908-likes.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
@@ -24,4 +24,4 @@ module.exports = {
return queryInterface.dropTable('users');
*/
}
-};
+}
diff --git a/server/migrations/20190801110053-boost.js b/server/migrations/20190801110053-boost.js
index 45d74fab..9b372ecb 100644
--- a/server/migrations/20190801110053-boost.js
+++ b/server/migrations/20190801110053-boost.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
@@ -25,4 +25,4 @@ module.exports = {
return queryInterface.dropTable('users');
*/
}
-};
+}
diff --git a/server/migrations/20190910085948-user_settings.js b/server/migrations/20190910085948-user_settings.js
new file mode 100644
index 00000000..10c45991
--- /dev/null
+++ b/server/migrations/20190910085948-user_settings.js
@@ -0,0 +1,27 @@
+'use strict'
+
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.addColumn('users', 'settings', {
+ type: Sequelize.JSON,
+ defaultValue: {}
+ })
+ /*
+ Add altering commands here.
+ Return a promise to correctly handle asynchronicity.
+
+ Example:
+ return queryInterface.createTable('users', { id: Sequelize.INTEGER });
+ */
+ },
+
+ down: (queryInterface, Sequelize) => {
+ /*
+ Add reverting commands here.
+ Return a promise to correctly handle asynchronicity.
+
+ Example:
+ return queryInterface.dropTable('users');
+ */
+ }
+}
diff --git a/server/notifier.js b/server/notifier.js
index 9cb8b554..ae664515 100644
--- a/server/notifier.js
+++ b/server/notifier.js
@@ -5,18 +5,18 @@ const config = require('config')
const eventController = require('./api/controller/event')
const get = require('lodash/get')
-const { event: Event, notification: Notification, event_notification: EventNotification,
+const { event: Event, notification: Notification, event_notification: EventNotification,
user: User, place: Place, tag: Tag } = require('./api/models')
const notifier = {
- async sendNotification(notification, event) {
+ async sendNotification (notification, event) {
return
const promises = []
switch (notification.type) {
case 'mail':
- return mail.send(notification.email, 'event', { event, config, notification })
- case 'admin_email':
- return mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification })
+ return mail.send(notification.email, 'event', { event, config, notification })
+ case 'admin_email':
+ return mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification })
// case 'mastodon':
// // instance publish
// if (bot.bot) {
@@ -31,7 +31,7 @@ const notifier = {
}
return Promise.all(promises)
},
- async notifyEvent(eventId) {
+ async notifyEvent (eventId) {
const event = await Event.findByPk(eventId, {
include: [ Tag, Place, User ]
})
@@ -42,7 +42,7 @@ const notifier = {
const eventNotifications = await EventNotification.findAll({
where: {
- notificationId: notifications.map(n=>n.id),
+ notificationId: notifications.map(n => n.id),
status: 'new'
}
})
@@ -57,15 +57,15 @@ const notifier = {
}
return e.save()
})
-
+
return Promise.all(promises)
},
- async notify() {
+ async notify () {
// get all event notification in queue
const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } })
const promises = eventNotifications.map(async e => {
const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] })
- if (!event.place) return
+ if (!event.place) { return }
const notification = await Notification.findByPk(e.notificationId)
try {
await sendNotification(notification, event, e)
@@ -82,4 +82,4 @@ const notifier = {
}
}
-module.exports = notifier
\ No newline at end of file
+module.exports = notifier
diff --git a/server/routes.js b/server/routes.js
index 9ee6e85b..19c2e4f7 100644
--- a/server/routes.js
+++ b/server/routes.js
@@ -7,6 +7,11 @@ const webfinger = require('./federation/webfinger')
const debug = require('debug')('routes')
const router = express.Router()
+router.use((req, res, next) => {
+ debug(req.path)
+ next()
+})
+
router.use('/favicon.ico', express.static(path.resolve(config.favicon || 'assets/favicon.ico')))
router.use('/media/', express.static(config.upload_path))
router.use('/api', api)
@@ -27,6 +32,4 @@ router.use((error, req, res, next) => {
res.status(500).send('500: Internal Server Error')
})
-
-
module.exports = router
diff --git a/store/index.js b/store/index.js
index bcbab503..8c5ce484 100644
--- a/store/index.js
+++ b/store/index.js
@@ -12,7 +12,7 @@ export const state = () => ({
allow_anon_event: true,
allow_recurrent_event: true,
recurrent_event_visible: false,
- enable_federation: false,
+ enable_federation: false
},
filters: {
tags: [],
@@ -26,37 +26,34 @@ export const state = () => ({
export const getters = {
// filter matches search tag/place
- filteredEvents: state => {
-
+ filteredEvents: state => {
const search_for_tags = !!state.filters.tags.length
const search_for_places = !!state.filters.places.length
return state.events.filter(e => {
-
// filter past events
- if (!state.filters.show_past_events && e.past) return false
+ 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.recurrent) { return false }
if (search_for_places) {
- if (find(state.filters.places, p => p === e.place.id)) return true
+ if (find(state.filters.places, p => p === e.place.id)) { return true }
}
if (search_for_tags) {
- const common_tags = intersection(e.tags, state.filters.tags);
- if (common_tags.length > 0) return true
+ const common_tags = intersection(e.tags, state.filters.tags)
+ if (common_tags.length > 0) { return true }
}
- if (!search_for_places && !search_for_tags) return true
-
+ if (!search_for_places && !search_for_tags) { return true }
+
return false
})
},
// filter matches search tag/place including past events
- filteredEventsWithPast: state => {
-
+ filteredEventsWithPast: state => {
const search_for_tags = !!state.filters.tags.length
const search_for_places = !!state.filters.places.length
@@ -64,75 +61,75 @@ 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.recurrent) { return false }
if (!match && search_for_places) {
- if (find(state.filters.places, p => p === e.place.id)) return true
+ if (find(state.filters.places, p => p === e.place.id)) { return true }
}
if (search_for_tags) {
- const common_tags = intersection(e.tags, state.filters.tags);
- if (common_tags.length > 0) return true
+ const common_tags = intersection(e.tags, state.filters.tags)
+ if (common_tags.length > 0) { return true }
}
- if (!search_for_places && !search_for_tags) return true
-
+ if (!search_for_places && !search_for_tags) { return true }
+
return false
})
}
}
export const mutations = {
- setEvents(state, events) {
+ setEvents (state, events) {
// set`past` and `newDay` flags to event
let lastDay = null
state.events = events.map(e => {
const currentDay = moment.unix(e.start_datetime).date()
e.newDay = (!lastDay || lastDay !== currentDay) && currentDay
lastDay = currentDay
- const end_datetime = e.end_datetime || e.start_datetime+3600*2
+ const end_datetime = e.end_datetime || e.start_datetime + 3600 * 2
const past = ((moment().unix()) - end_datetime) > 0
e.past = !!past
return e
})
},
- addEvent(state, event) {
+ addEvent (state, event) {
state.events.push(event)
},
- updateEvent(state, event) {
+ updateEvent (state, event) {
state.events = state.events.map((e) => {
- if (e.id !== event.id) return e
+ if (e.id !== event.id) { return e }
return event
})
},
- delEvent(state, eventId) {
+ delEvent (state, eventId) {
state.events = state.events.filter(ev => {
return ev.id !== eventId
})
},
- update(state, { tags, places }) {
+ update (state, { tags, places }) {
state.tags = tags
state.places = places
},
- setSearchTags(state, tags) {
+ setSearchTags (state, tags) {
state.filters.tags = tags
},
- setSearchPlaces(state, places) {
+ setSearchPlaces (state, places) {
state.filters.places = places
},
- showPastEvents(state, show) {
+ showPastEvents (state, show) {
state.filters.show_past_events = show
},
- showRecurrentEvents(state, show) {
+ showRecurrentEvents (state, show) {
state.filters.show_recurrent_events = show
},
- setSettings(state, settings) {
+ setSettings (state, settings) {
state.settings = settings
},
- setSetting(state, setting) {
+ setSetting (state, setting) {
state.settings[setting.key] = setting.value
},
- setLocale(state, locale) {
+ setLocale (state, locale) {
state.locale = locale
}
}
@@ -140,51 +137,50 @@ export const mutations = {
export const actions = {
// this method is called server side only for each request
// we use it to get configuration from db, setting locale, etc...
- async nuxtServerInit ({ commit }, { app, req } ) {
+ async nuxtServerInit ({ commit }, { app, req }) {
const settings = await app.$axios.$get('/settings')
commit('setSettings', settings)
// apply settings
commit('showRecurrentEvents', settings.allow_recurrent_event && settings.recurrent_event_visible)
-
},
- async updateEvents({ commit }, page) {
+ async updateEvents ({ commit }, page) {
const events = await this.$axios.$get(`/event/${page.month - 1}/${page.year}`)
commit('setEvents', events)
},
- async updateMeta({ commit }) {
+ async updateMeta ({ commit }) {
const { tags, places } = await this.$axios.$get('/event/meta')
commit('update', { tags, places })
},
- async addEvent({ commit }, formData) {
+ async addEvent ({ commit }, formData) {
const event = await this.$axios.$post('/user/event', formData)
if (event.user) {
commit('addEvent', event)
}
},
- async updateEvent({ commit }, formData) {
+ async updateEvent ({ commit }, formData) {
const event = await this.$axios.$put('/user/event', formData)
if (event.user) {
commit('updateEvent', event)
}
},
- delEvent({ commit }, eventId) {
+ delEvent ({ commit }, eventId) {
commit('delEvent', eventId)
},
- setSearchTags({ commit }, tags) {
+ setSearchTags ({ commit }, tags) {
commit('setSearchTags', tags)
},
- setSearchPlaces({ commit }, places) {
+ setSearchPlaces ({ commit }, places) {
commit('setSearchPlaces', places)
},
- showPastEvents({ commit }, show) {
+ showPastEvents ({ commit }, show) {
commit('showPastEvents', show)
},
- showRecurrentEvents({ commit }, show ) {
+ showRecurrentEvents ({ commit }, show) {
commit('showRecurrentEvents', show)
},
- async setSetting({ commit }, setting) {
- await this.$axios.$post('/settings', setting )
+ async setSetting ({ commit }, setting) {
+ await this.$axios.$post('/settings', setting)
commit('setSetting', setting)
- },
+ }
}
diff --git a/yarn.lock b/yarn.lock
index 7fea9151..370d3c3e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -708,6 +708,13 @@
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a"
integrity sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw==
+"@hapi/boom@^7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-7.4.3.tgz#79ffed6ef8c625046a3bd069abed5a9d35fc50c1"
+ integrity sha512-3di+R+BcGS7HKy67Zi6mIga8orf67GdR0ubDEVBG1oqz3y9B70LewsuCMCSvWWLKlI6V1+266zqhYzjMrPGvZw==
+ dependencies:
+ "@hapi/hoek" "8.x.x"
+
"@hapi/hoek@6.x.x":
version "6.2.4"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-6.2.4.tgz#4b95fbaccbfba90185690890bdf1a2fbbda10595"
@@ -740,21 +747,22 @@
dependencies:
"@hapi/hoek" "8.x.x"
-"@ladjs/i18n@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@ladjs/i18n/-/i18n-1.1.0.tgz#50a20cbcd3f0f0d880be9dea873e3efec3b6b02c"
- integrity sha512-Kynr5osjApDCyiik35MMNZC1lgjgrk7fbV6P1qHXKQ67sR/U85Ddnv1NNPc/2s08PQVjvIBNY96UACb0CivrWg==
+"@ladjs/i18n@^1.2.0":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@ladjs/i18n/-/i18n-1.2.1.tgz#74478f3495e2f6b1684e58ff043c040954c6ff35"
+ integrity sha512-rlo8e+2UIylCo/KiZuxd/DJsyGZ1XMFFJaxxVXMj6BO2qyfjB91pjCpIQxUmpSWddWQlPKxsm85avr1o2RG9Uw==
dependencies:
- auto-bind "^2.0.0"
- boolean "^0.2.0"
- boom "7.3.0"
+ "@hapi/boom" "^7.4.3"
+ auto-bind "^2.1.0"
+ boolean "^1.0.0"
country-language "^0.1.7"
+ debug "^4.1.1"
i18n "^0.8.3"
i18n-locales "^0.0.2"
- lodash "^4.17.11"
- moment "^2.23.0"
- qs "^6.6.0"
- underscore.string "^3.3.5"
+ lodash "^4.17.15"
+ moment "^2.24.0"
+ qs "^6.8.0"
+ titleize "^2.1.0"
"@nuxt/babel-preset-app@2.9.2":
version "2.9.2"
@@ -997,30 +1005,20 @@
webpack-node-externals "^1.7.2"
webpackbar "^4.0.0"
-"@nuxtjs/auth@^4.8.2":
- version "4.8.2"
- resolved "https://registry.yarnpkg.com/@nuxtjs/auth/-/auth-4.8.2.tgz#0276fe3a4291b61ec0b7fd4328d43a118d47f603"
- integrity sha512-LG+71qTGxValqDyhG1Zou5YyJSMQtMq4MaXd0gXsFFYlsPDEyysYtidoAG+LhUsO9grmAwWTvcqkXO2d94LNUg==
+"@nuxtjs/auth@^4.8.3":
+ version "4.8.3"
+ resolved "https://registry.yarnpkg.com/@nuxtjs/auth/-/auth-4.8.3.tgz#037509e0dea0329c9dae7be4f743cd9ff701efa5"
+ integrity sha512-t9RsEH/IdEl+tzR3qOV6lQlXv0sqD4CTdtJnpseVL7lBn1f1cKGGyDXsWdhOWiIKeLu7tl9HFzKXfKCQTNKzgA==
dependencies:
- "@nuxtjs/axios" "^5.5.4"
+ "@nuxtjs/axios" "^5.6.0"
body-parser "^1.19.0"
- consola "^2.9.0"
+ consola "^2.10.1"
cookie "^0.4.0"
dotprop "^1.2.0"
is-https "^1.0.0"
- js-cookie "^2.2.0"
+ js-cookie "^2.2.1"
lodash "^4.17.15"
- nanoid "^2.0.3"
-
-"@nuxtjs/axios@^5.5.4":
- version "5.5.4"
- resolved "https://registry.yarnpkg.com/@nuxtjs/axios/-/axios-5.5.4.tgz#c4aee2322901b19d4072670c03144662a73ea6f4"
- integrity sha512-/Ljsyh5VIc9paXGrQue7RQ+PpBNES1oit0g4l+ya1tfyKnZMpHSbghuLcv0xq+BpXlSEr690uemHbz54/N6U5w==
- dependencies:
- "@nuxtjs/proxy" "^1.3.3"
- axios "^0.19.0"
- axios-retry "^3.1.2"
- consola "^2.7.1"
+ nanoid "^2.1.0"
"@nuxtjs/axios@^5.6.0":
version "5.6.0"
@@ -1063,10 +1061,10 @@
mustache "^2.3.0"
stack-trace "0.0.10"
-"@sindresorhus/is@^0.17.1":
- version "0.17.1"
- resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.17.1.tgz#453b27750f358689c4aa3c9f32d9ace1f0929a79"
- integrity sha512-kg/maAZD2Z2AHDFp7cY/ACokjUL0e7MaupTtGXkSW2SV4DJQEHdslFUioP0SMccotjwqTdI0b4XH/qZh6CN+kQ==
+"@sindresorhus/is@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-1.0.0.tgz#4f475ff3f32b0a309b7faffd33328e93d7953330"
+ integrity sha512-3rlOB53XCVO7LfjXFx4bCGrZPPjkgYD7pP0E/yo4d57H32aYqD/QNmeXcVnx7CM5SxGScwl2P0b1kCDYZgNWqw==
"@types/babel-types@*", "@types/babel-types@^7.0.0":
version "7.0.7"
@@ -1715,7 +1713,7 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
-auto-bind@^2.0.0, auto-bind@^2.1.0:
+auto-bind@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-2.1.0.tgz#254e12d53063d7cab90446ce021accfb3faa1464"
integrity sha512-qZuFvkes1eh9lB2mg8/HG18C+5GIO51r+RrCSst/lh+i5B1CtVlkhTE488M805Nr3dKl0sM/pIFKSKUIlg3zUg==
@@ -1933,7 +1931,7 @@ bl@^1.0.0:
readable-stream "^2.3.5"
safe-buffer "^5.1.1"
-bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
+bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.3, bluebird@^3.5.5:
version "3.5.5"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
@@ -1964,17 +1962,10 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
-boolean@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/boolean/-/boolean-0.2.0.tgz#808dff32ce1c87b828cc381428dc3b158d4f85cb"
- integrity sha512-mDcM3ChboDuhv4glLXEH1us7jMiWXRSs3R13Okoo+kkFOlLIjvF1y88507wTfDf9zsuv0YffSDFUwX95VAT/mg==
-
-boom@7.3.0:
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/boom/-/boom-7.3.0.tgz#733a6d956d33b0b1999da3fe6c12996950d017b9"
- integrity sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A==
- dependencies:
- hoek "6.x.x"
+boolean@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/boolean/-/boolean-1.0.0.tgz#45764b4aac187a050995b0a33d7579b6759f0dfd"
+ integrity sha512-IB1lgIywn37N9Aff8CciCblVpMUflgL42vyxPUH0IvaDdIi/QwBHKv1lq/HOkATHCfa7c4MbMYJ7Bo7hGuoI+w==
bootstrap@^4.3.1:
version "4.3.1"
@@ -2723,7 +2714,7 @@ consola@^2.10.0, consola@^2.10.1:
resolved "https://registry.yarnpkg.com/consola/-/consola-2.10.1.tgz#4693edba714677c878d520e4c7e4f69306b4b927"
integrity sha512-4sxpH6SGFYLADfUip4vuY65f/gEogrzJoniVhNUYkJHtng0l8ZjnDCqxxrSVRHOHwKxsy8Vm5ONZh1wOR3/l/w==
-consola@^2.5.6, consola@^2.6.0, consola@^2.7.1, consola@^2.9.0:
+consola@^2.5.6, consola@^2.6.0, consola@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/consola/-/consola-2.9.0.tgz#57760e3a65a53ec27337f4add31505802d902278"
integrity sha512-34Iue+LRcWbndFIfZc5boNizWlsrRjqIBJZTe591vImgbnq7nx2EzlrLtANj9TH2Fxm7puFJBJAOk5BhvZOddQ==
@@ -3569,21 +3560,21 @@ elliptic@^6.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
-email-templates@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/email-templates/-/email-templates-6.0.0.tgz#cc235d49c967f16a15488906dd796f748f2a7531"
- integrity sha512-NzneEyM+J/DpMY7hK4Ii1HBmiX/BTQyAf8OEZh1yU+O9uYMgnJr+JvpAxLkqRxeWeA0dT2IV5K+6UcF/jMJk7Q==
+email-templates@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/email-templates/-/email-templates-6.0.2.tgz#8e71d5a65b51de32a80f36a37bcd4fead0d5c70a"
+ integrity sha512-eRM3HM6KVDkKhnLTt8sTpg3kFqG/4E/PVNJi3PtYtfVX+LSnd399f3tvRu9XyMu8jVisX3fXAAkvwBIevkwkAA==
dependencies:
- "@ladjs/i18n" "^1.1.0"
- "@sindresorhus/is" "^0.17.1"
+ "@ladjs/i18n" "^1.2.0"
+ "@sindresorhus/is" "^1.0.0"
auto-bind "^2.1.0"
consolidate "^0.15.1"
debug "^4.1.1"
- get-paths "^0.0.4"
+ get-paths "^0.0.7"
html-to-text "^5.1.1"
juice "^5.2.0"
- lodash "^4.17.11"
- nodemailer "^6.2.1"
+ lodash "^4.17.15"
+ nodemailer "^6.3.0"
pify "^4.0.1"
preview-email "^1.0.1"
@@ -3741,10 +3732,10 @@ eslint-ast-utils@^1.0.0:
lodash.get "^4.4.2"
lodash.zip "^4.2.0"
-eslint-config-prettier@^6.2.0:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.2.0.tgz#80e0b8714e3f6868c4ac2a25fbf39c02e73527a7"
- integrity sha512-VLsgK/D+S/FEsda7Um1+N8FThec6LqE3vhcMyp8mlmto97y3fGf3DX7byJexGuOb1QY0Z/zz222U5t+xSfcZDQ==
+eslint-config-prettier@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.3.0.tgz#e73b48e59dc49d950843f3eb96d519e2248286a3"
+ integrity sha512-EWaGjlDAZRzVFveh2Jsglcere2KK5CJBhkNSa1xs3KfMUGdRiT7lG089eqPdvlzWHpAqaekubOsOMu8W8Yk71A==
dependencies:
get-stdin "^6.0.0"
@@ -4162,6 +4153,11 @@ express-jwt@^5.3.1:
jsonwebtoken "^8.1.0"
lodash.set "^4.0.0"
+express-middleware-log@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/express-middleware-log/-/express-middleware-log-1.2.0.tgz#62682021ba3b1cbfd6b081e7364ebb1fd6d5a0fb"
+ integrity sha512-1G9cHlGJs4+nFphSqVduJfCzeaqHeOdpTRBAjceRRcLWeHzj9sXDYP99tNjaeHsHn3N3vlNI+vIn/lb9eYXmuw==
+
express-unless@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-0.3.1.tgz#2557c146e75beb903e2d247f9b5ba01452696e20"
@@ -4527,15 +4523,6 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
-fs-extra@^4.0.2:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
- integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
- dependencies:
- graceful-fs "^4.1.2"
- jsonfile "^4.0.0"
- universalify "^0.1.0"
-
fs-extra@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
@@ -4618,13 +4605,12 @@ get-caller-file@^2.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-get-paths@^0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/get-paths/-/get-paths-0.0.4.tgz#0a053ca424bab976383ce49929528ad642e2a0df"
- integrity sha512-+AxlfMGN7FuJr2zhT6aErH08HMKkRwynTTHtWCenIWkIZgx2OlkZKgt7SM4+rh8Dfi32lo6HcvqeTLxph3kjQw==
+get-paths@^0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/get-paths/-/get-paths-0.0.7.tgz#15331086752077cf130166ccd233a1cdbeefcf38"
+ integrity sha512-0wdJt7C1XKQxuCgouqd+ZvLJ56FQixKoki9MrFaO4EriqzXOiH9gbukaDE1ou08S8Ns3/yDzoBAISNPqj6e6tA==
dependencies:
- bluebird "^3.5.1"
- fs-extra "^4.0.2"
+ pify "^4.0.1"
get-stdin@^6.0.0:
version "6.0.0"
@@ -4937,11 +4923,6 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoek@6.x.x:
- version "6.1.3"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
- integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
-
homedir-polyfill@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
@@ -5705,10 +5686,10 @@ js-beautify@^1.8.8:
mkdirp "~0.5.1"
nopt "~4.0.1"
-js-cookie@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb"
- integrity sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=
+js-cookie@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
+ integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
js-levenshtein@^1.1.3:
version "1.1.6"
@@ -6563,7 +6544,7 @@ moment-timezone@^0.5.21:
dependencies:
moment ">= 2.9.0"
-"moment@>= 2.9.0", moment@^2.23.0, moment@^2.24.0:
+"moment@>= 2.9.0", moment@^2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
@@ -6645,10 +6626,10 @@ nan@^2.12.1, nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
-nanoid@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.0.3.tgz#dde999e173bc9d7bd2ee2746b89909ade98e075e"
- integrity sha512-NbaoqdhIYmY6FXDRB4eYtDVC9Z9eCbn8TyaiC16LNKtpPv/aqa0tOPD8y6gNE4yUNnaZ7LLhYtXOev/6+cBtfw==
+nanoid@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.1.tgz#524fd4acd45c126e0c87cd43ab5ee8346e695df9"
+ integrity sha512-0YbJdaL4JFoejIOoawgLcYValFGJ2iyUuVDIWL3g8Es87SSOWFbWdRUMV3VMSiyPs3SQ3QxCIxFX00q5DLkMCw==
nanomatch@^1.2.9:
version "1.2.13"
@@ -6809,7 +6790,7 @@ node-res@^5.0.1:
on-finished "^2.3.0"
vary "^1.1.2"
-nodemailer@^6.2.1:
+nodemailer@^6.2.1, nodemailer@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.0.tgz#a89b0c62d3937bdcdeecbf55687bd7911b627e12"
integrity sha512-TEHBNBPHv7Ie/0o3HXnb7xrPSSQmH1dXwQKRaMKDBGt/ZN54lvDVujP6hKkO/vjkIYL9rK8kHSG11+G42Nhxuw==
@@ -8502,11 +8483,16 @@ q@^1.1.2:
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
-qs@6.7.0, qs@^6.6.0:
+qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
+qs@^6.8.0:
+ version "6.8.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081"
+ integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w==
+
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -9098,10 +9084,10 @@ sequelize-pool@^2.3.0:
resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-2.3.0.tgz#64f1fe8744228172c474f530604b6133be64993d"
integrity sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==
-sequelize@^5.18.1:
- version "5.18.1"
- resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.18.1.tgz#31d5246dcdf01d0ac317454c28fb598359d5b60a"
- integrity sha512-jngo7pqilyOycMv6ZEwHLVn2wuHi/xkSQZfwK4jhjG8ta1HWYJK3XyQDFdhVEOH1GEq9pnqaf+7Kwqm+eqXD9Q==
+sequelize@^5.18.4:
+ version "5.18.4"
+ resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.18.4.tgz#1e2c7eabe4c554fa257a0115fad39f271bd56150"
+ integrity sha512-bBmJqpO1H8Z7L0xzITqVo5KHXFI7GmKfGl/5SIPDKsuUMbuZT98s+gyGeaLXpOWGH1ZUO79hvJ8z74vNcxBWHg==
dependencies:
bluebird "^3.5.0"
cls-bluebird "^2.1.0"
@@ -9415,7 +9401,7 @@ split@^1.0.0:
dependencies:
through "2"
-sprintf-js@>=1.0.3, sprintf-js@^1.0.3:
+sprintf-js@>=1.0.3:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
@@ -9863,6 +9849,11 @@ tiny-emitter@^2.0.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
+titleize@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/titleize/-/titleize-2.1.0.tgz#5530de07c22147a0488887172b5bd94f5b30a48f"
+ integrity sha512-m+apkYlfiQTKLW+sI4vqUkwMEzfgEUEYSqljx1voUE3Wz/z1ZsxyzSxvH2X8uKVrOp7QkByWt0rA6+gvhCKy6g==
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -10092,14 +10083,6 @@ underscore.deep@~0.5.1:
resolved "https://registry.yarnpkg.com/underscore.deep/-/underscore.deep-0.5.1.tgz#072671f48d68735c34223fcfef63e69e5276cc2b"
integrity sha1-ByZx9I1oc1w0Ij/P72PmnlJ2zCs=
-underscore.string@^3.3.5:
- version "3.3.5"
- resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023"
- integrity sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==
- dependencies:
- sprintf-js "^1.0.3"
- util-deprecate "^1.0.2"
-
underscore@~1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
@@ -10269,7 +10252,7 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
-util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=