start with nuxt

This commit is contained in:
lesion
2019-04-03 00:25:12 +02:00
parent afa6ad2a6b
commit 88b43f9bb1
54 changed files with 2742 additions and 0 deletions

48
server/api/auth.js Normal file
View File

@@ -0,0 +1,48 @@
const jwt = require('jsonwebtoken')
const { Op } = require('sequelize')
const config = require('./config')
const User = require('./models/user')
const Auth = {
fillUser(req, res, next) {
const token =
req.body.token || req.params.token || req.headers['x-access-token']
if (!token) return next()
jwt.verify(token, config.secret, async (err, decoded) => {
if (err) return next()
req.user = await User.findOne({
where: { email: { [Op.eq]: decoded.email }, is_active: true }
})
next()
})
},
isAuth(req, res, next) {
const token =
(req.body && req.body.token) ||
req.params.token ||
req.headers['x-access-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 })
}
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()
})
},
isAdmin(req, res, next) {
if (req.user.is_admin && req.user.is_active) return next()
return res.status(403).send({ message: 'Admin needed' })
}
}
module.exports = Auth

27
server/api/config.js Normal file
View File

@@ -0,0 +1,27 @@
/* backend configuration */
const env = process.env.NODE_ENV || 'development'
const db = require('./config/config.json')[env]
module.exports = {
locale: process.env.LOCALE || 'it',
title: process.env.TITLE || 'GANCIO',
description: process.env.DESCRIPTION || 'A calendar for radical communities',
baseurl: process.env.BASE_URL || 'http://localhost:8080',
apiurl:
env === 'production'
? process.env.BASE_URL + '/api'
: 'http://localhost:9000',
db,
admin: process.env.ADMIN_EMAIL,
smtp: {
host: process.env.SMTP_HOST,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
},
secret: process.env.SECRET || 'notsosecret'
}

View File

@@ -0,0 +1,14 @@
{
"development": {
"storage": "/home/les/dev/hacklab/gancio/db.sqlite",
"dialect": "sqlite",
"logging": false
},
"production": {
"username": "docker",
"password": "docker",
"database": "gancio",
"host": "db",
"dialect": "postgres"
}
}

View File

