diff --git a/Dockerfile b/Dockerfile
index 0c140f99..96dc6f9b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,15 +4,17 @@ FROM node:10
WORKDIR /usr/src/app
COPY package.json .
+COPY pm2.json .
# install backend dependencies
-RUN yarn
+RUN yarn --prod
# copy source
-COPY . .
+COPY app app/
+COPY client client/
# install nodemon
-RUN yarn global add nodemon
+RUN yarn global add pm2
WORKDIR /usr/src/app/client
@@ -26,4 +28,4 @@ WORKDIR /usr/src/app
EXPOSE 12300
-CMD [ "yarn", "run", "serve" ]
+CMD [ "pm2-runtime", "start", "pm2.json" ]
diff --git a/app/api.js b/app/api.js
index 0f8c2fe2..d88fe22a 100644
--- a/app/api.js
+++ b/app/api.js
@@ -4,16 +4,10 @@ const eventController = require('./controller/event')
const exportController = require('./controller/export')
const userController = require('./controller/user')
// const botController = require('./controller/bot')
-
-const path = require('path')
const multer = require('multer')
-const crypto = require('crypto')
const storage = require('./storage')({
- destination: 'uploads/',
- filename: (req, file, cb) => {
- cb(null, crypto.randomBytes(16).toString('hex') + path.extname(file.originalname))
- }
+ destination: 'uploads/'
})
const upload = multer({ storage })
const api = express.Router()
diff --git a/app/auth.js b/app/auth.js
index 71396057..56afe236 100644
--- a/app/auth.js
+++ b/app/auth.js
@@ -1,26 +1,24 @@
const jwt = require('jsonwebtoken')
const config = require('./config')
const User = require('./models/user')
+const { Op } = require('sequelize')
const Auth = {
async fillUser (req, res, next) {
const token = req.body.token || req.params.token || req.headers['x-access-token']
- console.log('[AUTH] ', token)
- if (!token) next()
+ if (!token) return next()
jwt.verify(token, config.secret, async (err, decoded) => {
- if (err) next()
- req.user = await User.findOne({ where: { email: decoded.email, is_active: true } })
+ if (err) return next()
+ req.user = await User.findOne({ where: { email: { [Op.eq]: decoded.email }, is_active: true } })
next()
})
},
async isAuth (req, res, next) {
const token = req.body.token || req.params.token || req.headers['x-access-token']
- console.log('[AUTH] ', token)
if (!token) return res.status(403).send({ message: 'Token not found' })
jwt.verify(token, config.secret, async (err, decoded) => {
if (err) return res.status(403).send({ message: 'Failed to authenticate token ' + err })
- console.log('DECODED TOKEN', decoded)
- req.user = await User.findOne({ where: { email: decoded.email, is_active: true } })
+ req.user = await User.findOne({ where: { email: { [Op.eq]: decoded.email }, is_active: true } })
if (!req.user) return res.status(403).send({ message: 'Failed to authenticate token ' + err })
next()
})
diff --git a/app/config.js b/app/config.js
index 355c9a94..a09c9615 100644
--- a/app/config.js
+++ b/app/config.js
@@ -20,9 +20,9 @@ if (process.env.NODE_ENV === 'production') {
}
module.exports = {
- locale: 'en',
+ locale: 'it',
- title: process.env.TITLE || 'Put here your site name',
+ title: process.env.TITLE || 'GANCIO',
description: process.env.DESCRIPTION || 'A calendar for radical communities',
baseurl: process.env.BASE_URL || 'http://localhost:8080',
diff --git a/app/controller/event.js b/app/controller/event.js
index 5a89d33e..33d196bb 100644
--- a/app/controller/event.js
+++ b/app/controller/event.js
@@ -1,14 +1,14 @@
-const { User, Event, Comment, Tag, Place, MailReminder } = require('../model')
+const { User, Event, Comment, Tag, Place, Reminder } = require('../model')
const moment = require('moment')
-const Sequelize = require('sequelize')
-
+const { Op } = require('sequelize')
+const lodash = require('lodash')
const eventController = {
async addComment (req, res) {
// comment could be added to an event or to another comment
- let event = await Event.findOne({ where: { activitypub_id: req.body.id } })
+ let event = await Event.findOne({ where: { activitypub_id: { [Op.eq]: req.body.id } } })
if (!event) {
- const comment = await Comment.findOne({ where: { activitypub_id: req.body.id }, include: Event })
+ const comment = await Comment.findOne({ where: { activitypub_id: { [Op.eq]: req.body.id } }, include: Event })
event = comment.event
}
const comment = new Comment(req.body)
@@ -23,6 +23,26 @@ const eventController = {
res.json({ tags, places })
},
+ async getReminders (event) {
+ function match (event, filters) {
+ // matches if no filter specified
+ 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 (filters.places.length) {
+ if (filters.places.find(p => p === event.place.name)) {
+ return true
+ }
+ }
+ }
+ const reminders = await Reminder.findAll()
+
+ // get reminder that matches with selected event
+ return reminders.filter(reminder => match(event, reminder.filters))
+ },
+
async updateTag (req, res) {
const tag = await Tag.findByPk(req.body.tag)
console.log(tag)
@@ -68,8 +88,12 @@ const eventController = {
},
async addReminder (req, res) {
- await MailReminder.create(req.body.reminder)
- res.json(200)
+ try {
+ await Reminder.create(req.body)
+ res.sendStatus(200)
+ } catch (e) {
+ res.sendStatus(404)
+ }
},
async getAll (req, res) {
const start = moment().year(req.params.year).month(req.params.month).startOf('month').subtract(1, 'week')
@@ -77,9 +101,9 @@ const eventController = {
const events = await Event.findAll({
where: {
is_visible: true,
- [Sequelize.Op.and]: [
- { start_datetime: { [Sequelize.Op.gte]: start } },
- { start_datetime: { [Sequelize.Op.lte]: end } }
+ [Op.and]: [
+ { start_datetime: { [Op.gte]: start } },
+ { start_datetime: { [Op.lte]: end } }
]
},
order: [['start_datetime', 'ASC']],
diff --git a/app/controller/export.js b/app/controller/export.js
index 6ad3c5c7..dba94cf7 100644
--- a/app/controller/export.js
+++ b/app/controller/export.js
@@ -1,5 +1,5 @@
const { Event, Comment, Tag, Place } = require('../model')
-const Sequelize = require('sequelize')
+const { Op } = require('sequelize')
const config = require('../config')
const moment = require('moment')
const ics = require('ics')
@@ -21,7 +21,7 @@ const exportController = {
}
const events = await Event.findAll({
order: [['start_datetime', 'ASC']],
- where: { start_datetime: { [Sequelize.Op.gte]: yesterday } },
+ where: { start_datetime: { [Op.gte]: yesterday } },
include: [Comment, {
model: Tag,
where: whereTag
diff --git a/app/controller/user.js b/app/controller/user.js
index 7e5b7617..9a84ba5d 100644
--- a/app/controller/user.js
+++ b/app/controller/user.js
@@ -3,14 +3,16 @@ const Mastodon = require('mastodon-api')
const User = require('../models/user')
const { Event, Tag, Place } = require('../models/event')
+const eventController = require('./event')
const config = require('../config')
const mail = require('../mail')
const bot = require('./bot')
+const { Op } = require('sequelize')
const userController = {
async login (req, res) {
// find the user
- const user = await User.findOne({ where: { email: req.body.email } })
+ const user = await User.findOne({ where: { email: { [Op.eq]: req.body.email } } })
if (!user) {
res.status(404).json({ success: false, message: 'AUTH_FAIL' })
} else if (user) {
@@ -82,6 +84,7 @@ const userController = {
} catch (e) {
console.log(e)
}
+
let event = await Event.create(eventDetails)
await event.setPlace(place)
@@ -89,7 +92,7 @@ const userController = {
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: body.tags } })
+ const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } })
await event.addTags(tags)
}
if (req.user) await req.user.addEvent(event)
@@ -102,7 +105,9 @@ const userController = {
event.save()
}
- mail.send(config.admin, 'event', { event })
+ // insert reminder
+ const reminders = await eventController.getReminders(event)
+ await event.setReminders(reminders)
return res.json(event)
},
@@ -121,7 +126,7 @@ const userController = {
await event.update(body)
let place
try {
- place = await Place.findOrCreate({ where: { name: body.place_name },
+ place = await Place.findOrCreate({ where: { name: { [Op.eq]: body.place_name } },
defaults: { address: body.place_address } })
.spread((place, created) => place)
} catch (e) {
@@ -132,7 +137,7 @@ const userController = {
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: body.tags } })
+ const tags = await Tag.findAll({ where: { tag: { [Op.eq]: body.tags } } })
await event.addTags(tags)
}
const newEvent = await Event.findByPk(event.id, { include: [User, Tag, Place] })
diff --git a/app/cron.js b/app/cron.js
new file mode 100644
index 00000000..4e184390
--- /dev/null
+++ b/app/cron.js
@@ -0,0 +1,27 @@
+const mail = require('./mail')
+const { Event, Reminder, EventReminder, User, Place, Tag } = require('./model')
+
+async function loop () {
+ console.log('nel loop')
+ // get all event reminder in queue
+ const eventReminders = await EventReminder.findAll()
+ const promises = eventReminders.map(async e => {
+ const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] })
+ console.log('EVENT ')
+ console.log(event)
+ if (!event.place) return
+ const reminder = await Reminder.findByPk(e.reminderId)
+ try {
+ await mail.send(reminder.email, 'event', { event })
+ } catch (e) {
+ console.log('DENTRO CATCH!', e)
+ return false
+ }
+ return e.destroy()
+ })
+
+ return Promise.all(promises)
+}
+
+setInterval(loop, 20000)
+loop()
diff --git a/app/emails/event/html.pug b/app/emails/event/html.pug
new file mode 100644
index 00000000..853ad236
--- /dev/null
+++ b/app/emails/event/html.pug
@@ -0,0 +1,10 @@
+h3 #{event.title}
+p Dove: #{event.place.name} - #{event.place.address}
+p Quando: #{datetime(event.start_datetime)}
+br
+
+p #{event.description}
+
+#{config.baseurl}/event/#{event.id}
+hr
+#{config.title} - #{config.description}
\ No newline at end of file
diff --git a/app/emails/event/subject.pug b/app/emails/event/subject.pug
new file mode 100644
index 00000000..fd159324
--- /dev/null
+++ b/app/emails/event/subject.pug
@@ -0,0 +1 @@
+= `[${config.title}] ${event.title} @${event.place.name} ${datetime(event.start_datetime)}`
diff --git a/emails/mail.css b/app/emails/mail.css
similarity index 100%
rename from emails/mail.css
rename to app/emails/mail.css
diff --git a/emails/register/html.pug b/app/emails/register/html.pug
similarity index 100%
rename from emails/register/html.pug
rename to app/emails/register/html.pug
diff --git a/emails/register/subject.pug b/app/emails/register/subject.pug
similarity index 100%
rename from emails/register/subject.pug
rename to app/emails/register/subject.pug
diff --git a/locales/en.json b/app/locales/en.json
similarity index 100%
rename from locales/en.json
rename to app/locales/en.json
diff --git a/locales/es.json b/app/locales/es.json
similarity index 100%
rename from locales/es.json
rename to app/locales/es.json
diff --git a/locales/it.json b/app/locales/it.json
similarity index 100%
rename from locales/it.json
rename to app/locales/it.json
diff --git a/locales/zh.json b/app/locales/zh.json
similarity index 100%
rename from locales/zh.json
rename to app/locales/zh.json
diff --git a/app/mail.js b/app/mail.js
index c02dc611..911eba02 100644
--- a/app/mail.js
+++ b/app/mail.js
@@ -1,16 +1,19 @@
const Email = require('email-templates')
const path = require('path')
const config = require('./config')
+const moment = require('moment')
+moment.locale('it')
const mail = {
send (addresses, template, locals) {
locals.locale = config.locale
const email = new Email({
+ views: { root: path.join(__dirname, 'emails') },
juice: true,
juiceResources: {
preserveImportant: true,
webResources: {
- relativeTo: path.join(__dirname, '..', 'emails')
+ relativeTo: path.join(__dirname, 'emails')
}
},
message: {
@@ -26,7 +29,7 @@ const mail = {
to: addresses,
bcc: config.admin
},
- locals
+ locals: { ...locals, config, datetime: datetime => moment(datetime).format('ddd, D MMMM HH:mm') }
})
}
}
diff --git a/app/model.js b/app/model.js
index 8948a26b..2ff5d4d3 100644
--- a/app/model.js
+++ b/app/model.js
@@ -1,4 +1,4 @@
const User = require('./models/user')
-const { Event, Comment, Tag, Place, MailReminder } = require('./models/event')
+const { Event, Comment, Tag, Place, Reminder, EventReminder } = require('./models/event')
-module.exports = { User, Event, Comment, Tag, Place, MailReminder }
+module.exports = { User, Event, Comment, Tag, Place, Reminder, EventReminder }
diff --git a/app/models/event.js b/app/models/event.js
index ccb45d25..0928c10e 100644
--- a/app/models/event.js
+++ b/app/models/event.js
@@ -24,10 +24,10 @@ const Comment = db.define('comment', {
text: Sequelize.STRING
})
-const MailReminder = db.define('reminder', {
+const Reminder = db.define('reminder', {
filters: Sequelize.JSON,
- mail: Sequelize.STRING,
- send_on_add: Sequelize.BOOLEAN,
+ email: Sequelize.STRING,
+ notify_on_add: Sequelize.BOOLEAN,
send_reminder: Sequelize.BOOLEAN
})
@@ -42,10 +42,14 @@ Event.hasMany(Comment)
Event.belongsToMany(Tag, { through: 'tagEvent' })
Tag.belongsToMany(Event, { through: 'tagEvent' })
+const EventReminder = db.define('EventReminder')
+Event.belongsToMany(Reminder, { through: EventReminder })
+Reminder.belongsToMany(Event, { through: EventReminder })
+
Event.belongsTo(User)
Event.belongsTo(Place)
User.hasMany(Event)
Place.hasMany(Event)
-module.exports = { Event, Comment, Tag, Place, MailReminder }
+module.exports = { Event, Comment, Tag, Place, Reminder, EventReminder }
diff --git a/server.js b/app/server.js
similarity index 55%
rename from server.js
rename to app/server.js
index e8817404..e7006ffb 100644
--- a/server.js
+++ b/app/server.js
@@ -1,21 +1,22 @@
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
-const api = require('./app/api')
+const api = require('./api')
const cors = require('cors')
const path = require('path')
const port = process.env.PORT || 9000
+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('/api', api)
-app.use('/', express.static(path.join(__dirname, 'client', 'dist')))
-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.use('/', express.static(path.join(__dirname, '..', 'client', 'dist')))
+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('Magic happens at http://localhost:' + port)
diff --git a/app/storage.js b/app/storage.js
new file mode 100644
index 00000000..4c81ce1d
--- /dev/null
+++ b/app/storage.js
@@ -0,0 +1,56 @@
+const fs = require('fs')
+const os = require('os')
+const path = require('path')
+const crypto = require('crypto')
+const mkdirp = require('mkdirp')
+const sharp = require('sharp')
+
+function getDestination (req, file, cb) {
+ cb(null, os.tmpdir())
+}
+
+function DiskStorage (opts) {
+ if (typeof opts.destination === 'string') {
+ mkdirp.sync(opts.destination)
+ this.getDestination = function ($0, $1, cb) { cb(null, opts.destination) }
+ } else {
+ this.getDestination = (opts.destination || getDestination)
+ }
+}
+
+DiskStorage.prototype._handleFile = function _handleFile (req, file, cb) {
+ var that = this
+ that.getDestination(req, file, function (err, destination) {
+ if (err) return cb(err)
+
+ const filename = crypto.randomBytes(16).toString('hex') + '.webp'
+ const finalPath = path.join(destination, filename)
+ const outStream = fs.createWriteStream(finalPath)
+ const resizer = sharp().resize(800).webp()
+
+ file.stream.pipe(resizer).pipe(outStream)
+ outStream.on('error', cb)
+ outStream.on('finish', function () {
+ cb(null, {
+ destination,
+ filename,
+ path: finalPath,
+ size: outStream.bytesWritten
+ })
+ })
+ })
+}
+
+DiskStorage.prototype._removeFile = function _removeFile (req, file, cb) {
+ var path = file.path
+
+ delete file.destination
+ delete file.filename
+ delete file.path
+
+ fs.unlink(path, cb)
+}
+
+module.exports = function (opts) {
+ return new DiskStorage(opts)
+}
diff --git a/views/feed/rss.pug b/app/views/feed/rss.pug
similarity index 100%
rename from views/feed/rss.pug
rename to app/views/feed/rss.pug
diff --git a/client/.eslintrc.js b/client/.eslintrc.js
index b6351b05..2f16b25f 100644
--- a/client/.eslintrc.js
+++ b/client/.eslintrc.js
@@ -1,3 +1,3 @@
module.exports = {
- "extends": "standard"
-};
+ 'extends': 'standard'
+}
diff --git a/client/src/components/Admin.vue b/client/src/components/Admin.vue
index 0b246a82..87317905 100644
--- a/client/src/components/Admin.vue
+++ b/client/src/components/Admin.vue
@@ -37,7 +37,7 @@
v-icon(name='calendar')
span.ml-1 {{$t('Events')}}
p {{$t('event_confirm_explanation')}}
- el-table(:data='paginatedEvents' small primary-key='id')
+ el-table(:data='paginatedEvents' small primary-key='id' v-loading='loading')
el-table-column(:label='$t("Name")')
template(slot-scope='data') {{data.row.title}}
el-table-column(:label='$t("Where")')
@@ -76,6 +76,7 @@