first commit backend
This commit is contained in:
commit
887157f2a9
27 changed files with 819 additions and 0 deletions
5
.editorconfig
Normal file
5
.editorconfig
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[*.{js,jsx,ts,tsx,vue}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw*
|
31
app/TODO.md
Normal file
31
app/TODO.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# features importanti
|
||||||
|
- filtri (posto, tag, full text)
|
||||||
|
- routing interface permission
|
||||||
|
- admin:
|
||||||
|
- vedere utenti
|
||||||
|
- bloccare utenti
|
||||||
|
- approvare utenti
|
||||||
|
- disapprovare utenti
|
||||||
|
- scrape facebook
|
||||||
|
- output
|
||||||
|
- rss
|
||||||
|
- ics
|
||||||
|
- mail
|
||||||
|
- mastodon.
|
||||||
|
- embed
|
||||||
|
|
||||||
|
gancio.cisti.org
|
||||||
|
----
|
||||||
|
# mastodon
|
||||||
|
- autodifesa
|
||||||
|
- mastodon social
|
||||||
|
|
||||||
|
#stakkastakka
|
||||||
|
cosa parlare
|
||||||
|
|
||||||
|
#cavo
|
||||||
|
|
||||||
|
-------
|
||||||
|
Ci sentiamo spesso rispondere con un'alzata di spalle e un "ma su facebook ci sono tutti",
|
||||||
|
e' vero, anche
|
||||||
|
Per noi facebook e'
|
50
app/api.js
Normal file
50
app/api.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
const express = require('express')
|
||||||
|
const { isAuth, isAdmin } = require('./auth')
|
||||||
|
|
||||||
|
const eventController = require('./controller/event')
|
||||||
|
const exportController = require('./controller/export')
|
||||||
|
// const botController = require('./controller/bot')
|
||||||
|
|
||||||
|
const multer = require('multer')
|
||||||
|
const upload = multer({ dest: 'uploads/' })
|
||||||
|
const api = express.Router()
|
||||||
|
|
||||||
|
// USER API
|
||||||
|
const userController = require('./controller/user')
|
||||||
|
|
||||||
|
api.route('/login')
|
||||||
|
.post(userController.login)
|
||||||
|
|
||||||
|
api.route('/user')
|
||||||
|
.post(userController.register)
|
||||||
|
.get(isAuth, userController.current)
|
||||||
|
.put(isAuth, isAdmin, userController.update)
|
||||||
|
|
||||||
|
api.get('/users', isAuth, isAdmin, userController.getAll)
|
||||||
|
api.put('/tag', isAuth, isAdmin, eventController.updateTag)
|
||||||
|
|
||||||
|
api.route('/user/event')
|
||||||
|
.post(isAuth, upload.single('image'), userController.addEvent)
|
||||||
|
.get(isAuth, userController.getMyEvents)
|
||||||
|
.put(isAuth, upload.single('image'), userController.updateEvent)
|
||||||
|
|
||||||
|
api.route('/user/event/:id')
|
||||||
|
.delete(isAuth, userController.delEvent)
|
||||||
|
|
||||||
|
api.route('/event/:event_id')
|
||||||
|
.get(eventController.get)
|
||||||
|
|
||||||
|
api.route('/event/meta')
|
||||||
|
.get(eventController.getMeta)
|
||||||
|
|
||||||
|
|
||||||
|
api.get('/export/feed', exportController.feed)
|
||||||
|
api.get('/export/ics', exportController.ics)
|
||||||
|
|
||||||
|
api.route('/event/:year/:month')
|
||||||
|
.get(eventController.getAll)
|
||||||
|
|
||||||
|
api.post('/user/getauthurl', isAuth, userController.getAuthURL)
|
||||||
|
api.post('/user/code', isAuth, userController.code)
|
||||||
|
|
||||||
|
module.exports = api
|
23
app/auth.js
Normal file
23
app/auth.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const config = require('./config')
|
||||||
|
const User = require('./models/user')
|
||||||
|
|
||||||
|
const Auth = {
|
||||||
|
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}})
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async isAdmin (req, res, next) {
|
||||||
|
if (req.user.is_admin) return next()
|
||||||
|
return res.status(403).send({ message: 'Admin needed' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Auth
|
2
app/config.js
Normal file
2
app/config.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
const env = process.env.NODE_ENV
|
||||||
|
module.exports = require('../config/config.' + env + '.js')
|
74
app/controller/bot.js
Normal file
74
app/controller/bot.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const { User, Event, Comment, Tag } = require('../model')
|
||||||
|
const config = require('../config')
|
||||||
|
const mail = require('../mail')
|
||||||
|
const Mastodon = require('mastodon-api')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const Op = Sequelize.Op
|
||||||
|
const moment = require('moment')
|
||||||
|
moment.locale('it')
|
||||||
|
const botController = {
|
||||||
|
bots: [],
|
||||||
|
async initialize() {
|
||||||
|
console.log('initialize bots')
|
||||||
|
const botUsers = await User.findAll({where: { mastodon_auth: { [Op.ne]: null }}})
|
||||||
|
console.log(botUsers)
|
||||||
|
botController.bots = botUsers.map(user => {
|
||||||
|
console.log('initialize bot ', user.name)
|
||||||
|
console.log('.. ', user.mastodon_auth)
|
||||||
|
const { client_id, client_secret, access_token } = user.mastodon_auth
|
||||||
|
const bot = new Mastodon({ access_token, api_url: `https://${user.instance}/api/v1/` })
|
||||||
|
console.log(bot)
|
||||||
|
const listener = bot.stream('streaming/direct')
|
||||||
|
listener.on('message', botController.message)
|
||||||
|
listener.on('error', botController.error)
|
||||||
|
return {email: user.email, bot}
|
||||||
|
})
|
||||||
|
console.log(botController.bots)
|
||||||
|
},
|
||||||
|
add (user) {
|
||||||
|
const bot = new Mastodon({ access_token: user.mastodon_auth.access_token, api_url: `https://${user.instance}/api/v1/` })
|
||||||
|
const listener = bot.stream('streaming/direct')
|
||||||
|
listener.on('message', botController.message)
|
||||||
|
listener.on('error', botController.error)
|
||||||
|
botController.bots.push({ email: user.email, bot})
|
||||||
|
},
|
||||||
|
post(user, event) {
|
||||||
|
const { bot } = botController.bots.filter(b => b.email === user.email)[0]
|
||||||
|
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' })
|
||||||
|
},
|
||||||
|
async message (msg) {
|
||||||
|
console.log(msg)
|
||||||
|
console.log(msg.data.accounts)
|
||||||
|
const replyid = msg.data.in_reply_to_id || msg.data.last_status.in_reply_to_id
|
||||||
|
if (!replyid) return
|
||||||
|
const event = await Event.findOne( {where: {activitypub_id: replyid}} )
|
||||||
|
if (!event) {
|
||||||
|
// check for comment..
|
||||||
|
const comment = await Comment.findOne( {where: { }})
|
||||||
|
}
|
||||||
|
const comment = await Comment.create({activitypub_id: msg.data.last_status.id, text: msg.data.last_status.content, author: msg.data.accounts[0].username })
|
||||||
|
event.addComment(comment)
|
||||||
|
console.log(event)
|
||||||
|
// const comment = await Comment.findOne( { where: {activitypub_id: msg.data.in_reply_to}} )
|
||||||
|
// console.log('dentro message ', data)
|
||||||
|
return
|
||||||
|
// add comment to specified event
|
||||||
|
// let comment
|
||||||
|
//if (!event) {
|
||||||
|
//const comment = await Comment.findOne({where: {activitypub_id: req.body.id}, include: Event})
|
||||||
|
//event = comment.event
|
||||||
|
//}
|
||||||
|
//const comment = new Comment(req.body)
|
||||||
|
//event.addComment(comment)
|
||||||
|
},
|
||||||
|
error (err) {
|
||||||
|
console.log('error ', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(botController.initialize, 2000)
|
||||||
|
module.exports = botController
|
70
app/controller/event.js
Normal file
70
app/controller/event.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const { User, Event, Comment, Tag, Place } = require('../model')
|
||||||
|
const config = require('../config')
|
||||||
|
const mail = require('../mail')
|
||||||
|
const moment = require('moment')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
|
||||||
|
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}})
|
||||||
|
if (!event) {
|
||||||
|
const comment = await Comment.findOne({where: {activitypub_id: req.body.id}, include: Event})
|
||||||
|
event = comment.event
|
||||||
|
}
|
||||||
|
const comment = new Comment(req.body)
|
||||||
|
event.addComment(comment)
|
||||||
|
res.json(comment)
|
||||||
|
},
|
||||||
|
|
||||||
|
// async boost (req, res) {
|
||||||
|
// const event = await Event.findById(req.body.id)
|
||||||
|
// req.user.addBoost(event)
|
||||||
|
// res.status(200)
|
||||||
|
// },
|
||||||
|
|
||||||
|
async getMeta(req, res) {
|
||||||
|
const places = await Place.findAll()
|
||||||
|
const tags = await Tag.findAll()
|
||||||
|
res.json({tags, places})
|
||||||
|
},
|
||||||
|
|
||||||
|
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 {
|
||||||
|
res.send(404)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async get(req, res) {
|
||||||
|
const id = req.params.event_id
|
||||||
|
const event = await Event.findByPk(id, { include: [User, Tag, Comment, Place]})
|
||||||
|
res.json(event)
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAll (req, res) {
|
||||||
|
const start = moment().year(req.params.year).month(req.params.month).startOf('month').subtract(1, 'week')
|
||||||
|
const end = moment().year(req.params.year).month(req.params.month).endOf('month').add(1, 'week')
|
||||||
|
console.log('start', start)
|
||||||
|
console.log('end', end)
|
||||||
|
const events = await Event.findAll({
|
||||||
|
where: {
|
||||||
|
[Sequelize.Op.and]: [
|
||||||
|
{ start_datetime: { [Sequelize.Op.gte]: start } },
|
||||||
|
{ start_datetime: { [Sequelize.Op.lte]: end } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
order: [['createdAt', 'ASC']],
|
||||||
|
include: [User, Comment, Tag, Place]
|
||||||
|
})
|
||||||
|
res.json(events)
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = eventController
|
45
app/controller/export.js
Normal file
45
app/controller/export.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const { User, Event, Comment, Tag, Place } = require('../model')
|
||||||
|
const config = require('../config')
|
||||||
|
const mail = require('../mail')
|
||||||
|
const moment = require('moment')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const ics = require('ics')
|
||||||
|
|
||||||
|
const exportController = {
|
||||||
|
async getAll (req, res) {
|
||||||
|
const events = await Event.findAll({
|
||||||
|
where: {
|
||||||
|
[Sequelize.Op.and]: [
|
||||||
|
{ start_datetime: { [Sequelize.Op.gte]: start } },
|
||||||
|
{ start_datetime: { [Sequelize.Op.lte]: end } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
order: [['createdAt', 'DESC']],
|
||||||
|
include: [User, Comment, Tag, Place]
|
||||||
|
})
|
||||||
|
res.json(events)
|
||||||
|
},
|
||||||
|
async feed (req, res) {
|
||||||
|
const events = await Event.findAll({include: [Comment, Tag, Place]})
|
||||||
|
res.type('application/rss+xml; charset=UTF-8')
|
||||||
|
res.render('feed/rss.pug', {events, config, moment})
|
||||||
|
},
|
||||||
|
async ics (req, res) {
|
||||||
|
const events = await Event.findAll({include: [Comment, Tag, Place]})
|
||||||
|
console.log(events)
|
||||||
|
const eventsMap = events.map(e => ({
|
||||||
|
start: [2019, 2, 2],
|
||||||
|
end: [2019, 2, 3],
|
||||||
|
title: e.title,
|
||||||
|
description: e.description,
|
||||||
|
location: e.place.name
|
||||||
|
}))
|
||||||
|
res.type('text/calendar; charset=UTF-8')
|
||||||
|
const { error, value } = ics.createEvents(eventsMap)
|
||||||
|
console.log(value)
|
||||||
|
res.send(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exportController
|
198
app/controller/user.js
Normal file
198
app/controller/user.js
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
const Mastodon = require('mastodon-api')
|
||||||
|
|
||||||
|
const User = require('../models/user')
|
||||||
|
const { Event, Tag, Place } = require('../models/event')
|
||||||
|
const config = require('../config')
|
||||||
|
const mail = require('../mail')
|
||||||
|
const bot = require('./bot')
|
||||||
|
|
||||||
|
const userController = {
|
||||||
|
async login (req, res) {
|
||||||
|
// find the user
|
||||||
|
const user = await User.findOne({where: { email: req.body.email }})
|
||||||
|
if (!user) {
|
||||||
|
res.status(404).json({ success: false, message: 'AUTH_FAIL' })
|
||||||
|
} else if (user) {
|
||||||
|
if (!user.is_active) {
|
||||||
|
res.status(403).json({success: false, message: 'NOT)CONFIRMED'})
|
||||||
|
}
|
||||||
|
// check if password matches
|
||||||
|
else if (!await user.comparePassword(req.body.password)) {
|
||||||
|
res.status(403).json({ success: false, message: 'AUTH_FAIL' })
|
||||||
|
} else {
|
||||||
|
// if user is found and password is right
|
||||||
|
// create a token
|
||||||
|
const payload = { email: user.email }
|
||||||
|
var token = jwt.sign(payload, config.secret)
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Enjoy your token!',
|
||||||
|
token,
|
||||||
|
user
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async setToken (req, res) {
|
||||||
|
req.user.mastodon_auth = req.body
|
||||||
|
await req.user.save()
|
||||||
|
res.json(req.user)
|
||||||
|
},
|
||||||
|
|
||||||
|
async delEvent (req, res) {
|
||||||
|
//check if event is mine
|
||||||
|
const event = await Event.findByPk(req.params.id)
|
||||||
|
if (event && (req.user.is_admin || req.user.id === event.userId))
|
||||||
|
{
|
||||||
|
await event.destroy()
|
||||||
|
res.sendStatus(200)
|
||||||
|
} else {
|
||||||
|
res.sendStatus(404)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async addEvent (req, res, next) {
|
||||||
|
const body = req.body
|
||||||
|
const eventDetails = {
|
||||||
|
title: body.title,
|
||||||
|
description: body.description,
|
||||||
|
multidate: body.multidate,
|
||||||
|
start_datetime: body.start_datetime,
|
||||||
|
end_datetime: body.end_datetime
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.file) {
|
||||||
|
eventDetails.image_path = req.file.path
|
||||||
|
}
|
||||||
|
|
||||||
|
//create place
|
||||||
|
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(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: body.tags }})
|
||||||
|
await event.addTags(tags)
|
||||||
|
}
|
||||||
|
await req.user.addEvent(event)
|
||||||
|
event = await Event.findByPk(event.id, {include: [User, Tag, Place]})
|
||||||
|
// check if bot exists
|
||||||
|
if (req.user.mastodon_auth) {
|
||||||
|
const post = await bot.post(req.user, event)
|
||||||
|
}
|
||||||
|
return res.json(event)
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateEvent (req, res) {
|
||||||
|
const body = req.body
|
||||||
|
const event = await Event.findByPk(body.id)
|
||||||
|
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('catch', e)
|
||||||
|
}
|
||||||
|
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: body.tags }})
|
||||||
|
await event.addTags(tags)
|
||||||
|
}
|
||||||
|
const newEvent = await Event.findByPk(event.id, {include: [User, Tag, Place]})
|
||||||
|
// check if bot exists
|
||||||
|
if (req.user.mastodon_auth) {
|
||||||
|
const post = await bot.post(req.user, newEvent)
|
||||||
|
}
|
||||||
|
return res.json(newEvent)
|
||||||
|
},
|
||||||
|
|
||||||
|
async getMyEvents (req, res) {
|
||||||
|
const events = await req.user.getEvents()
|
||||||
|
res.json(events)
|
||||||
|
},
|
||||||
|
|
||||||
|
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.instance = instance
|
||||||
|
req.user.mastodon_auth = { client_id, client_secret }
|
||||||
|
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.instance
|
||||||
|
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 botController.add(token)
|
||||||
|
res.json(req.user)
|
||||||
|
} catch (e) {
|
||||||
|
res.json(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async current (req, res) {
|
||||||
|
res.json(req.user)
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAll (req, res) {
|
||||||
|
const users = await User.findAll({
|
||||||
|
order: [['createdAt', 'DESC']]
|
||||||
|
})
|
||||||
|
res.json(users)
|
||||||
|
},
|
||||||
|
|
||||||
|
async update (req, res) {
|
||||||
|
const user = await User.findByPk(req.body.id)
|
||||||
|
if (user) {
|
||||||
|
await user.update(req.body)
|
||||||
|
res.json(user)
|
||||||
|
} else {
|
||||||
|
res.send(400)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async register (req, res) {
|
||||||
|
try {
|
||||||
|
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 }
|
||||||
|
const token = jwt.sign(payload, config.secret)
|
||||||
|
res.json({ user, token })
|
||||||
|
} catch (e) {
|
||||||
|
res.status(404).json(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = userController
|
10
app/db.js
Normal file
10
app/db.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const env = process.env.NODE_ENV || 'development'
|
||||||
|
const conf = require('../config/config.' + env + '.js')
|
||||||
|
const db = new Sequelize(conf.db)
|
||||||
|
|
||||||
|
|
||||||
|
db.sync({ force: true })
|
||||||
|
// db.sync()
|
||||||
|
|
||||||
|
module.exports = db
|
34
app/mail.js
Normal file
34
app/mail.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
const Email = require('email-templates')
|
||||||
|
const path = require('path')
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
const mail = {
|
||||||
|
send (addresses, template, locals) {
|
||||||
|
locals.locale = config.locale
|
||||||
|
const email = new Email({
|
||||||
|
juice: true,
|
||||||
|
juiceResources: {
|
||||||
|
preserveImportant: true,
|
||||||
|
webResources: {
|
||||||
|
relativeTo: path.join(__dirname, '..', 'emails')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
from: 'Gancio <eventi@cisti.org>'
|
||||||
|
},
|
||||||
|
send: true,
|
||||||
|
i18n: {},
|
||||||
|
transport: config.smtp
|
||||||
|
})
|
||||||
|
return email.send({
|
||||||
|
template,
|
||||||
|
message: {
|
||||||
|
to: addresses,
|
||||||
|
bcc: config.admin
|
||||||
|
},
|
||||||
|
locals
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = mail
|
4
app/model.js
Normal file
4
app/model.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const User = require('./models/user')
|
||||||
|
const { Event, Comment, Tag, Place } = require('./models/event')
|
||||||
|
|
||||||
|
module.exports = { User, Event, Comment, Tag, Place }
|
52
app/models/event.js
Normal file
52
app/models/event.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const db = require('../db')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const User = require('./user')
|
||||||
|
|
||||||
|
const Event = db.define('event', {
|
||||||
|
title: Sequelize.STRING,
|
||||||
|
description: Sequelize.STRING,
|
||||||
|
multidate: Sequelize.BOOLEAN,
|
||||||
|
start_datetime: { type: Sequelize.DATE, index: true},
|
||||||
|
end_datetime: {type: Sequelize.DATE, index: true},
|
||||||
|
image_path: Sequelize.STRING,
|
||||||
|
activitypub_id: { type: Sequelize.INTEGER, index: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
const Tag = db.define('tag', {
|
||||||
|
tag: { type: Sequelize.STRING, index: true, unique: true, primaryKey: true},
|
||||||
|
color: { type: Sequelize.STRING }
|
||||||
|
})
|
||||||
|
|
||||||
|
const Comment = db.define('comment', {
|
||||||
|
activitypub_id: { type: Sequelize.INTEGER, index: true },
|
||||||
|
author: Sequelize.STRING,
|
||||||
|
text: Sequelize.STRING,
|
||||||
|
})
|
||||||
|
|
||||||
|
const MailSubscription = db.define('subscription' , {
|
||||||
|
filters: Sequelize.JSON,
|
||||||
|
mail: Sequelize.TEXT,
|
||||||
|
send_on_add: Sequelize.BOOLEAN,
|
||||||
|
send_reminder: Sequelize.INTEGER,
|
||||||
|
})
|
||||||
|
|
||||||
|
const Place = db.define('place', {
|
||||||
|
name: { type: Sequelize.STRING, unique: true, index: true },
|
||||||
|
address: { type: Sequelize.STRING }
|
||||||
|
})
|
||||||
|
|
||||||
|
Comment.belongsTo(Event)
|
||||||
|
Event.hasMany(Comment)
|
||||||
|
|
||||||
|
Event.belongsToMany(Tag, {through: 'tagEvent'})
|
||||||
|
Tag.belongsToMany(Event, {through: 'tagEvent'})
|
||||||
|
|
||||||
|
Event.belongsToMany(User, {through: 'boost'})
|
||||||
|
Event.belongsTo(User)
|
||||||
|
Event.belongsTo(Place)
|
||||||
|
|
||||||
|
User.hasMany(Event)
|
||||||
|
Place.hasMany(Event)
|
||||||
|
User.belongsToMany(User, {through: 'userFollower', as: 'follower'})
|
||||||
|
|
||||||
|
module.exports = { Event, Comment, Tag, Place }
|
33
app/models/user.js
Normal file
33
app/models/user.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
const bcrypt = require('bcrypt')
|
||||||
|
const db = require('../db')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
|
||||||
|
const User = db.define('user', {
|
||||||
|
email: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
unique: {msg: 'Email already exists'},
|
||||||
|
index: true, allowNull: false },
|
||||||
|
description: Sequelize.TEXT,
|
||||||
|
password: Sequelize.STRING,
|
||||||
|
is_admin: Sequelize.BOOLEAN,
|
||||||
|
is_active: Sequelize.BOOLEAN,
|
||||||
|
instance: Sequelize.STRING,
|
||||||
|
mastodon_auth: Sequelize.JSON
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
User.prototype.comparePassword = async function (pwd) {
|
||||||
|
if (!this.password) return false
|
||||||
|
const ret = await bcrypt.compare(pwd, this.password)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
User.beforeSave(async (user, options) => {
|
||||||
|
if (user.changed('password')) {
|
||||||
|
const salt = await bcrypt.genSalt(10)
|
||||||
|
const hash = await bcrypt.hash(user.password, salt)
|
||||||
|
user.password = hash
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = User
|
35
config/config.development.js
Normal file
35
config/config.development.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// environment
|
||||||
|
env: 'development',
|
||||||
|
locale: 'it',
|
||||||
|
|
||||||
|
title: 'Gancio',
|
||||||
|
description: 'Un calendario dei movimenti piemontesi',
|
||||||
|
|
||||||
|
// base url
|
||||||
|
baseurl: 'http://localhost:8080',
|
||||||
|
apiurl: 'http://localhost:9000/api',
|
||||||
|
|
||||||
|
|
||||||
|
// db configuration
|
||||||
|
db: {
|
||||||
|
'storage': path.join(__dirname, '/../db.sqlite'),
|
||||||
|
'dialect': 'sqlite'
|
||||||
|
},
|
||||||
|
admin: 'lesion@autistici.org',
|
||||||
|
|
||||||
|
// email configuration
|
||||||
|
smtp: {
|
||||||
|
host: 'mail.example.com',
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: 'user@example.com',
|
||||||
|
pass: 'password'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// jwt secret
|
||||||
|
secret: 'nonosecretsuper'
|
||||||
|
}
|
30
config/config.production.js
Normal file
30
config/config.production.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module.exports = {
|
||||||
|
// environment
|
||||||
|
env: 'production',
|
||||||
|
locale: 'en',
|
||||||
|
|
||||||
|
title: 'Put here your site name',
|
||||||
|
description: 'A calendar for radical communities',
|
||||||
|
|
||||||
|
// base url
|
||||||
|
baseurl: 'https://example.com',
|
||||||
|
apiurl: 'https://example.com/api',
|
||||||
|
|
||||||
|
// db configuration
|
||||||
|
db: {
|
||||||
|
},
|
||||||
|
admin: 'admin@example.com',
|
||||||
|
|
||||||
|
// email configuration
|
||||||
|
smtp: {
|
||||||
|
host: 'mail.example.com',
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: 'admin@example.com',
|
||||||
|
pass: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// jwt secret
|
||||||
|
secret: 'randomstringhere'
|
||||||
|
}
|
8
emails/mail.css
Normal file
8
emails/mail.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table, th, td {
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
6
emails/register/html.pug
Normal file
6
emails/register/html.pug
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
h4 Gancio
|
||||||
|
|
||||||
|
p= t('registration_email')
|
||||||
|
|
||||||
|
small --
|
||||||
|
small https://cisti.org
|
1
emails/register/subject.pug
Normal file
1
emails/register/subject.pug
Normal file
|
@ -0,0 +1 @@
|
||||||
|
= `[Gancio] Richiesta registrazione`
|
3
locales/en.json
Normal file
3
locales/en.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
|
||||||
|
}
|
3
locales/es.json
Normal file
3
locales/es.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"registration_email": "registration_email"
|
||||||
|
}
|
3
locales/it.json
Normal file
3
locales/it.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
|
||||||
|
}
|
3
locales/zh.json
Normal file
3
locales/zh.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"registration_email": "registration_email"
|
||||||
|
}
|
33
package.json
Normal file
33
package.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "gancio",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"serve": "NODE_ENV=production PORT=9000 nodemon server.js",
|
||||||
|
"dev": "NODE_ENV=development PORT=9000 nodemon server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^3.0.2",
|
||||||
|
"body-parser": "^1.15.0",
|
||||||
|
"cors": "^2.8.4",
|
||||||
|
"email-templates": "^5.0.2",
|
||||||
|
"express": "^4.13.4",
|
||||||
|
"ics": "^2.13.1",
|
||||||
|
"jsonwebtoken": "^5.7.0",
|
||||||
|
"mastodon-api": "^1.3.0",
|
||||||
|
"mongoose": "^5.2.17",
|
||||||
|
"morgan": "^1.7.0",
|
||||||
|
"multer": "^1.4.1",
|
||||||
|
"mysql2": "^1.6.4",
|
||||||
|
"pug": "^2.0.3",
|
||||||
|
"sequelize": "^4.41.0",
|
||||||
|
"sqlite3": "^4.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^5.8.0",
|
||||||
|
"eslint-config-standard": "^12.0.0",
|
||||||
|
"eslint-plugin-import": "^2.14.0",
|
||||||
|
"eslint-plugin-node": "^8.0.0",
|
||||||
|
"eslint-plugin-promise": "^4.0.1",
|
||||||
|
"eslint-plugin-standard": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
19
server.js
Normal file
19
server.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const express = require('express')
|
||||||
|
const app = express()
|
||||||
|
const bodyParser = require('body-parser')
|
||||||
|
const api = require('./app/api')
|
||||||
|
const cors = require('cors')
|
||||||
|
const path = require('path')
|
||||||
|
const db = require('./app/db')
|
||||||
|
const port = process.env.PORT || 8080
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
app.use('/static', express.static(path.join(__dirname, 'uploads')))
|
||||||
|
app.use('/uploads', express.static('uploads'))
|
||||||
|
app.use('/', express.static(path.join(__dirname, 'client', 'dist')))
|
||||||
|
app.use(cors())
|
||||||
|
app.use('/api', api)
|
||||||
|
|
||||||
|
app.listen(port)
|
||||||
|
console.log('Magic happens at http://localhost:' + port)
|
23
views/feed/rss.pug
Normal file
23
views/feed/rss.pug
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
doctype xml
|
||||||
|
rss(version='2.0', xmlns:atom='<a href="http://www.w3.org/2005/Atom" rel="nofollow">http://www.w3.org/2005/Atom</a>')
|
||||||
|
channel
|
||||||
|
title #{config.title}
|
||||||
|
link <a href="#{config.baseurl}" rel="nofollow">#{config.baseurl}</a>
|
||||||
|
atom:link(href='<a href="#{config.apiurl}/export/feed/rss" rel="nofollow">#{config.apiurl}/export/feed/rss</a>', rel='self', type='application/rss+xml')
|
||||||
|
description #{config.description}
|
||||||
|
language #{config.locale}
|
||||||
|
//- if events.length
|
||||||
|
lastBuildDate= new Date(posts[0].publishedAt).toUTCString()
|
||||||
|
each event in events
|
||||||
|
item
|
||||||
|
title= event.title
|
||||||
|
link <a href=#{config.baseurl}/event/#{event.id}" rel="nofollow">#{config.baseurl}/event/#{event.id}</a>
|
||||||
|
description
|
||||||
|
| <![CDATA[
|
||||||
|
| <h4>#{event.title}</h4>
|
||||||
|
| <strong>#{event.place.name} - #{event.place.address}</strong>
|
||||||
|
| #{moment(event.start_datetime).format("ddd, D MMMM HH:mm")}<br/>
|
||||||
|
| !{event.description}
|
||||||
|
| ]]>
|
||||||
|
pubDate= new Date(event.start_datetime).toUTCString()
|
||||||
|
guid(isPermaLink='false') <a href="#{config.baseurl}/event/#{event.id}" rel="nofollow">#{config.baseurl}/event/#{event.id}</a>
|
Loading…
Reference in a new issue