@@ -0,0 +1,82 @@
// 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 fs = require('fs')
const path = require('path')
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.mastodon_instance}/api/v1/` })
// 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, token) {
// const bot = new Mastodon({ access_token: user.mastodon_auth.access_token, api_url: `https://${user.mastodon_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 })
// },
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.length > 200 ? event.description.substr(0, 200) + '...' : event.description} - ${event.tags.map(t => '#' + t.tag).join(' ')} ${config.baseurl}/event/${event.id}`
let media
if (event.image_path) {
const file = path.join(__dirname, '..', '..', 'uploads', event.image_path)
if (fs.statSync(file)) {
media = await bot.post('media', { file: fs.createReadStream(file) })
}
}
return bot.post('statuses', { status, visibility: 'public', media_ids: media ? [media.data.id] : [] })
}
// 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)
// 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

View File

@@ -0,0 +1,169 @@
const crypto = require('crypto')
const moment = require('moment')
const { Op } = require('sequelize')
const lodash = require('lodash')
const { User, Event, Comment, Tag, Place, Notification } = require('../model')
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: { [Op.eq]: req.body.id } } })
if (!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)
event.addComment(comment)
res.json(comment)
},
async getMeta(req, res) {
const places = await Place.findAll()
const tags = await Tag.findAll()
res.json({ tags, places })
},
async getNotifications(event) {
function match(event, filters) {
// matches if no filter specified
if (!filters) return true
// check for visibility
if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) return false
if (!filters.tags && !filters.places) 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)
if (m.length > 0) return true
}
if (filters.places.length) {
if (filters.places.find(p => p === event.place.name)) {
return true
}
}
}
const notifications = await Notification.findAll()
// get notification that matches with selected event
return notifications.filter(notification => match(event, notification.filters))
},
async updateTag(req, res) {
const tag = await Tag.findByPk(req.body.tag)
if (tag) {
res.json(await tag.update(req.body))
} else {
res.sendStatus(404)
}
},
async updatePlace(req, res) {
const place = await Place.findByPk(req.body.id)
await place.update(req.body)
res.json(place)
},
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 confirm(req, res) {
const id = req.params.event_id
const event = await Event.findByPk(id)
try {
await event.update({ is_visible: true })
// insert notification
const notifications = await eventController.getNotifications(event)
await event.setNotifications(notifications)
res.sendStatus(200)
} catch (e) {
res.sendStatus(404)
}
},
async unconfirm(req, res) {
const id = req.params.event_id
const event = await Event.findByPk(id)
try {
await event.update({ is_visible: false })
res.sendStatus(200)
} catch (e) {
res.sendStatus(404)
}
},
async getUnconfirmed(req, res) {
const events = await Event.findAll({
where: {
is_visible: false
},
order: [['start_datetime', 'ASC']],
include: [Tag, Place]
})
res.json(events)
},
async addNotification(req, res) {
try {
const notification = {
filters: { is_visible: true },
email: req.body.email,
type: 'mail',
remove_code: crypto.randomBytes(16).toString('hex')
}
await Notification.create(notification)
res.sendStatus(200)
} catch (e) {
res.sendStatus(404)
}
},
async delNotification(req, res) {
const remove_code = req.params.code
try {
const notification = await Notification.findOne({ where: { remove_code: { [Op.eq]: remove_code } } })
await notification.destroy()
} catch (e) {
return res.sendStatus(404)
}
res.sendStatus(200)
},
async getAll(req, res) {
console.log('sono qui dentro !')
// this is due how v-calendar shows dates
const start = moment().year(req.params.year).month(req.params.month)
.startOf('month').startOf('isoWeek')
let end = moment().year(req.params.year).month(req.params.month).endOf('month')
const shownDays = end.diff(start, 'days')
if (shownDays <= 34) end = end.add(1, 'week')
end = end.endOf('isoWeek')
const events = await Event.findAll({
// where: {
// is_visible: true,
// [Op.and]: [
// { start_datetime: { [Op.gte]: start } },
// { start_datetime: { [Op.lte]: end } }
// ]
// },
// order: [['start_datetime', 'ASC']],
// include: [
// { model: User, required: false },
// Comment,
// Tag,
// { model: Place, required: false }
// ]
})
console.log(events)
res.json(events)
}
}
module.exports = eventController

View File

@@ -0,0 +1,64 @@
const { Event, Comment, Tag, Place } = require('../model')
const { Op } = require('sequelize')
const config = require('../config')
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
const tags = req.query.tags
const places = req.query.places
const whereTag = {}
const wherePlace = {}
const yesterday = moment().subtract('1', 'day')
if (tags) {
whereTag.tag = tags.split(',')
}
if (places) {
wherePlace.name = places.split(',')
}
const events = await Event.findAll({
where: { is_visible: true, start_datetime: { [Op.gte]: yesterday } },
include: [Comment, {
model: Tag,
where: whereTag
}, { model: Place, where: wherePlace } ]
})
switch (type) {
case 'feed':
return exportController.feed(res, events.slice(0, 20))
case 'ics':
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 })
},
async ics (res, events) {
const eventsMap = events.map(e => {
const tmpStart = moment(e.start_datetime)
const tmpEnd = moment(e.end_datetime)
const start = [tmpStart.year(), tmpStart.month() + 1, tmpStart.date(), tmpStart.hour(), tmpStart.minute()]
const end = [tmpEnd.year(), tmpEnd.month() + 1, tmpEnd.date(), tmpEnd.hour(), tmpEnd.minute()]
return {
start,
end,
title: e.title,
description: e.description,
location: e.place.name + ' ' + e.place.address
}
})
res.type('text/calendar; charset=UTF-8')
const { error, value } = ics.createEvents(eventsMap)
console.log(error, value)
res.send(value)
}
}
module.exports = exportController

View File

@@ -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

View File

@@ -0,0 +1,283 @@
const jwt = require('jsonwebtoken')
const Mastodon = require('mastodon-api')
const User = require('../models/user')
const { Event, Tag, Place } = require('../models/event')
const settingsController = require('./settings')
const eventController = require('./event')
const config = require('../config')
const mail = require('../mail')
const { Op } = require('sequelize')
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
const userController = {
async login (req, res) {
// find the user
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) {
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) {
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.resolve(__dirname, '..', '..', 'uploads', event.image_path)
const old_thumb_path = path.resolve(__dirname, '..', '..', 'uploads', 'thumb', event.image_path)
await fs.unlink(old_path)
await fs.unlink(old_thumb_path)
}
await event.destroy()
res.sendStatus(200)
} else {
res.sendStatus(403)
}
},
// ADD EVENT
async addEvent (req, res) {
const body = req.body
// remove description tag and create anchor tags
const description = body.description
.replace(/(<([^>]+)>)/ig, '')
.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>')
const eventDetails = {
title: body.title,
description,
multidate: body.multidate,
start_datetime: body.start_datetime,
end_datetime: body.end_datetime,
is_visible: !!req.user
}
if (req.file) {
eventDetails.image_path = req.file.filename
}
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.error(e)
}
// 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 event.addTags(tags)
}
if (req.user) await req.user.addEvent(event)
event = await Event.findByPk(event.id, { include: [User, Tag, Place] })
// insert notifications
const notifications = await eventController.getNotifications(event)
await event.setNotifications(notifications)
return res.json(event)
},
async updateEvent (req, res) {
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(__dirname, '..', '..', 'uploads', event.image_path)
const old_thumb_path = path.resolve(__dirname, '..', '..', 'uploads', '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 = body.description
.replace(/(<([^>]+)>)/ig, '') // remove all tags from description
.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>') // add links
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: [User, Tag, Place] })
return res.json(newEvent)
},
async getAuthURL (req, res) {
const instance = req.body.instance
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, 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}`, 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)
}
},
async forgotPassword (req, res) {
const email = req.body.email
const user = await User.findOne({ where: { email: { [Op.eq]: email } } })
if (!user) return res.sendStatus(200)
user.recover_code = crypto.randomBytes(16).toString('hex')
mail.send(user.email, 'recover', { user, config })
await user.save()
res.sendStatus(200)
},
async checkRecoverCode (req, res) {
const recover_code = req.body.recover_code
if (!recover_code) return res.sendStatus(400)
const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } })
if (!user) return res.sendStatus(400)
res.json(user)
},
async updatePasswordWithRecoverCode (req, res) {
const recover_code = req.body.recover_code
if (!recover_code) return res.sendStatus(400)
const password = req.body.password
const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } })
if (!user) return res.sendStatus(400)
user.password = password
await user.save()
res.sendStatus(200)
},
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) {
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 {
res.sendStatus(400)
}
},
async register (req, res) {
const n_users = await User.count()
try {
if (n_users === 0) {
// the first registered user will be an active admin
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, config.admin], 'register', { user, config })
} catch (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

5
server/api/db.js Normal file
View File

@@ -0,0 +1,5 @@
const Sequelize = require('sequelize')
const conf = require('./config.js')
const db = new Sequelize(conf.db)
// db.sync({force: true})
module.exports = db

86
server/api/index.js Normal file
View File

@@ -0,0 +1,86 @@
const express = require('express')
const multer = require('multer')
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 storage = require('./storage')({
destination: 'uploads/'
})
const upload = multer({ storage })
const api = express.Router()
// login
api.post('/login', userController.login)
api.post('/user/recover', userController.forgotPassword)
api.post('/user/check_recover_code', userController.checkRecoverCode)
api.post('/user/recover_password', userController.updatePasswordWithRecoverCode)
api
.route('/user')
// register
.post(userController.register)
// get current user
.get(isAuth, userController.current)
// update user (eg. confirm)
.put(isAuth, isAdmin, userController.update)
// get all users
api.get('/users', isAuth, isAdmin, userController.getAll)
// update a tag (modify color)
api.put('/tag', isAuth, isAdmin, eventController.updateTag)
// update a place (modify address..)
api.put('/place', isAuth, isAdmin, eventController.updatePlace)
api
.route('/user/event')
// add event
.post(fillUser, upload.single('image'), userController.addEvent)
// update event
.put(isAuth, upload.single('image'), userController.updateEvent)
// remove event
api.delete('/user/event/:id', isAuth, userController.delEvent)
// get tags/places
api.get('/event/meta', eventController.getMeta)
// get unconfirmed events
api.get('/event/unconfirmed', isAuth, isAdmin, eventController.getUnconfirmed)
// add event notification
api.post('/event/notification', eventController.addNotification)
api.delete('/event/notification/:code', eventController.delNotification)
api.get('/settings', settingsController.getAdminSettings)
api.post('/settings', settingsController.setAdminSetting)
// get event
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)
// get events in this range
api.get('/event/:year/:month', eventController.getAll)
// mastodon oauth auth
api.post('/user/getauthurl', isAuth, userController.getAuthURL)
api.post('/user/code', isAuth, userController.code)
module.exports = api

