diff --git a/.eslintrc.js b/.eslintrc.js
index b6351b05..6962b689 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,3 +1,6 @@
module.exports = {
- "extends": "standard"
+ "extends": "standard",
+ "rules": {
+ "camelcase": 0
+ }
};
diff --git a/app/api.js b/app/api.js
index dd316d79..261f7052 100644
--- a/app/api.js
+++ b/app/api.js
@@ -3,6 +3,8 @@ const { fillUser, isAuth, isAdmin } = require('./auth')
const eventController = require('./controller/event')
const exportController = require('./controller/export')
const userController = require('./controller/user')
+const settingsController = require('./controller/settings')
+
// const botController = require('./controller/bot')
const multer = require('multer')
@@ -51,6 +53,9 @@ api.get('/event/unconfirmed', isAuth, isAdmin, eventController.getUnconfirmed)
api.post('/event/notification', eventController.addNotification)
api.delete('/event/del_notification/:code', eventController.delNotification)
+api.get('/settings', settingsController.getAdminSettings)
+api.post('/settings', settingsController.setAdminSetting)
+
// get event
api.get('/event/:event_id', eventController.get)
diff --git a/app/controller/bot.js b/app/controller/bot.js
index d2c0104e..38851e55 100644
--- a/app/controller/bot.js
+++ b/app/controller/bot.js
@@ -1,10 +1,13 @@
-const { User, Event, Comment, Tag } = require('../model')
+// const { User, Event, Comment, Tag } = require('../model')
const config = require('../config')
const Mastodon = require('mastodon-api')
-const Sequelize = require('sequelize')
-const Op = Sequelize.Op
+// const Sequelize = require('sequelize')
+// const Op = Sequelize.Op
+const fs = require('fs')
+const path = require('path')
const moment = require('moment')
moment.locale('it')
+
const botController = {
bots: [],
// async initialize () {
@@ -30,12 +33,20 @@ const botController = {
// listener.on('error', botController.error)
// botController.bots.push({ email: user.email, bot })
// },
- post (user, event) {
- const { client_id, client_secret, access_token } = user.mastodon_auth
- const bot = new Mastodon({ access_token, api_url: `https://${user.mastodon_instance}/api/v1/` })
+ async post (mastodon_auth, event) {
+ const { access_token, instance } = mastodon_auth
+ const bot = new Mastodon({ access_token, api_url: `https://${instance}/api/v1/` })
const status = `${event.title} @ ${event.place.name} ${moment(event.start_datetime).format('ddd, D MMMM HH:mm')} -
${event.description} - ${event.tags.map(t => '#' + t.tag).join(' ')} ${config.baseurl}/event/${event.id}`
- return bot.post('/statuses', { status, visibility: 'private' })
+
+ let media
+ if (event.image_path) {
+ const file = path.join(__dirname, '..', '..', event.image_path)
+ if (fs.statSync(file)) {
+ media = await bot.post('media', { file: fs.createReadStream(file) })
+ }
+ }
+ return bot.post('statuses', { status, visibility: 'direct', media_ids: media ? [media.data.id] : [] })
}
// async message (msg) {
// console.log(msg)
diff --git a/app/controller/event.js b/app/controller/event.js
index d542eb09..bb9f8d57 100644
--- a/app/controller/event.js
+++ b/app/controller/event.js
@@ -19,7 +19,6 @@ const eventController = {
},
async getMeta (req, res) {
- console.log('GET META')
const places = await Place.findAll()
const tags = await Tag.findAll()
res.json({ tags, places })
@@ -28,6 +27,7 @@ const eventController = {
async getNotifications (event) {
function match (event, filters) {
// matches if no filter specified
+ if (!filters) 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)
@@ -47,7 +47,6 @@ const eventController = {
async updateTag (req, res) {
const tag = await Tag.findByPk(req.body.tag)
- console.log(tag)
if (tag) {
res.json(await tag.update(req.body))
} else {
diff --git a/app/controller/export.js b/app/controller/export.js
index dba94cf7..407b8b4e 100644
--- a/app/controller/export.js
+++ b/app/controller/export.js
@@ -5,6 +5,7 @@ const moment = require('moment')
const ics = require('ics')
const exportController = {
+
async export (req, res) {
console.log('type ', req.params.type)
const type = req.params.type
@@ -34,6 +35,7 @@ const exportController = {
return exportController.ics(res, events)
}
},
+
async feed (res, events) {
res.type('application/rss+xml; charset=UTF-8')
res.render('feed/rss.pug', { events, config, moment })
diff --git a/app/controller/settings.js b/app/controller/settings.js
new file mode 100644
index 00000000..85f32819
--- /dev/null
+++ b/app/controller/settings.js
@@ -0,0 +1,27 @@
+const { Settings } = require('../model')
+
+const settingsController = {
+ async setAdminSetting (key, value) {
+ await Settings.findOrCreate({ where: { key },
+ defaults: { value } })
+ .spread((settings, created) => {
+ if (!created) return settings.update({ value })
+ })
+ },
+
+ async getAdminSettings (req, res) {
+ const settings = await settingsController.settings()
+ res.json(settings)
+ },
+
+ async settings () {
+ const settings = await Settings.findAll()
+ const map = {}
+ settings.forEach(setting => {
+ map[setting.key] = setting.value
+ })
+ return map
+ }
+}
+
+module.exports = settingsController
diff --git a/app/controller/user.js b/app/controller/user.js
index a54e0b02..e4b0f2af 100644
--- a/app/controller/user.js
+++ b/app/controller/user.js
@@ -2,7 +2,8 @@ const jwt = require('jsonwebtoken')
const Mastodon = require('mastodon-api')
const User = require('../models/user')
-const { Event, Tag, Place, Notification } = require('../models/event')
+const { Event, Tag, Place } = require('../models/event')
+const settingsController = require('./settings')
const eventController = require('./event')
const config = require('../config')
const mail = require('../mail')
@@ -57,7 +58,7 @@ const userController = {
async addEvent (req, res) {
const body = req.body
- // remove description tag and create anchor tag
+ // remove description tag and create anchor tags
const description = body.description
.replace(/(<([^>]+)>)/ig, '')
.replace(/(https?:\/\/[^\s]+)/g, '$1')
@@ -68,28 +69,27 @@ const userController = {
multidate: body.multidate,
start_datetime: body.start_datetime,
end_datetime: body.end_datetime,
- is_visible: req.user ? true : false
+ is_visible: !!req.user
}
if (req.file) {
eventDetails.image_path = req.file.path
}
+ let event = await Event.create(eventDetails)
+
// create place
let place
try {
place = await Place.findOrCreate({ where: { name: body.place_name },
defaults: { address: body.place_address } })
.spread((place, created) => place)
+ await event.setPlace(place)
} catch (e) {
- console.log(e)
+ console.error(e)
}
- let event = await Event.create(eventDetails)
- await event.setPlace(place)
-
// create/assign tags
- console.log(body.tags)
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 } } })
@@ -98,22 +98,11 @@ const userController = {
if (req.user) await req.user.addEvent(event)
event = await Event.findByPk(event.id, { include: [User, Tag, Place] })
- // check if bot exists
- if (req.user && req.user.mastodon_auth) {
- const post = await bot.post(req.user, event)
- event.activitypub_id = post.id
- event.save()
- }
-
if (req.user) {
// insert notifications
const notifications = await eventController.getNotifications(event)
await event.setNotifications(notifications)
- } else {
- const notification = await Notification.create({ type: 'admin_email' })
- await event.setNotification(notification)
}
-
return res.json(event)
},
@@ -139,7 +128,6 @@ const userController = {
}
await event.setPlace(place)
await event.setTags([])
- console.log(body.tags)
if (body.tags) {
await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true })
const tags = await Tag.findAll({ where: { tag: { [Op.eq]: body.tags } } })
@@ -157,26 +145,47 @@ const userController = {
async getAuthURL (req, res) {
const instance = req.body.instance
- const { client_id, client_secret } = await Mastodon.createOAuthApp(`https://${instance}/api/v1/apps`, 'eventi', 'read write', `${config.baseurl}/settings`)
- const url = await Mastodon.getAuthorizationUrl(client_id, client_secret, `https://${instance}`, 'read write', `${config.baseurl}/settings`)
- console.log(req.user)
- req.user.mastodon_instance = instance
- req.user.mastodon_auth = { client_id, client_secret }
- await req.user.save()
+ const is_admin = req.body.admin && req.user.is_admin
+ const callback = `${config.baseurl}/${is_admin ? 'admin/oauth' : 'settings'}`
+ const { client_id, client_secret } = await Mastodon.createOAuthApp(`https://${instance}/api/v1/apps`,
+ config.title, 'read write', callback)
+ const url = await Mastodon.getAuthorizationUrl(client_id, client_secret,
+ `https://${instance}`, 'read write', callback)
+
+ if (is_admin) {
+ await settingsController.setAdminSetting('mastodon_auth', { client_id, client_secret, instance })
+ } else {
+ req.user.mastodon_auth = { client_id, client_secret, instance }
+ await req.user.save()
+ }
res.json(url)
},
async code (req, res) {
- const code = req.body.code
- const { client_id, client_secret } = req.user.mastodon_auth
- const instance = req.user.mastodon_instance
+ const { code, is_admin } = req.body
+ let client_id, client_secret, instance
+ const callback = `${config.baseurl}/${is_admin ? 'admin/oauth' : 'settings'}`
+
+ if (is_admin) {
+ const settings = await settingsController.settings();
+ ({ client_id, client_secret, instance } = settings.mastodon_auth)
+ } else {
+ ({ client_id, client_secret, instance } = req.user.mastodon_auth)
+ }
+
try {
- const token = await Mastodon.getAccessToken(client_id, client_secret, code, `https://${instance}`, `${config.baseurl}/settings`)
- const mastodon_auth = { client_id, client_secret, access_token: token }
- req.user.mastodon_auth = mastodon_auth
- await req.user.save()
- await bot.add(req.user, token)
- res.json(req.user)
+ const token = await Mastodon.getAccessToken(client_id, client_secret, code,
+ `https://${instance}`, callback)
+ const mastodon_auth = { client_id, client_secret, access_token: token, instance }
+ if (is_admin) {
+ await settingsController.setAdminSetting('mastodon_auth', mastodon_auth)
+ res.json(instance)
+ } else {
+ req.user.mastodon_auth = mastodon_auth
+ await req.user.save()
+ // await bot.add(req.user, token)
+ res.json(req.user)
+ }
} catch (e) {
res.json(e)
}
@@ -204,13 +213,18 @@ const userController = {
},
async register (req, res) {
+ const n_users = await User.count()
try {
- req.body.is_active = false
+ if (n_users === 0) {
+ // admin will be the first registered user
+ req.body.is_active = req.body.is_admin = true
+ } else {
+ req.body.is_active = false
+ }
const user = await User.create(req.body)
try {
mail.send(user.email, 'register', { user })
} catch (e) {
- console.log(e)
return res.status(400).json(e)
}
const payload = { email: user.email }
diff --git a/app/cron.js b/app/cron.js
index 7a31eff1..8826817a 100644
--- a/app/cron.js
+++ b/app/cron.js
@@ -1,26 +1,57 @@
const mail = require('./mail')
-const { Event, Notification, EventNotification, User, Place, Tag } = require('./model')
+const bot = require('./controller/bot')
+const settingsController = require('./controller/settings')
+
+const { Event, Notification, EventNotification,
+ User, Place, Tag } = require('./model')
+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 'mail_admin':
+ 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) {
+ const b = bot.post(settings.mastodon_auth, event)
+ promises.push(b)
+ }
+ // user publish
+ if (event.user && event.user.mastodon_auth) {
+ 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
+ }
+ return Promise.all(promises)
+}
async function loop () {
+ settings = await settingsController.settings()
// get all event notification in queue
const eventNotifications = await EventNotification.findAll()
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)
- try {
- if (notification.type === 'mail') {
- await mail.send(notification.email, 'event', { event })
- } else if (notification.type === 'mail_admin') {
- const admins = await User.findAll({ where: { is_admin: true } })
- await Promise.all(admins.map(admin =>
- mail.send(admin.email, 'event', { event, to_confirm: true, notification })))
- }
- } catch (e) {
- console.log('CATCH!', e)
- return false
- }
- return e.destroy()
+ await sendNotification(notification, event, e)
+ e.destroy()
})
return Promise.all(promises)
diff --git a/app/db.js b/app/db.js
index 98f95a23..3d526686 100644
--- a/app/db.js
+++ b/app/db.js
@@ -1,6 +1,5 @@
const Sequelize = require('sequelize')
const conf = require('./config.js')
-console.error(conf.db)
const db = new Sequelize(conf.db)
// db.sync({ force: true })
diff --git a/app/locales/es.json b/app/locales/es.json
index f01ae3b9..9cd985b3 100644
--- a/app/locales/es.json
+++ b/app/locales/es.json
@@ -1,3 +1,3 @@
{
"registration_email": "registration_email"
-}
\ No newline at end of file
+}
diff --git a/app/model.js b/app/model.js
index 81e07db1..303fa1dd 100644
--- a/app/model.js
+++ b/app/model.js
@@ -1,4 +1,14 @@
const User = require('./models/user')
const { Event, Comment, Tag, Place, Notification, EventNotification } = require('./models/event')
+const Settings = require('./models/settings')
-module.exports = { User, Event, Comment, Tag, Place, Notification, EventNotification }
+module.exports = {
+ User,
+ Event,
+ Comment,
+ Tag,
+ Place,
+ Notification,
+ EventNotification,
+ Settings
+}
diff --git a/app/models/event.js b/app/models/event.js
index 334b8ef1..27436ac5 100644
--- a/app/models/event.js
+++ b/app/models/event.js
@@ -30,10 +30,13 @@ const Notification = db.define('notification', {
remove_code: Sequelize.STRING,
type: {
type: Sequelize.ENUM,
- values: ['mail', 'admin_mail', 'activity_pub']
+ values: ['mail', 'admin_mail', 'mastodon']
}
})
+Notification.findOrCreate({ where: { type: 'mastodon' } })
+Notification.findOrCreate({ where: { type: 'admin_email' } })
+
const Place = db.define('place', {
name: { type: Sequelize.STRING, unique: true, index: true },
address: { type: Sequelize.STRING }
diff --git a/app/models/settings.js b/app/models/settings.js
new file mode 100644
index 00000000..148b7bdb
--- /dev/null
+++ b/app/models/settings.js
@@ -0,0 +1,9 @@
+const db = require('../db')
+const Sequelize = require('sequelize')
+
+const Settings = db.define('settings', {
+ key: { type: Sequelize.STRING, primaryKey: true, index: true },
+ value: Sequelize.JSON
+})
+
+module.exports = Settings
diff --git a/app/models/user.js b/app/models/user.js
index 45eb963f..3af9a95a 100644
--- a/app/models/user.js
+++ b/app/models/user.js
@@ -13,7 +13,6 @@ const User = db.define('user', {
password: Sequelize.STRING,
is_admin: Sequelize.BOOLEAN,
is_active: Sequelize.BOOLEAN,
- mastodon_instance: Sequelize.STRING,
mastodon_auth: Sequelize.JSON
})
diff --git a/app/server.js b/app/server.js
index 8b8b7be2..7d75173b 100644
--- a/app/server.js
+++ b/app/server.js
@@ -11,8 +11,8 @@ app.set('views', path.join(__dirname, 'views'))
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(cors())
-app.use('/static', express.static(path.join(__dirname, 'uploads')))
-app.use('/uploads', express.static('uploads'))
+app.use('/static', express.static(path.join(__dirname, '..', 'uploads')))
+app.use('/uploads', express.static(path.join(__dirname, '..', 'uploads')))
app.use('/api', api)
app.use('/', express.static(path.join(__dirname, '..', 'client', 'dist')))
app.use('/css', express.static(path.join(__dirname, '..', 'client', 'dist', 'css')))
diff --git a/app/storage.js b/app/storage.js
index 4c81ce1d..7e117daa 100644
--- a/app/storage.js
+++ b/app/storage.js
@@ -23,10 +23,10 @@ DiskStorage.prototype._handleFile = function _handleFile (req, file, cb) {
that.getDestination(req, file, function (err, destination) {
if (err) return cb(err)
- const filename = crypto.randomBytes(16).toString('hex') + '.webp'
+ const filename = crypto.randomBytes(16).toString('hex') + '.jpg'
const finalPath = path.join(destination, filename)
const outStream = fs.createWriteStream(finalPath)
- const resizer = sharp().resize(800).webp()
+ const resizer = sharp().resize(800).jpeg({ quality: 80 })
file.stream.pipe(resizer).pipe(outStream)
outStream.on('error', cb)
diff --git a/client/src/api.js b/client/src/api.js
index d293039e..d2908963 100644
--- a/client/src/api.js
+++ b/client/src/api.js
@@ -60,5 +60,7 @@ export default {
getAuthURL: mastodonInstance => post('/user/getauthurl', mastodonInstance),
setCode: code => post('/user/code', code),
getKnowLocations: () => get('/locations'),
- getKnowTags: () => get('/tags')
+ getKnowTags: () => get('/tags'),
+ getAdminSettings: () => get('/settings')
+ // setAdminSetting: (key, value) => post('/settings', { key, value })
}
diff --git a/client/src/components/Admin.vue b/client/src/components/Admin.vue
index 87317905..75c7596b 100644
--- a/client/src/components/Admin.vue
+++ b/client/src/components/Admin.vue
@@ -1,6 +1,6 @@
b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Admin")' :visible='true' size='lg')
- el-tabs
+ el-tabs(tabPosition='left' v-model='tab')
//- USERS
el-tab-pane.pt-1
template(slot='label')
@@ -54,7 +54,7 @@
template(slot='label')
v-icon(name='tag')
span {{$t('Tags')}}
- p Select a tag to change it's color
+ p {{$t('admin_tag_explanation')}}
el-tag(v-if='tag.tag' :color='tag.color || "grey"' size='mini') {{tag.tag}}
el-form(:inline='true' label-width='120px')
el-form-item(:label="$t('Color')")
@@ -71,7 +71,12 @@
template(slot='label')
v-icon(name='tools')
span {{$t('Settings')}}
-
+ el-form(inline)
+ span {{$t('admin_mastodon_explanation')}}
+ el-input(v-model="mastodon_instance")
+ span(slot='prepend') {{$t('Mastodon instance')}}
+ el-button(slot='append' @click='associate' variant='success' type='success') {{$t('Associate')}}
+