diff --git a/app/api.js b/app/api.js
index d4e793cc..d6e0c6f2 100644
--- a/app/api.js
+++ b/app/api.js
@@ -16,6 +16,9 @@ const api = express.Router()
// login
api.post('/login', userController.login)
+api.post('/user/recover', userController.forgotPassword)
+api.post('/user/check_recover_code', userController.checkRecoverCode)
+api.post('/user/recover_password', userController.updatePasswordWithRecoverCode)
api.route('/user')
// register
@@ -51,7 +54,7 @@ api.get('/event/unconfirmed', isAuth, isAdmin, eventController.getUnconfirmed)
// add event notification
api.post('/event/notification', eventController.addNotification)
-api.delete('/event/del_notification/:code', eventController.delNotification)
+api.delete('/event/notification/:code', eventController.delNotification)
api.get('/settings', settingsController.getAdminSettings)
api.post('/settings', settingsController.setAdminSetting)
diff --git a/app/config.js b/app/config.js
index a09c9615..7fd55154 100644
--- a/app/config.js
+++ b/app/config.js
@@ -1,32 +1,13 @@
/* backend configuration */
-let db = {}
-let apiurl
-
-if (process.env.NODE_ENV === 'production') {
- db = {
- host: process.env.DB_HOST,
- username: process.env.DB_USER,
- password: process.env.DB_PASS,
- database: process.env.DB_NAME,
- dialect: 'postgres'
- }
- apiurl = process.env.BASE_URL + '/api'
-} else {
- db = {
- dialect: 'sqlite',
- storage: './db.sqlite'
- }
- apiurl = 'http://localhost:9000'
-}
+const env = process.env.NODE_ENV || 'development'
+const db = require('./config/config.json')[env]
module.exports = {
- locale: 'it',
-
+ locale: process.env.LOCALE || 'it',
title: process.env.TITLE || 'GANCIO',
description: process.env.DESCRIPTION || 'A calendar for radical communities',
-
baseurl: process.env.BASE_URL || 'http://localhost:8080',
- apiurl,
+ apiurl: env === 'production' ? process.env.BASE_URL + '/api' : 'http://localhost:9000',
db,
admin: process.env.ADMIN_EMAIL,
diff --git a/app/config/config.json b/app/config/config.json
new file mode 100644
index 00000000..5a68a3e1
--- /dev/null
+++ b/app/config/config.json
@@ -0,0 +1,21 @@
+{
+ "development": {
+ "storage": "/home/les/dev/hacklab/eventi/db.sqlite",
+ "dialect": "sqlite",
+ "logging": false
+ },
+ "test": {
+ "username": "root",
+ "password": null,
+ "database": "database_test",
+ "host": "127.0.0.1",
+ "dialect": "mysql"
+ },
+ "production": {
+ "username": "root",
+ "password": null,
+ "database": "database_production",
+ "host": "127.0.0.1",
+ "dialect": "mysql"
+ }
+}
diff --git a/app/controller/event.js b/app/controller/event.js
index 933118ce..66d187b1 100644
--- a/app/controller/event.js
+++ b/app/controller/event.js
@@ -111,9 +111,12 @@ const eventController = {
async addNotification (req, res) {
try {
- const notification = req.body
- notification.remove_code = crypto.randomBytes(16).toString('hex')
- await Notification.create(req.body)
+ const notification = {
+ email: req.body.email,
+ type: 'mail',
+ remove_code: crypto.randomBytes(16).toString('hex')
+ }
+ await Notification.create(notification)
res.sendStatus(200)
} catch (e) {
res.sendStatus(404)
@@ -126,7 +129,7 @@ const eventController = {
const notification = await Notification.findOne({ where: { remove_code: { [Op.eq]: remove_code } } })
await notification.destroy()
} catch (e) {
- return res.send('Error')
+ return res.status(404).send('Error')
}
res.send('Ok, notification removed')
},
diff --git a/app/controller/export.js b/app/controller/export.js
index 00ece085..3f54ecf2 100644
--- a/app/controller/export.js
+++ b/app/controller/export.js
@@ -21,8 +21,7 @@ const exportController = {
wherePlace.name = places.split(',')
}
const events = await Event.findAll({
- // order: [['start_datetime', 'ASC']],
- where: { start_datetime: { [Op.gte]: yesterday } },
+ where: { is_visible: true, start_datetime: { [Op.gte]: yesterday } },
include: [Comment, {
model: Tag,
where: whereTag
diff --git a/app/controller/user.js b/app/controller/user.js
index 19d396e1..466dc9dd 100644
--- a/app/controller/user.js
+++ b/app/controller/user.js
@@ -10,6 +10,7 @@ const mail = require('../mail')
const { Op } = require('sequelize')
const fs = require('fs')
const path = require('path')
+const crypto = require('crypto')
const userController = {
async login (req, res) {
@@ -201,6 +202,36 @@ const userController = {
}
},
+ 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)
+
+ user.recover_code = crypto.randomBytes(16).toString('hex')
+ mail.send(user.email, 'recover', { user, config })
+ await user.save()
+ res.sendStatus(200)
+ },
+
+ async checkRecoverCode (req, res) {
+ const recover_code = req.body.recover_code
+ 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)
+ res.json(user)
+ },
+
+ async updatePasswordWithRecoverCode (req, res) {
+ const recover_code = req.body.recover_code
+ if (!recover_code) return res.sendStatus(400)
+ const password = req.body.password
+ const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } })
+ if (!user) return res.sendStatus(400)
+ user.password = password
+ await user.save()
+ res.sendStatus(200)
+ },
+
async current (req, res) {
res.json(req.user)
},
@@ -236,7 +267,7 @@ const userController = {
}
const user = await User.create(req.body)
try {
- mail.send(user.email, 'register', { user })
+ mail.send([user.email, config.admin], 'register', { user, config })
} catch (e) {
return res.status(400).json(e)
}
diff --git a/app/cron.js b/app/cron.js
index c0d27abe..3259beff 100644
--- a/app/cron.js
+++ b/app/cron.js
@@ -1,6 +1,7 @@
const mail = require('./mail')
const bot = require('./controller/bot')
const settingsController = require('./controller/settings')
+const config = require('./config.js')
const { Event, Notification, EventNotification,
User, Place, Tag } = require('./model')
@@ -8,36 +9,28 @@ let settings
async function sendNotification (notification, event, eventNotification) {
const promises = []
- try {
- switch (notification.type) {
- case 'mail':
- const p = mail.send(notification.email, 'event', { event })
- promises.push(p)
- break
- case 'admin_email':
- const admins = await User.findAll({ where: { is_admin: true } })
- promises.push(admins.map(admin =>
- mail.send(admin.email, 'event', { event, to_confirm: true, notification })))
- break
- case 'mastodon':
- // instance publish
- if (settings.mastodon_auth.instance && settings.mastodon_auth.access_token) {
- const b = bot.post(settings.mastodon_auth, event)
- promises.push(b)
- }
- // user publish
- if (event.user && event.user.mastodon_auth && event.user.mastodon_auth.access_token) {
- const b = bot.post(event.user.mastodon_auth, event).then(ret => {
- event.activitypub_id = ret.id
- return event.save()
- })
- promises.push(b)
- }
- break
- }
- } catch (e) {
- console.log('CATCH!', e)
- return false
+ switch (notification.type) {
+ case 'mail':
+ return mail.send(notification.email, 'event', { event, config, notification })
+ case 'admin_email':
+ const admins = await User.findAll({ where: { is_admin: true } })
+ const admin_emails = admins.map(admin => admin.email)
+ return mail.send(admin_emails, 'event', { event, to_confirm: true, notification })
+ case 'mastodon':
+ // instance publish
+ if (settings.mastodon_auth.instance && settings.mastodon_auth.access_token) {
+ const b = bot.post(settings.mastodon_auth, event)
+ promises.push(b)
+ }
+ // user publish
+ if (event.user && event.user.mastodon_auth && event.user.mastodon_auth.access_token) {
+ const b = bot.post(event.user.mastodon_auth, event).then(ret => {
+ event.activitypub_id = ret.id
+ return event.save()
+ })
+ promises.push(b)
+ }
+ break
}
return Promise.all(promises)
}
@@ -54,8 +47,8 @@ async function loop () {
await sendNotification(notification, event, e)
e.status = 'sent'
return e.save()
- } catch (e) {
- console.error(e)
+ } catch (err) {
+ console.error(err)
e.status = 'error'
return e.save()
}
diff --git a/app/db.js b/app/db.js
index 3d526686..16479cdb 100644
--- a/app/db.js
+++ b/app/db.js
@@ -1,8 +1,4 @@
const Sequelize = require('sequelize')
const conf = require('./config.js')
const db = new Sequelize(conf.db)
-
-// db.sync({ force: true })
-// db.sync()
-
module.exports = db
diff --git a/app/emails/event/html.pug b/app/emails/event/html.pug
index 72b0151d..dfaa099f 100644
--- a/app/emails/event/html.pug
+++ b/app/emails/event/html.pug
@@ -13,6 +13,6 @@ br
hr
if to_confirm
p Puoi confermare questo evento qui
-//- else
- p Puoi eliminare queste notifiche qui
+else
+ p Puoi eliminare queste notifiche qui
#{config.title} - #{config.description}
diff --git a/app/emails/recover/html.pug b/app/emails/recover/html.pug
new file mode 100644
index 00000000..fd8cb11a
--- /dev/null
+++ b/app/emails/recover/html.pug
@@ -0,0 +1,3 @@
+p= t('recover_email')
+
+#{t('press here')}
\ No newline at end of file
diff --git a/app/emails/recover/subject.pug b/app/emails/recover/subject.pug
new file mode 100644
index 00000000..6d0067a6
--- /dev/null
+++ b/app/emails/recover/subject.pug
@@ -0,0 +1 @@
+= `[Gancio] Richiesta password recovery`
diff --git a/app/emails/register/html.pug b/app/emails/register/html.pug
index 87d894b7..de38b8ed 100644
--- a/app/emails/register/html.pug
+++ b/app/emails/register/html.pug
@@ -1,4 +1,6 @@
p= t('registration_email')
-hr
+---
+small #{config.title}
+br
small #{config.baseurl}
\ No newline at end of file
diff --git a/app/migrations/20190316230454-notification_status.js b/app/migrations/20190316230454-notification_status.js
index 789275c5..1d689aca 100644
--- a/app/migrations/20190316230454-notification_status.js
+++ b/app/migrations/20190316230454-notification_status.js
@@ -1,26 +1,10 @@
-'use strict';
-
module.exports = {
up: (queryInterface, Sequelize) => {
- /*
- Add altering commands here.
- Return a promise to correctly handle asynchronicity.
-
- Example:
- return queryInterface.createTable('users', { id: Sequelize.INTEGER });
- */
- return queryInterface.addColumn('EventNotifications', 'status',
- { type: Sequelize.ENUM, values: ['new', 'sent', 'error'], index: true, defaultValue: 'new' })
+ // return queryInterface.addColumn('EventNotifications', 'status',
+ // { type: Sequelize.ENUM, values: ['new', 'sent', 'error'], index: true, defaultValue: 'new' })
},
down: (queryInterface, Sequelize) => {
- /*
- Add reverting commands here.
- Return a promise to correctly handle asynchronicity.
-
- Example:
- return queryInterface.dropTable('users');
- */
return queryInterface.removeColumn('EventNotifications', 'status')
}
-};
+}
diff --git a/app/migrations/20190321163240-recover_code.js b/app/migrations/20190321163240-recover_code.js
new file mode 100644
index 00000000..54aab425
--- /dev/null
+++ b/app/migrations/20190321163240-recover_code.js
@@ -0,0 +1,10 @@
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.addColumn('users', 'recover_code',
+ { type: Sequelize.STRING })
+ },
+
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.removeColumn('users', 'recover_code')
+ }
+}
diff --git a/app/models/event.js b/app/models/event.js
index ed894783..c0e3de2b 100644
--- a/app/models/event.js
+++ b/app/models/event.js
@@ -34,9 +34,6 @@ const Notification = db.define('notification', {
}
})
-Notification.findOrCreate({ where: { type: 'mastodon', filters: { is_visible: true } } })
-Notification.findOrCreate({ where: { type: 'admin_email', filters: { is_visible: false } } })
-
const Place = db.define('place', {
name: { type: Sequelize.STRING, unique: true, index: true },
address: { type: Sequelize.STRING }
@@ -66,4 +63,10 @@ Event.belongsTo(Place)
User.hasMany(Event)
Place.hasMany(Event)
+async function init () {
+ await Notification.findOrCreate({ where: { type: 'mastodon', filters: { is_visible: true } } })
+ await Notification.findOrCreate({ where: { type: 'admin_email', filters: { is_visible: false } } })
+}
+
+init()
module.exports = { Event, Comment, Tag, Place, Notification, EventNotification }
diff --git a/app/models/user.js b/app/models/user.js
index 3af9a95a..2fcfbac5 100644
--- a/app/models/user.js
+++ b/app/models/user.js
@@ -11,6 +11,7 @@ const User = db.define('user', {
},
description: Sequelize.TEXT,
password: Sequelize.STRING,
+ recover_code: Sequelize.STRING,
is_admin: Sequelize.BOOLEAN,
is_active: Sequelize.BOOLEAN,
mastodon_auth: Sequelize.JSON
diff --git a/app/server.js b/app/server.js
index 7d75173b..a6340b1b 100644
--- a/app/server.js
+++ b/app/server.js
@@ -4,9 +4,12 @@ const bodyParser = require('body-parser')
const api = require('./api')
const cors = require('cors')
const path = require('path')
+const morgan = require('morgan')
const config = require('./config')
+const db = require('./db')
const port = process.env.PORT || 9000
+app.use(morgan('dev'))
app.set('views', path.join(__dirname, 'views'))
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
@@ -19,5 +22,7 @@ app.use('/css', express.static(path.join(__dirname, '..', 'client', 'dist', 'css
app.use('/js', express.static(path.join(__dirname, '..', 'client', 'dist', 'js')))
app.use('*', express.static(path.join(__dirname, '..', 'client', 'dist', 'index.html')))
-app.listen(port)
-console.log(`[${config.title}] Started ${process.env.NODE_ENV} mode at ${config.baseurl} (api @ ${config.apiurl})`, config)
+app.listen(port, () => {
+ console.log(`[${config.title}] Started ${process.env.NODE_ENV} mode at ${config.baseurl} (api @ ${config.apiurl})\n\nCONFIG`, config)
+ db.sync()
+})
diff --git a/app/views/feed/rss.pug b/app/views/feed/rss.pug
index 8a0ed94c..ae57d6f7 100644
--- a/app/views/feed/rss.pug
+++ b/app/views/feed/rss.pug
@@ -16,8 +16,8 @@ rss(version='2.0', xmlns:atom='
- |
+ | #{moment(event.start_datetime).format("dddd, D MMMM HH:mm")}
+ |
|
!{event.description}
| ]]>
pubDate= new Date(event.createdAt).toUTCString()
diff --git a/client/src/api.js b/client/src/api.js
index 18718402..de4deb0c 100644
--- a/client/src/api.js
+++ b/client/src/api.js
@@ -48,6 +48,11 @@ export default {
login: (email, password) => post('/login', { email, password }),
register: user => post('/user', user),
+ // password recovery
+ forgotPassword: email => post('/user/recover', { email }),
+ checkRecoverCode: recover_code => post('/user/check_recover_code', { recover_code }),
+ recoverPassword: (recover_code, password) => post('/user/recover_password', { recover_code, password }),
+
getAllEvents: (month, year) => get(`/event/${year}/${month}/`),
getUnconfirmedEvents: () => get('/event/unconfirmed'),
@@ -55,6 +60,7 @@ export default {
unconfirmEvent: id => get(`/event/unconfirm/${id}`),
addNotification: notification => post('/event/notification', notification),
+ delNotification: code => del(`/event/notification/${code}`),
addEvent: event => post('/user/event', event),
updateEvent: event => put('/user/event', event),
diff --git a/client/src/assets/main.css b/client/src/assets/main.css
index 9badc857..e36e9153 100644
--- a/client/src/assets/main.css
+++ b/client/src/assets/main.css
@@ -26,5 +26,5 @@ pre {
}
a, a:hover {
- color: orange;
+ color: red;
}
\ No newline at end of file
diff --git a/client/src/components/About.vue b/client/src/components/About.vue
index 991697fc..81805c9e 100644
--- a/client/src/components/About.vue
+++ b/client/src/components/About.vue
@@ -12,9 +12,9 @@
Dentro gancio puoi trovare e inserire eventi.
Gancio, come tutto cisti.org è uno strumento
antisessista, antirazzista, antifascista e anticapitalista, riflettici quando
- stai pubblicando un evento.
+ pubblichi un evento.
- h5 Ok, ma cosa vuol dire?
+ h5 Ok, ma cosa vuol dire gancio?
blockquote.
Se vieni a Torino e dici: "ehi, ci diamo un gancio alle 8?" nessuno si presenterà con i guantoni per fare a mazzate.
Darsi un gancio vuol dire beccarsi alle ore X in un posto Y
diff --git a/client/src/components/DelNotification.vue b/client/src/components/DelNotification.vue
new file mode 100644
index 00000000..4869df83
--- /dev/null
+++ b/client/src/components/DelNotification.vue
@@ -0,0 +1,39 @@
+
+ b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Remove notification")'
+ :visible='true' size='sm' ref='modal')
+ div( v-loading='loading')
+ p Vuoi eliminare le notifiche?
+ el-button.float-right(type='success' @click='remove') {{$t('Yes')}}
+ el-button.float-right.mr-1(type='danger' @click='$refs.modal.hide()') {{$t('No')}}
+
+
+
diff --git a/client/src/components/Event.vue b/client/src/components/Event.vue
index 482bef36..035a5eb7 100644
--- a/client/src/components/Event.vue
+++ b/client/src/components/Event.vue
@@ -14,8 +14,6 @@ import { mapState, mapActions } from 'vuex';
import api from '@/api'
import filters from '@/filters'
-console.log(process.env)
-
export default {
props: ['event'],
computed: {
diff --git a/client/src/components/Export.vue b/client/src/components/Export.vue
index f5f86628..20b9391c 100644
--- a/client/src/components/Export.vue
+++ b/client/src/components/Export.vue
@@ -11,12 +11,12 @@
el-tab-pane.pt-1(label='email' name='email')
p(v-html='$t(`export_email_explanation`)')
- b-form
+ el-form(@submit.native.prevent='add_notification')
//- 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-input.mt-2(v-model='notification.email' :placeholder="$t('Insert your address')")
- el-button.mt-2.float-right(type='success' @click='add_notification') {{$t('Send')}}
+ el-input.mt-2(v-model='notification.email' :placeholder="$t('Insert your address')" ref='email')
+ el-button.mt-2.float-right(native-type= 'submit' type='success' @click='add_notification') {{$t('Send')}}
el-tab-pane.pt-1(label='feed rss' name='feed')
span(v-html='$t(`export_feed_explanation`)')
@@ -59,6 +59,7 @@ import filters from '../filters'
import Calendar from '@/components/Calendar'
import {intersection} from 'lodash'
import api from '@/api'
+import { Message } from 'element-ui'
export default {
name: 'Export',
@@ -83,8 +84,13 @@ export default {
},
methods: {
async add_notification () {
+ if (!this.notification.email){
+ Message({message:'Inserisci una mail', 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'), type: 'success'})
},
loadLink () {
const tags = this.filters.tags.join(',')
diff --git a/client/src/components/Login.vue b/client/src/components/Login.vue
index 4dbfd2a0..5aca03f7 100644
--- a/client/src/components/Login.vue
+++ b/client/src/components/Login.vue
@@ -1,15 +1,16 @@
b-modal(@shown="$refs.email.focus()" :title='$t("Login")' hide-footer
@hidden='$router.replace("/")' :visible='true' ref='modal')
- el-form
- p {{$t('login_explanation')}}
+ el-form(v-loading='loading')
+ p(v-html="$t('login_explanation')")
el-input.mb-2(v-model='email' type='email' :placeholder='$t("Email")' autocomplete='email' ref='email')
v-icon(name='user' slot='prepend')
- el-input.mb-2(v-model='password' type='password' :placeholder='$t("Password")')
+ el-input.mb-1(v-model='password' @keyup.enter.native="submit" type='password' :placeholder='$t("Password")')
v-icon(name="lock" slot='prepend')
- el-button(plain type="success" icon='el-icon-arrow-right' @click='submit') {{$t('Login')}}
- router-link.float-right(to='/register')
- a {{$t('Not registered?')}}
+ el-button.mr-1(plain type="success" @click='submit') {{$t('Login')}}
+ router-link(to='/register')
+ el-button.mt-1(plain type="primary") {{$t('Not registered?')}}
+ a.float-right(href='#' @click='forgot') {{$t('Forgot password?')}}
diff --git a/client/src/components/newEvent.vue b/client/src/components/newEvent.vue
index b06d497d..f626b83f 100644
--- a/client/src/components/newEvent.vue
+++ b/client/src/components/newEvent.vue
@@ -1,8 +1,8 @@
b-modal(ref='modal' @hidden='$router.replace("/")' size='lg' :visible='true'
:title="edit?$t('Edit event'):$t('New event')" hide-footer)
- b-container
- el-tabs.mb-2(v-model='activeTab' v-loading='sending' @tab-click.native='changeTab')
+ el-form
+ el-tabs.mb-2(v-model='activeTab' v-loading='sending')
//- NOT LOGGED EVENT
el-tab-pane(v-if='!logged')
@@ -36,12 +36,13 @@
:picker-options="{start: '00:00', step: '00:30', end: '24:00'}")
el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}}
+ //- WHAT
el-tab-pane
span(slot='label') {{$t('What')}}