44
server/api/mail.js Normal file
View File

@@ -0,0 +1,44 @@
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) {
const email = new Email({
views: { root: path.join(__dirname, 'emails') },
juice: true,
juiceResources: {
preserveImportant: true,
webResources: {
relativeTo: path.join(__dirname, 'emails')
}
},
message: {
from: `${config.title} <${config.smtp.auth.user}>`
},
send: true,
i18n: {
locales: ['en', 'es', 'it'],
defaultLocale: config.locale
},
transport: config.smtp
})
return email.send({
template,
message: {
to: addresses,
bcc: config.admin
},
locals: {
...locals,
locale: config.locale,
config,
datetime: datetime => moment(datetime).format('ddd, D MMMM HH:mm')
}
})
}
}
module.exports = mail

14
server/api/model.js Normal file
View File

@@ -0,0 +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,
Settings
}

View File

@@ -0,0 +1,72 @@
const Sequelize = require('sequelize')
const db = require('../db')
const User = require('./user')
const Event = db.define('event', {
title: Sequelize.STRING,
description: Sequelize.TEXT,
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 },
is_visible: Sequelize.BOOLEAN
})
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 Notification = db.define('notification', {
filters: Sequelize.JSON,
email: Sequelize.STRING,
remove_code: Sequelize.STRING,
type: {
type: Sequelize.ENUM,
values: ['mail', 'admin_email', 'mastodon']
}
})
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' })
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 })
Event.belongsTo(User)
Event.belongsTo(Place)
User.hasMany(Event)
Place.hasMany(Event)
// async function init() {
// await Notification.findOrCreate({ where: { type: 'mastodon', filters: { is_visible: true } } })
// await Notification.findOrCreate({ where: { type: 'admin_email', filters: { is_visible: false } } })
// }
// init()
module.exports = { Event, Comment, Tag, Place, Notification, EventNotification }

