diff --git a/app/api.js b/app/api.js
index 261f7052..d4e793cc 100644
--- a/app/api.js
+++ b/app/api.js
@@ -61,6 +61,7 @@ api.get('/event/:event_id', eventController.get)
// confirm event
api.get('/event/confirm/:event_id', isAuth, isAdmin, eventController.confirm)
+api.get('/event/unconfirm/:event_id', isAuth, isAdmin, eventController.unconfirm)
// export events (rss/ics)
api.get('/export/:type', exportController.export)
diff --git a/app/controller/event.js b/app/controller/event.js
index bb9f8d57..d7d9f2a4 100644
--- a/app/controller/event.js
+++ b/app/controller/event.js
@@ -82,6 +82,18 @@ const eventController = {
}
},
+ async unconfirm (req, res) {
+ const id = req.params.event_id
+ const event = await Event.findByPk(id)
+
+ try {
+ await event.update({ is_visible: false })
+ res.send(200)
+ } catch (e) {
+ res.send(404)
+ }
+ },
+
async getUnconfirmed (req, res) {
const events = await Event.findAll({
where: {
diff --git a/app/controller/user.js b/app/controller/user.js
index bbda197f..ca4cc343 100644
--- a/app/controller/user.js
+++ b/app/controller/user.js
@@ -198,6 +198,9 @@ const userController = {
async update (req, res) {
const user = await User.findByPk(req.body.id)
if (user) {
+ if (!user.is_active && req.body.is_active) {
+ await mail.send(user.email, 'confirm', { user, config })
+ }
await user.update(req.body)
res.json(user)
} else {
@@ -209,7 +212,7 @@ const userController = {
const n_users = await User.count()
try {
if (n_users === 0) {
- // admin will be the first registered user
+ // the first registered user will be an active admin
req.body.is_active = req.body.is_admin = true
} else {
req.body.is_active = false
diff --git a/app/cron.js b/app/cron.js
index ff3f4dd0..77656bbc 100644
--- a/app/cron.js
+++ b/app/cron.js
@@ -14,7 +14,7 @@ async function sendNotification (notification, event, eventNotification) {
const p = mail.send(notification.email, 'event', { event })
promises.push(p)
break
- case 'mail_admin':
+ 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 })))
@@ -26,7 +26,7 @@ async function sendNotification (notification, event, eventNotification) {
promises.push(b)
}
// user publish
- if (event.user && event.user.mastodon_auth) {
+ 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()
@@ -45,17 +45,24 @@ async function sendNotification (notification, event, eventNotification) {
async function loop () {
settings = await settingsController.settings()
// get all event notification in queue
- const eventNotifications = await EventNotification.findAll()
+ 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
const notification = await Notification.findByPk(e.notificationId)
- await sendNotification(notification, event, e)
- e.destroy()
+ try {
+ await sendNotification(notification, event, e)
+ e.status = 'sent'
+ e.save()
+ } catch (e) {
+ console.error(e)
+ e.status = 'error'
+ return e.save()
+ }
})
return Promise.all(promises)
}
-setInterval(loop, 20000)
+setInterval(loop, 260000)
loop()
diff --git a/app/emails/confirm/html.pug b/app/emails/confirm/html.pug
new file mode 100644
index 00000000..e0f26aa1
--- /dev/null
+++ b/app/emails/confirm/html.pug
@@ -0,0 +1,4 @@
+p= t('confirm_email')
+
+hr
+small #{config.baseurl}
diff --git a/app/emails/register/html.pug b/app/emails/register/html.pug
index fca72152..87d894b7 100644
--- a/app/emails/register/html.pug
+++ b/app/emails/register/html.pug
@@ -1,6 +1,4 @@
-h4 Gancio
-
p= t('registration_email')
-small --
-small https://cisti.org
\ No newline at end of file
+hr
+small #{config.baseurl}
\ No newline at end of file
diff --git a/app/locales/en.json b/app/locales/en.json
deleted file mode 100644
index f31adcfc..00000000
--- a/app/locales/en.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
-}
diff --git a/app/locales/es.json b/app/locales/es.json
deleted file mode 100644
index 9cd985b3..00000000
--- a/app/locales/es.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "registration_email": "registration_email"
-}
diff --git a/app/locales/it.json b/app/locales/it.json
deleted file mode 100644
index f31adcfc..00000000
--- a/app/locales/it.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
-}
diff --git a/app/locales/zh.json b/app/locales/zh.json
deleted file mode 100644
index f01ae3b9..00000000
--- a/app/locales/zh.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "registration_email": "registration_email"
-}
\ No newline at end of file
diff --git a/app/migrations/20190316230454-notification_status.js b/app/migrations/20190316230454-notification_status.js
new file mode 100644
index 00000000..789275c5
--- /dev/null
+++ b/app/migrations/20190316230454-notification_status.js
@@ -0,0 +1,26 @@
+'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' })
+ },
+
+ 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/models/event.js b/app/models/event.js
index 6646e4c3..80ea88d8 100644
--- a/app/models/event.js
+++ b/app/models/event.js
@@ -48,7 +48,15 @@ Event.hasMany(Comment)
Event.belongsToMany(Tag, { through: 'tagEvent' })
Tag.belongsToMany(Event, { through: 'tagEvent' })
-const EventNotification = db.define('EventNotification')
+const EventNotification = db.define('EventNotification', {
+ status: {
+ type: Sequelize.ENUM,
+ values: ['new', 'sent', 'error'],
+ defaultValue: 'new',
+ index: true
+ }
+})
+
Event.belongsToMany(Notification, { through: EventNotification })
Notification.belongsToMany(Event, { through: EventNotification })
diff --git a/app/models/index.js b/app/models/index.js
new file mode 100644
index 00000000..c1a3d6d5
--- /dev/null
+++ b/app/models/index.js
@@ -0,0 +1,37 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const Sequelize = require('sequelize');
+const basename = path.basename(__filename);
+const env = process.env.NODE_ENV || 'development';
+const config = require(__dirname + '/../config/config.json')[env];
+const db = {};
+
+let sequelize;
+if (config.use_env_variable) {
+ sequelize = new Sequelize(process.env[config.use_env_variable], config);
+} else {
+ sequelize = new Sequelize(config.database, config.username, config.password, config);
+}
+
+fs
+ .readdirSync(__dirname)
+ .filter(file => {
+ return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
+ })
+ .forEach(file => {
+ 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;
diff --git a/client/.eslintrc.js b/client/.eslintrc.js
index 2f16b25f..d410c471 100644
--- a/client/.eslintrc.js
+++ b/client/.eslintrc.js
@@ -1,3 +1,6 @@
module.exports = {
- 'extends': 'standard'
+ 'extends': 'standard',
+ "rules": {
+ "camelcase": 0
+ }
}
diff --git a/client/src/api.js b/client/src/api.js
index d2908963..18718402 100644
--- a/client/src/api.js
+++ b/client/src/api.js
@@ -18,6 +18,8 @@ function get (path) {
store.commit('logout')
return false
}
+ throw e.response && e.response.data &&
+ e.response.data.errors && e.response.data.errors[0].message
})
}
@@ -29,6 +31,8 @@ function post (path, data) {
store.commit('logout')
return false
}
+ throw e.response && e.response.data &&
+ e.response.data.errors && e.response.data.errors[0].message
})
}
function put (path, data) {
@@ -43,12 +47,18 @@ function del (path) {
export default {
login: (email, password) => post('/login', { email, password }),
register: user => post('/user', user),
+
getAllEvents: (month, year) => get(`/event/${year}/${month}/`),
getUnconfirmedEvents: () => get('/event/unconfirmed'),
+
confirmEvent: id => get(`/event/confirm/${id}`),
+ unconfirmEvent: id => get(`/event/unconfirm/${id}`),
+
addNotification: notification => post('/event/notification', notification),
+
addEvent: event => post('/user/event', event),
updateEvent: event => put('/user/event', event),
+
updatePlace: place => put('/place', place),
delEvent: eventId => del(`/user/event/${eventId}`),
getEvent: eventId => get(`/event/${eventId}`),
@@ -59,8 +69,6 @@ export default {
updateUser: user => put('/user', user),
getAuthURL: mastodonInstance => post('/user/getauthurl', mastodonInstance),
setCode: code => post('/user/code', code),
- getKnowLocations: () => get('/locations'),
- getKnowTags: () => get('/tags'),
getAdminSettings: () => get('/settings')
// setAdminSetting: (key, value) => post('/settings', { key, value })
}
diff --git a/client/src/components/About.vue b/client/src/components/About.vue
new file mode 100644
index 00000000..e5b5bdde
--- /dev/null
+++ b/client/src/components/About.vue
@@ -0,0 +1,25 @@
+
+ b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("About")'
+ :visible='true' size='lg')
+ h5 Chi siamo
+ p.
+ Gancio e' un progetto dell'underscore hacklab e uno dei
+ servizi di cisti.org.
+
+ h5 Ok, ma cosa vuol dire?
+ 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
+ p
+ small A: a che ora è il gancio in radio per andare al presidio?
+ p
+ small B: non so ma domani non posso venire, ho gia' un gancio per caricare il bar.
+
+
+ h5 Contatti
+ p.
+ Hai scritto una nuova interfaccia per gancio? Vuoi aprire un nuovo nodo di gancio nella tua città?
+ C'è qualcosa che vorresti migliorare? Aiuti e suggerimenti sono sempre benvenuti, puoi scriverci
+ su underscore chicciola autistici.org
+
+
\ No newline at end of file
diff --git a/client/src/components/Admin.vue b/client/src/components/Admin.vue
index 75c7596b..0acc1f1e 100644
--- a/client/src/components/Admin.vue
+++ b/client/src/components/Admin.vue
@@ -1,16 +1,26 @@
- b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Admin")' :visible='true' size='lg')
+ b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Admin")'
+ :visible='true' size='lg')
el-tabs(tabPosition='left' v-model='tab')
+
//- USERS
el-tab-pane.pt-1
template(slot='label')
v-icon(name='users')
span.ml-1 {{$t('Users')}}
- b-table(:items='users' :fields='userFields' striped small hover
- :per-page='5' :current-page='userPage')
- template(slot='action' slot-scope='data')
- el-button.mr-1(size='mini' :type='data.item.is_active?"warning":"success"' @click='toggle(data.item)') {{data.item.is_active?$t('Deactivate'):$t('Activate')}}
- el-button(size='mini' :type='data.item.is_admin?"danger":"warning"' @click='toggleAdmin(data.item)') {{data.item.is_admin?$t('Remove Admin'):$t('Admin')}}
+ el-table(:data='paginatedUsers' small)
+ el-table-column(label='Email')
+ template(slot-scope='data')
+ el-popover(trigger='hover' :content='data.row.description' width='400')
+ span(slot='reference') {{data.row.email}}
+ el-table-column(label='Azioni')
+ template(slot-scope='data')
+ el-button.mr-1(size='mini'
+ :type='data.row.is_active?"warning":"success"'
+ @click='toggle(data.row)') {{data.row.is_active?$t('Deactivate'):$t('Activate')}}
+ el-button(size='mini'
+ :type='data.row.is_admin?"danger":"warning"'
+ @click='toggleAdmin(data.row)') {{data.row.is_admin?$t('Remove Admin'):$t('Admin')}}
el-pagination(:page-size='perPage' :currentPage.sync='userPage' :total='users.length')
//- PLACES
@@ -124,8 +134,12 @@ export default {
this.eventPage * this.perPage)
},
paginatedTags () {
- return this.tags.slice((this.tagPage-1) * this.perPage,
+ return this.tags.slice((this.tagPage-1) * this.perPage,
this.tagPage * this.perPage)
+ },
+ paginatedUsers () {
+ return this.users.slice((this.userPage-1) * this.perPage,
+ this.userPage * this.perPage)
}
},
methods: {
diff --git a/client/src/components/EventDetail.vue b/client/src/components/EventDetail.vue
index da0c8492..ff2a0ff0 100644
--- a/client/src/components/EventDetail.vue
+++ b/client/src/components/EventDetail.vue
@@ -18,6 +18,8 @@
size='mini' :key='tag.tag') {{tag.tag}}
.ml-auto(v-if='mine')
hr
+ el-button(v-if='event.is_visible' plain type='warning' @click.prevents='toggle' icon='el-icon-remove') {{$t('Unconfirm')}}
+ el-button(v-else plain type='success' @click.prevents='toggle' icon='el-icon-remove') {{$t('Confirm')}}
el-button(plain type='danger' @click.prevent='remove' icon='el-icon-remove') {{$t('Remove')}}
el-button(plain type='primary' @click='$router.replace("/edit/"+event.id)') {{$t('Edit')}}
@@ -75,6 +77,20 @@ export default {
await api.delEvent(this.event.id)
this.delEvent(this.event.id)
this.$refs.eventDetail.hide()
+ },
+ async toggle () {
+ try {
+ if (this.event.is_visible) {
+
+ await api.unconfirmEvent(this.id)
+ this.event.is_visible = false
+ } else {
+ await api.confirmEvent(this.id)
+ this.event.is_visible = true
+ }
+ } catch (e) {
+
+ }
}
}
}
diff --git a/client/src/components/Login.vue b/client/src/components/Login.vue
index 5b40697e..aeb4dd72 100644
--- a/client/src/components/Login.vue
+++ b/client/src/components/Login.vue
@@ -15,7 +15,8 @@