diff --git a/pages/event/eventAdmin.vue b/pages/event/eventAdmin.vue index d2bbb9ce..98075d3e 100644 --- a/pages/event/eventAdmin.vue +++ b/pages/event/eventAdmin.vue @@ -36,7 +36,7 @@ export default { type: 'error' }) const id = parent ? this.event.parentId : this.event.id - await this.$axios.delete(`/user/event/${id}`) + await this.$axios.delete(`/event/${id}`) this.delEvent(Number(id)) this.$router.replace('/') } catch (e) { diff --git a/server/api/controller/event.js b/server/api/controller/event.js index 70cd427f..ff0b698d 100644 --- a/server/api/controller/event.js +++ b/server/api/controller/event.js @@ -1,12 +1,16 @@ const crypto = require('crypto') const moment = require('moment-timezone') +const path = require('path') +const config = require('config') +const fs = require('fs') const { Op } = require('sequelize') const _ = require('lodash') const { event: Event, resource: Resource, tag: Tag, place: Place, notification: Notification } = require('../models') const Sequelize = require('sequelize') const exportController = require('./export') +const sanitizeHtml = require('sanitize-html') + const debug = require('debug')('controller:event') -// const { Task, TaskManager } = require('../../taskManager') const eventController = { @@ -191,6 +195,148 @@ const eventController = { res.sendStatus(200) }, + async add (req, res) { + // req.err comes from multer streaming error + if (req.err) { + debug(req.err) + return res.status(400).json(req.err.toString()) + } + + try { + const body = req.body + const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null + + const eventDetails = { + title: body.title, + // remove html tags + description: sanitizeHtml(body.description), + multidate: body.multidate, + start_datetime: body.start_datetime, + end_datetime: body.end_datetime, + recurrent, + // publish this event only if authenticated + is_visible: !!req.user + } + + if (req.file) { + eventDetails.image_path = req.file.filename + } + + const event = await Event.create(eventDetails) + + // create place if needed + const place = await Place.findOrCreate({ + where: { name: body.place_name }, + defaults: { address: body.place_address } + }) + .spread((place, created) => place) + await event.setPlace(place) + event.place = place + + // create/assign 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 } } }) + await Promise.all(tags.map(t => t.update({ weigth: Number(t.weigth) + 1 }))) + await event.addTags(tags) + event.tags = tags + } + + // associate user to event and reverse + if (req.user) { + await req.user.addEvent(event) + await event.setUser(req.user) + } + + // create recurrent instances of event if needed + // without waiting for the task manager + if (event.recurrent) { + eventController._createRecurrent() + } + + // return created event to the client + res.json(event) + + // send notification (mastodon/email) + // only if user is authenticated + if (req.user) { + const notifier = require('../../notifier') + notifier.notifyEvent('Create', event.id) + } + } catch (e) { + res.sendStatus(400) + debug(e) + } + }, + + async update (req, res) { + if (req.err) { + return res.status(400).json(req.err.toString()) + } + const body = req.body + const event = await Event.findByPk(body.id) + if (!req.user.is_admin && event.userId !== req.user.id) { + return res.sendStatus(403) + } + + if (req.file) { + if (event.image_path) { + const old_path = path.resolve(config.upload_path, event.image_path) + const old_thumb_path = path.resolve(config.upload_path, 'thumb', event.image_path) + await fs.unlink(old_path, e => console.error(e)) + await fs.unlink(old_thumb_path, e => console.error(e)) + } + body.image_path = req.file.filename + } + + body.description = sanitizeHtml(body.description) + + await event.update(body) + let place + try { + place = await Place.findOrCreate({ + where: { name: body.place_name }, + defaults: { address: body.place_address } + }).spread((place, created) => place) + } catch (e) { + console.log('error', e) + } + await event.setPlace(place) + await event.setTags([]) + 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 event.addTags(tags) + } + const newEvent = await Event.findByPk(event.id, { include: [Tag, Place] }) + res.json(newEvent) + const notifier = require('../../notifier') + notifier.notifyEvent('Update', event.id) + }, + + async remove (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)) { + if (event.image_path) { + const old_path = path.join(config.upload_path, event.image_path) + const old_thumb_path = path.join(config.upload_path, 'thumb', event.image_path) + try { + fs.unlinkSync(old_thumb_path) + fs.unlinkSync(old_path) + } catch (e) { + debug(e) + } + } + const notifier = require('../../notifier') + await notifier.notifyEvent('Delete', event.id) + await event.destroy() + res.sendStatus(200) + } else { + res.sendStatus(403) + } + }, + async _select (start = moment.unix(), limit = 100) { const where = { // confirmed event only diff --git a/server/api/controller/oauth.js b/server/api/controller/oauth.js index 2c4a1bf2..9f4abe63 100644 --- a/server/api/controller/oauth.js +++ b/server/api/controller/oauth.js @@ -76,7 +76,7 @@ const oauthController = { * */ async getAccessToken (accessToken) { const oauth_token = await OAuthToken.findByPk(accessToken, - { include: [User, { model: OAuthClient, as: 'client' }] }) + { include: [{ model: User, attributes: { exclude: ['password'] } }, { model: OAuthClient, as: 'client' }] }) return oauth_token }, diff --git a/server/api/controller/user.js b/server/api/controller/user.js index 852d7f20..517e14a9 100644 --- a/server/api/controller/user.js +++ b/server/api/controller/user.js @@ -1,160 +1,12 @@ -const fs = require('fs') -const path = require('path') const crypto = require('crypto') const { Op } = require('sequelize') -const sanitizeHtml = require('sanitize-html') const config = require('config') const mail = require('../mail') -const { user: User, event: Event, tag: Tag, place: Place } = require('../models') +const { user: User } = require('../models') const settingsController = require('./settings') -const eventController = require('./event') const debug = require('debug')('user:controller') const userController = { - 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)) { - if (event.image_path) { - const old_path = path.join(config.upload_path, event.image_path) - const old_thumb_path = path.join(config.upload_path, 'thumb', event.image_path) - try { - fs.unlinkSync(old_thumb_path) - fs.unlinkSync(old_path) - } catch (e) { - debug(e) - } - } - const notifier = require('../../notifier') - await notifier.notifyEvent('Delete', event.id) - await event.destroy() - res.sendStatus(200) - } else { - res.sendStatus(403) - } - }, - - /** - * add event - */ - async addEvent (req, res) { - // req.err comes from multer streaming error - if (req.err) { - debug(req.err) - return res.status(400).json(req.err.toString()) - } - - try { - const body = req.body - const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null - - const eventDetails = { - title: body.title, - // remove html tags - description: sanitizeHtml(body.description), - multidate: body.multidate, - start_datetime: body.start_datetime, - end_datetime: body.end_datetime, - recurrent, - // publish this event only if authenticated - is_visible: !!req.user - } - - if (req.file) { - eventDetails.image_path = req.file.filename - } - - const event = await Event.create(eventDetails) - - // create place if needed - const place = await Place.findOrCreate({ - where: { name: body.place_name }, - defaults: { address: body.place_address } - }) - .spread((place, created) => place) - await event.setPlace(place) - event.place = place - - // create/assign 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 } } }) - await Promise.all(tags.map(t => t.update({ weigth: Number(t.weigth) + 1 }))) - await event.addTags(tags) - event.tags = tags - } - - // associate user to event and reverse - if (req.user) { - await req.user.addEvent(event) - await event.setUser(req.user) - } - - // create recurrent instances of event if needed - // without waiting for the task manager - if (event.recurrent) { - eventController._createRecurrent() - } - - // return created event to the client - res.json(event) - - // send notification (mastodon/email) - // only if user is authenticated - if (req.user) { - const notifier = require('../../notifier') - notifier.notifyEvent('Create', event.id) - } - } catch (e) { - res.sendStatus(400) - debug(e) - } - }, - - async updateEvent (req, res) { - if (req.err) { - return res.status(400).json(req.err.toString()) - } - const body = req.body - const event = await Event.findByPk(body.id) - if (!req.user.is_admin && event.userId !== req.user.id) { - return res.sendStatus(403) - } - - if (req.file) { - if (event.image_path) { - const old_path = path.resolve(config.upload_path, event.image_path) - const old_thumb_path = path.resolve(config.upload_path, 'thumb', event.image_path) - await fs.unlink(old_path, e => console.error(e)) - await fs.unlink(old_thumb_path, e => console.error(e)) - } - body.image_path = req.file.filename - } - - body.description = sanitizeHtml(body.description) - - await event.update(body) - let place - try { - place = await Place.findOrCreate({ - where: { name: body.place_name }, - defaults: { address: body.place_address } - }).spread((place, created) => place) - } catch (e) { - console.log('error', e) - } - await event.setPlace(place) - await event.setTags([]) - 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 event.addTags(tags) - } - const newEvent = await Event.findByPk(event.id, { include: [Tag, Place] }) - res.json(newEvent) - const notifier = require('../../notifier') - notifier.notifyEvent('Update', event.id) - }, async forgotPassword (req, res) { const email = req.body.email diff --git a/server/api/index.js b/server/api/index.js index 813641fc..5c559c16 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -21,9 +21,14 @@ const api = express.Router() api.use(express.urlencoded({ extended: false })) api.use(express.json()) -api.get('/user', isAuth, (req, res) => res.json(res.locals.oauth.token.user)) -// api.post('/user/login', userController.login) -// api.get('/user/logout', userController.logout) +/** + * Get current authenticated user + * @category User + * @path /api/user + * @method GET + */ +api.get('/user', isAuth, (req, res) => res.json(req.user)) + api.post('/user/recover', userController.forgotPassword) api.post('/user/check_recover_code', userController.checkRecoverCode) api.post('/user/recover_password', userController.updatePasswordWithRecoverCode) @@ -45,14 +50,31 @@ api.get('/users', isAdmin, userController.getAll) // update a place (modify address..) api.put('/place', isAdmin, eventController.updatePlace) -// add event -api.post('/user/event', upload.single('image'), userController.addEvent) +/** + * Add a new event + * @category Event + * @path /event + * @method POST + * @note `Content-Type` has to be `multipart/form-data` 'cause support image upload + * @param {string} title - event's title + * @param {string} description - event's description (html accepted and sanitized) + * @param {string} place_name - the name of the place + * @param {string} [place_address] - the address of the place + * @param {integer} start_datetime - start timestamp + * @param {integer} multidate - is a multidate event? + * @param {array} tags - List of tags + * @param {object} [recurrent] - Recurrent event details + * @param {string} [recurrent.frequency] - could be `1w` or `2w` + * @param {string} [recurrent.type] - not used + * @param {array} [recurrent.days] - array of days + * @param {image} [image] - Image + */ +api.post('/event', upload.single('image'), eventController.add) -// update event -api.put('/user/event', hasPerm('event:write'), upload.single('image'), userController.updateEvent) +api.put('/event', hasPerm('event:write'), upload.single('image'), eventController.update) // remove event -api.delete('/user/event/:id', hasPerm('event:remove'), userController.delEvent) +api.delete('/event/:id', hasPerm('event:remove'), eventController.remove) // get tags/places api.get('/event/meta', eventController.getMeta) diff --git a/server/routes.js b/server/routes.js index bf769b6e..cfda114b 100644 --- a/server/routes.js +++ b/server/routes.js @@ -17,14 +17,14 @@ const helpers = require('./helpers') const { startOfMonth, startOfWeek, getUnixTime } = require('date-fns') const app = express() +// ignore unimplemented ping url from fediverse +app.use(spamFilter) + app.use((req, res, next) => { debug(req.path) next() }) -// ignore unimplemented ping url from fediverse -app.use(spamFilter) - // serve favicon and static content app.use('/logo.png', express.static('./static/gancio.png')) app.use('/media/', express.static(config.upload_path)) diff --git a/store/index.js b/store/index.js index f29e9982..e4e702d1 100644 --- a/store/index.js +++ b/store/index.js @@ -175,13 +175,13 @@ export const actions = { commit('update', { tags, places }) }, async addEvent ({ commit }, formData) { - const event = await this.$axios.$post('/user/event', formData) + const event = await this.$axios.$post('/event', formData) if (event.user) { commit('addEvent', event) } }, async updateEvent ({ commit }, formData) { - const event = await this.$axios.$put('/user/event', formData) + const event = await this.$axios.$put('/event', formData) if (event.user) { commit('updateEvent', event) }