View File

@@ -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;

View File

@@ -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

34
server/api/models/user.js Normal file
View File

@@ -0,0 +1,34 @@
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,
recover_code: Sequelize.STRING,
is_admin: Sequelize.BOOLEAN,
is_active: Sequelize.BOOLEAN,
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

62
server/api/storage.js Normal file
View File

@@ -0,0 +1,62 @@
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) {
const that = this
that.getDestination(req, file, function (err, destination) {
if (err) return cb(err)
const filename = crypto.randomBytes(16).toString('hex') + '.jpg'
const finalPath = path.join(destination, filename)
const thumbPath = path.join(destination, 'thumb', filename)
const outStream = fs.createWriteStream(finalPath)
const thumbStream = fs.createWriteStream(thumbPath)
const resizer = sharp().resize(800).jpeg({ quality: 80 })
const thumbnailer = sharp().resize(400).jpeg({ quality: 60 })
file.stream.pipe(thumbnailer).pipe(thumbStream)
thumbStream.on('error', e => console.log('thumbStream error ', e))
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) {
let path = file.path
delete file.destination
delete file.filename
delete file.path
fs.unlink(path, cb)
}
module.exports = function (opts) {
return new DiskStorage(opts)
}

36
server/index.js Normal file
View File

@@ -0,0 +1,36 @@
const express = require('express')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = express()
const cors = require('cors')
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
const { host, port } = nuxt.options.server
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
} else {
await nuxt.ready()
}
// Give nuxt middleware to express
app.use(cors())
app.use(nuxt.render)
// Listen the server
app.listen(port, host)
consola.ready({
message: `Server listening on http://${host}:${port}`,
badge: true
})
}
start()