cleaning master
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
client/node_modules
|
||||
node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
21
.env
21
.env
@@ -1,21 +0,0 @@
|
||||
NODE_ENV=production
|
||||
|
||||
# complete url (use https here!)
|
||||
BASE_URL=http://localhost:12300
|
||||
|
||||
# node server will listen here
|
||||
PORT=12300
|
||||
|
||||
# your instance's name and description
|
||||
TITLE=Gancio
|
||||
|
||||
DESCRIPTION="description"
|
||||
|
||||
# where emails comes from
|
||||
ADMIN_EMAIL=admin@example.org
|
||||
SMTP_HOST=mail.example.org
|
||||
SMTP_USER=admin@example.org
|
||||
SMTP_PASS=password
|
||||
|
||||
# please put a random string here
|
||||
SECRET=secret
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
"extends": "standard",
|
||||
"rules": {
|
||||
"camelcase": 0
|
||||
}
|
||||
};
|
||||
25
.gitignore
vendored
25
.gitignore
vendored
@@ -1,25 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
client/dist
|
||||
|
||||
|
||||
uploads
|
||||
|
||||
# local env files
|
||||
.env.production
|
||||
|
||||
db.sqlite
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
33
Dockerfile
33
Dockerfile
@@ -1,33 +0,0 @@
|
||||
|
||||
FROM node:10
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
COPY package.json .
|
||||
COPY pm2.json .
|
||||
COPY .env.production .
|
||||
|
||||
# install backend dependencies
|
||||
RUN yarn
|
||||
|
||||
# copy source
|
||||
COPY app app/
|
||||
COPY client client/
|
||||
|
||||
# install nodemon
|
||||
RUN yarn global add pm2
|
||||
|
||||
WORKDIR /usr/src/app/client
|
||||
|
||||
# install frontend dependencies
|
||||
RUN yarn
|
||||
|
||||
# build frontend
|
||||
RUN export $(cat /usr/src/app/.env.production); yarn build
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
EXPOSE 12300
|
||||
|
||||
CMD [ "pm2-runtime", "start", "pm2.json" ]
|
||||
41
README.md
41
README.md
@@ -1,41 +0,0 @@
|
||||
# gancio
|
||||
an event manager for radical communities
|
||||
|
||||
:warning: Gancio is under heavy development,
|
||||
if something is not working as expected, it's expected :D
|
||||
|
||||
#### Install
|
||||
We provide a docker way to run **gancio** but you can also manually install it.
|
||||
```
|
||||
git clone https://git.lattuga.net/lesion/gancio.git
|
||||
cd gancio
|
||||
|
||||
# copy .env into .env.production and edit it
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
#### Development
|
||||
both backend (`/app`) and frontend(`/client`) are in this repo.
|
||||
|
||||
backend stack: node.js, express, sequelize.
|
||||
frontend stack: vue, webpack, boostrap
|
||||
|
||||
```
|
||||
git clone https://git.lattuga.net/lesion/gancio.git
|
||||
cd gancio
|
||||
# install back-end dependencies
|
||||
yarn
|
||||
# run back-end in development mode
|
||||
yarn dev
|
||||
|
||||
cd client
|
||||
# install front-end dependencies
|
||||
yarn
|
||||
# run front-end in development mode
|
||||
yarn dev
|
||||
```
|
||||
|
||||
|
||||
#### Migrate / Backup
|
||||
- db
|
||||
- images
|
||||
79
app/api.js
79
app/api.js
@@ -1,79 +0,0 @@
|
||||
const express = require('express')
|
||||
const { fillUser, isAuth, isAdmin } = require('./auth')
|
||||
const eventController = require('./controller/event')
|
||||
const exportController = require('./controller/export')
|
||||
const userController = require('./controller/user')
|
||||
const settingsController = require('./controller/settings')
|
||||
|
||||
// const botController = require('./controller/bot')
|
||||
const multer = require('multer')
|
||||
|
||||
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
|
||||
32
app/auth.js
32
app/auth.js
@@ -1,32 +0,0 @@
|
||||
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']
|
||||
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()
|
||||
})
|
||||
},
|
||||
async isAuth (req, res, next) {
|
||||
const token = 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()
|
||||
})
|
||||
},
|
||||
async 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
|
||||
@@ -1,24 +0,0 @@
|
||||
/* 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'
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"development": {
|
||||
"storage": "/home/les/dev/hacklab/eventi/db.sqlite",
|
||||
"dialect": "sqlite",
|
||||
"logging": false
|
||||
},
|
||||
"production": {
|
||||
"username": "docker",
|
||||
"password": "docker",
|
||||
"database": "gancio",
|
||||
"host": "db",
|
||||
"dialect": "postgres"
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// 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
|
||||
@@ -1,162 +0,0 @@
|
||||
const { User, Event, Comment, Tag, Place, Notification } = require('../model')
|
||||
const moment = require('moment')
|
||||
const { Op } = require('sequelize')
|
||||
const lodash = require('lodash')
|
||||
const crypto = require('crypto')
|
||||
|
||||
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) {
|
||||
// this is due how v-calendar shows dates
|
||||
let 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: [User, Comment, Tag, Place]
|
||||
})
|
||||
res.json(events)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = eventController
|
||||
@@ -1,64 +0,0 @@
|
||||
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
|
||||
@@ -1,27 +0,0 @@
|
||||
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
|
||||
@@ -1,283 +0,0 @@
|
||||
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
|
||||
61
app/cron.js
61
app/cron.js
@@ -1,61 +0,0 @@
|
||||
const mail = require('./mail')
|
||||
const bot = require('./controller/bot')
|
||||
const settingsController = require('./controller/settings')
|
||||
const config = require('./config.js')
|
||||
|
||||
const { Event, Notification, EventNotification,
|
||||
User, Place, Tag } = require('./model')
|
||||
let settings
|
||||
|
||||
async function sendNotification (notification, event, eventNotification) {
|
||||
const promises = []
|
||||
switch (notification.type) {
|
||||
case 'mail':
|
||||
return mail.send(notification.email, 'event', { event, config, notification })
|
||||
case 'admin_email':
|
||||
const admins = await User.findAll({ where: { is_admin: true } })
|
||||
const admin_emails = admins.map(admin => admin.email)
|
||||
return mail.send(admin_emails, 'event', { event, to_confirm: true, notification })
|
||||
case 'mastodon':
|
||||
// instance publish
|
||||
if (settings.mastodon_auth.instance && settings.mastodon_auth.access_token) {
|
||||
const b = bot.post(settings.mastodon_auth, event)
|
||||
promises.push(b)
|
||||
}
|
||||
// user publish
|
||||
if (event.user && event.user.mastodon_auth && event.user.mastodon_auth.access_token) {
|
||||
const b = bot.post(event.user.mastodon_auth, event).then(ret => {
|
||||
event.activitypub_id = ret.id
|
||||
return event.save()
|
||||
})
|
||||
promises.push(b)
|
||||
}
|
||||
break
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
async function loop () {
|
||||
settings = await settingsController.settings()
|
||||
// get all event notification in queue
|
||||
const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } })
|
||||
const promises = eventNotifications.map(async e => {
|
||||
const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] })
|
||||
if (!event.place) return
|
||||
const notification = await Notification.findByPk(e.notificationId)
|
||||
try {
|
||||
await sendNotification(notification, event, e)
|
||||
e.status = 'sent'
|
||||
return e.save()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
e.status = 'error'
|
||||
return e.save()
|
||||
}
|
||||
})
|
||||
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
setInterval(loop, 260000)
|
||||
loop()
|
||||
@@ -1,4 +0,0 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const conf = require('./config.js')
|
||||
const db = new Sequelize(conf.db)
|
||||
module.exports = db
|
||||
@@ -1,4 +0,0 @@
|
||||
p= t('confirm_email')
|
||||
|
||||
hr
|
||||
small #{config.baseurl}
|
||||
@@ -1,18 +0,0 @@
|
||||
h3 #{event.title}
|
||||
p Dove: #{event.place.name} - #{event.place.address}
|
||||
p Quando: #{datetime(event.start_datetime)}
|
||||
br
|
||||
if event.image_path
|
||||
<img style="width: 100%" src="#{config.apiurl}/uploads/#{event.image_path}" />
|
||||
p #{event.description}
|
||||
|
||||
each tag in event.tags
|
||||
span ##{tag.tag}
|
||||
br
|
||||
<a href="#{config.baseurl}/event/#{event.id}">#{config.baseurl}/event/#{event.id}</a>
|
||||
hr
|
||||
if to_confirm
|
||||
p Puoi confermare questo evento <a href="#{config.baseurl}/admin/confirm/#{event.id}">qui</a>
|
||||
else
|
||||
p Puoi eliminare queste notifiche <a href="#{config.baseurl}/del_notification/#{notification.remove_code}">qui</a>
|
||||
<a href="#{config.baseurl}">#{config.title} - #{config.description}</a>
|
||||
@@ -1 +0,0 @@
|
||||
= `[${config.title}] ${event.title} @${event.place.name} ${datetime(event.start_datetime)}`
|
||||
@@ -1,8 +0,0 @@
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #555;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
p= t('recover_email')
|
||||
|
||||
<a href="#{config.baseurl}/recover/#{user.recover_code}">#{t('press here')}</a>
|
||||
@@ -1 +0,0 @@
|
||||
= `[Gancio] Richiesta password recovery`
|
||||
@@ -1,6 +0,0 @@
|
||||
p= t('registration_email')
|
||||
|
||||
hr
|
||||
small #{config.title} / #{config.description}
|
||||
br
|
||||
small #{config.baseurl}
|
||||
@@ -1 +0,0 @@
|
||||
= `[Gancio] Richiesta registrazione`
|
||||
44
app/mail.js
44
app/mail.js
@@ -1,44 +0,0 @@
|
||||
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
|
||||
@@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
// return queryInterface.addColumn('EventNotifications', 'status',
|
||||
// { type: Sequelize.ENUM, values: ['new', 'sent', 'error'], index: true, defaultValue: 'new' })
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.removeColumn('EventNotifications', 'status')
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.addColumn('users', 'recover_code',
|
||||
{ type: Sequelize.STRING })
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.removeColumn('users', 'recover_code')
|
||||
}
|
||||
}
|
||||
14
app/model.js
14
app/model.js
@@ -1,14 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
const db = require('../db')
|
||||
const Sequelize = require('sequelize')
|
||||
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 }
|
||||
@@ -1,37 +0,0 @@
|
||||
'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;
|
||||
@@ -1,9 +0,0 @@
|
||||
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
|
||||
@@ -1,34 +0,0 @@
|
||||
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
|
||||
@@ -1,28 +0,0 @@
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const bodyParser = require('body-parser')
|
||||
const api = require('./api')
|
||||
const cors = require('cors')
|
||||
const path = require('path')
|
||||
const morgan = require('morgan')
|
||||
const config = require('./config')
|
||||
const db = require('./db')
|
||||
const port = process.env.PORT || 9000
|
||||
|
||||
app.use(morgan('dev'))
|
||||
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(path.join(__dirname, '..', 'uploads')))
|
||||
app.use('/api', api)
|
||||
app.use('/', express.static(path.join(__dirname, '..', 'client', 'dist')))
|
||||
app.use('/css', express.static(path.join(__dirname, '..', 'client', 'dist', 'css')))
|
||||
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(`[${config.title}] Started ${process.env.NODE_ENV} mode at ${config.baseurl} (api @ ${config.apiurl})\n\nCONFIG`, config)
|
||||
db.sync()
|
||||
})
|
||||
@@ -1,62 +0,0 @@
|
||||
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') + '.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) {
|
||||
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)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
doctype xml
|
||||
rss(version='2.0')
|
||||
channel
|
||||
title #{config.title}
|
||||
link #{config.baseurl}
|
||||
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 #{config.baseurl}/event/#{event.id}
|
||||
description
|
||||
| <![CDATA[
|
||||
| <h4>#{event.title}</h4>
|
||||
| <strong>#{event.place.name} - #{event.place.address}</strong>
|
||||
| #{moment(event.start_datetime).format("dddd, D MMMM HH:mm")}<br/>
|
||||
| <img src="#{config.apiurl}/../uploads/#{event.image_path}"/>
|
||||
| <pre>!{event.description}</pre>
|
||||
| ]]>
|
||||
pubDate= new Date(event.createdAt).toUTCString()
|
||||
guid(isPermaLink='false') #{config.baseurl}/event/#{event.id}
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
'extends': 'standard',
|
||||
"rules": {
|
||||
"camelcase": 0
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app' //, 'es2015', { 'modules': false }
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
'component',
|
||||
{
|
||||
'libraryName': 'element-ui',
|
||||
'styleLibraryName': 'theme-chalk'
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"name": "eventi",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.13",
|
||||
"dayjs": "^1.8.9",
|
||||
"element-ui": "^2.6.1",
|
||||
"mastodon-api": "^1.3.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"npm": "^6.8.0",
|
||||
"postcss-flexbugs-fixes": "^4.1.0",
|
||||
"pug": "^2.0.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"v-calendar": "^0.9.7",
|
||||
"vue": "^2.5.17",
|
||||
"vue-awesome": "^3.3.1",
|
||||
"vue-clipboard2": "^0.3.0",
|
||||
"vue-i18n": "^8.5.0",
|
||||
"vue-magic-grid": "^0.0.4",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-persist": "^2.0.0",
|
||||
"weekstart": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.2.0",
|
||||
"@vue/cli-plugin-eslint": "^3.2.0",
|
||||
"@vue/cli-service": "^3.2.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-plugin-vue": "^5.0.0-0",
|
||||
"pug-plain-loader": "^1.0.0",
|
||||
"vue-template-compiler": "^2.5.17"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
],
|
||||
"rules": {},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,144 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="115.41461mm"
|
||||
height="27.793631mm"
|
||||
viewBox="0 0 115.41461 27.793631"
|
||||
version="1.1"
|
||||
id="svg4662"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="gancio_logo.svg">
|
||||
<defs
|
||||
id="defs4656" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#acacac"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.5"
|
||||
inkscape:cx="935.05166"
|
||||
inkscape:cy="-337.87097"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="g4601"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1029"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1107"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="false" />
|
||||
<metadata
|
||||
id="metadata4659">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Livello 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(276.02814,-32.077815)">
|
||||
<g
|
||||
id="g4558"
|
||||
transform="translate(1.3890529,0.07244553)">
|
||||
<g
|
||||
id="g4601"
|
||||
transform="translate(0,-42.333335)">
|
||||
<g
|
||||
id="g4629-3"
|
||||
transform="translate(-305.51837,-29.938164)">
|
||||
<g
|
||||
transform="translate(0,47.625002)"
|
||||
id="g4603-6" />
|
||||
<g
|
||||
transform="translate(-3.7041668,23.812509)"
|
||||
id="g4627-7">
|
||||
<g
|
||||
id="g4609-5"
|
||||
transform="translate(2.9252966,21.389747)">
|
||||
<path
|
||||
d="m 124.73699,81.740247 c -0.042,-1.344482 -0.063,-2.773002 -0.063,-4.28554 0,-1.764643 0.042,-3.21416 0.12604,-4.348571 0.042,-0.378133 0.063,-0.735262 0.063,-1.071379 0,-1.17642 -0.25208,-2.037734 -0.75627,-2.583929 -0.46216,-0.58821 -1.21843,-0.882318 -2.26881,-0.882318 h -0.37814 c -2.10076,0.126047 -3.6343,0.189066 -4.60065,0.189066 -1.05038,0 -1.72263,-0.06303 -2.01673,-0.189066 0.0841,1.722621 0.12604,3.31919 0.12604,4.789718 0,0.588211 -0.021,1.176421 -0.063,1.764637 0,0.04202 -0.021,0.399144 -0.063,1.071385 -0.042,0.630226 -0.063,1.134405 -0.063,1.512544 0,1.218436 0.23109,2.20579 0.69325,2.962069 0.50418,0.714256 1.40751,1.071384 2.70998,1.071384 1.89068,0.04202 3.59229,0.06297 5.10484,0.06297 v 0 c 0.12604,0.420148 -0.86131,0.840303 -0.86131,1.260451 0.59102,-0.182249 2.25198,-0.718484 2.31083,-1.323478 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:SoupLeaf;-inkscape-font-specification:'SoupLeaf, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26259452"
|
||||
id="path4605-3"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cscscscscsccscccccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccscscscsccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4607-5"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:SoupLeaf;-inkscape-font-specification:'SoupLeaf, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.15776582"
|
||||
d="m 117.1149,61.679053 c -1.43919,0.42444 -2.03367,0.649045 -2.07229,1.182896 0.042,0.485302 0.063,1.000931 0.063,1.546894 0,0.636953 -0.042,1.526455 -0.12604,1.935931 -0.042,0.136488 -0.063,0.2654 -0.063,0.386724 0,0.424633 0.18246,0.349604 0.72145,0.453913 0.5086,0.107876 1.25327,0.272056 2.30364,0.272056 h 0.37813 c 2.10077,-0.0455 3.63431,-0.06824 4.60066,-0.06824 1.05038,0 1.37798,0.974658 1.67208,1.020149 -0.0457,-1.295385 0.31716,-2.9161 0.40768,-4.091524 0,-0.439804 -0.23108,-0.796193 -0.69325,-1.069175 -0.50419,-0.257818 -1.40752,-0.386724 -2.70998,-0.386724 -1.89069,-0.01516 -3.5923,-0.02275 -5.10484,-0.02275 v 0 c -0.41273,-0.497342 0.0725,-0.856932 0.62276,-1.16015 z" />
|
||||
</g>
|
||||
<path
|
||||
transform="translate(0,47.625003)"
|
||||
sodipodi:nodetypes="cscscccccccsssccccssccccccsccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4611-6"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:SoupLeaf;-inkscape-font-specification:'SoupLeaf, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
d="m 76.98359,38.242574 c -0.592667,0.338667 -0.837977,0.04028 -1.938644,0.04028 -0.804333,0 -1.672167,-0.105833 -2.6035,-0.3175 -2.328333,-0.508 -4.699,-0.762 -7.112,-0.762 -2.116667,0 -4.219443,0.597132 -4.572,0.762 -0.340875,0.03112 -1.012654,-0.341285 0.365451,-0.567012 0.26732,-0.05131 0.48816,-0.112725 0.602566,-0.206246 0.07353,-0.816118 -2.880962,-0.280435 -2.555517,1.471758 0.169333,0.635 0.275167,1.354667 0.3175,2.159 v 0.0635 0.889 c 3.641547,0.663744 8.545765,-0.506384 9.657068,-0.147837 0.787558,0.254095 0.888412,2.489721 0.385715,2.831895 -1.988477,1.353512 -8.401427,1.363785 -9.286054,0.942899 -0.844467,-0.401779 -0.418064,0.881542 -0.248729,2.024542 0.127,1.100667 0.1905,1.820333 0.1905,2.159 0.211667,1.989667 0.5715,3.788833 1.0795,5.3975 3.048,-0.211667 5.291667,-0.402167 6.731,-0.5715 3.333711,0.01832 3.530728,-0.487574 5.920619,-0.32001 1.179892,0.08273 2.336574,0.241881 3.646192,1.142358 0.715811,0.492183 0.682983,-0.789215 0.672073,-1.193451 -0.26018,-1.429378 -0.103039,-2.38471 -0.178275,-3.375398 -0.825875,-4.154467 -0.753063,-9.0021 -1.073465,-12.422778 z M 66.133782,51.469685 h -0.0635 c -0.211667,-0.423333 -0.3175,-1.312333 -0.3175,-2.667 l 0.0635,-0.0635 c 0.169333,-0.04233 0.529167,-0.105833 1.0795,-0.1905 0.211667,-0.04233 0.486833,-0.0635 0.8255,-0.0635 0.338667,0 1.672167,0.02117 1.883834,0.0635 v 0 l 0.3175,2.6035 v 0.0635 0 c -0.254,0 -1.756834,0.02117 -2.391834,0.0635 -0.677333,0.04233 -1.143,0.105834 -1.397,0.1905 z" />
|
||||
<path
|
||||
transform="translate(0,47.625003)"
|
||||
sodipodi:nodetypes="sscscccsscccccccscccccscccsscscccs"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4613-2"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:SoupLeaf;-inkscape-font-specification:'SoupLeaf, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
d="m 97.168085,54.221352 c 0.838348,-1.067123 0.6985,-4.148667 0.6985,-6.477 0,-2.328333 -0.3175,-4.3815 -0.9525,-6.1595 -1.016,-3.090333 -2.116667,-4.6355 -3.302,-4.6355 -0.127,0 -0.338667,0.04233 -0.635,0.127 -0.254,0.04233 -0.402167,0.0635 -0.4445,0.0635 -0.169333,0.04233 -0.5715,0.127 -1.2065,0.254 -0.635,0.08467 -1.248833,0.169333 -1.8415,0.254 -0.592667,0.08467 -1.058333,0.127 -1.397,0.127 -0.296333,0 -0.529167,-0.02117 -0.6985,-0.0635 -0.677333,-0.254 -1.121833,-0.719667 -1.3335,-1.397 -0.09681,-0.188828 -0.131976,-0.218944 -0.381,-0.1905 -0.635,0.127 -2.0955,0.4445 -4.3815,0.9525 -3.623469,-0.381 -2.513622,8.534263 -2.286,13.589 l 0.1905,4.699 7.874,-0.127 c 0.254,-1.481667 0.381,-3.259667 0.381,-5.334 0,-0.762 -0.02117,-1.439333 -0.0635,-2.032 -0.169333,-2.751667 -0.338667,-4.529667 -0.508,-5.334 v 0 l 0.0635,-0.0635 v 0 c 0.381,-0.127 0.762,-0.1905 1.143,-0.1905 0.465667,0 0.762,0.148167 0.889,0.4445 0.423333,0.931333 0.635,3.132667 0.635,6.604 -0.03639,0.326737 -0.0635,0.552493 -0.0635,0.889 0,0.296333 -0.02117,0.719667 -0.0635,1.27 -0.04233,0.550333 -0.0635,0.931333 -0.0635,1.143 0,1.058333 0.5715,1.651 1.7145,1.778 0.592667,0.04233 1.143,0.0635 1.651,0.0635 0.635,0 1.312333,-0.02117 2.032,-0.0635 l 0.748355,-0.08851 c 1.1219,-0.0845 -0.343517,0.755441 -0.343517,0.755441 0,0 1.075474,0.248947 1.944662,-0.857431 z" />
|
||||
<path
|
||||
transform="translate(0,47.625003)"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4615-9"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:SoupLeaf;-inkscape-font-specification:'SoupLeaf, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
d="m 144.74231,37.865848 c 0.67677,0.05658 1.21623,0.713304 0.7936,0.755671 v 0 c -1.94733,0 -3.81,-0.08467 -5.588,-0.254 -0.46567,-0.04233 -1.56633,-0.1905 -3.302,-0.4445 -1.69333,-0.254 -2.96333,-0.381 -3.81,-0.381 -2.286,0 -3.59833,0.804333 -3.937,2.413 v 0 0 0 0 0 c -0.381,0.635 -0.0423,2.518833 -0.0423,5.6515 v 1.397 c 0.127,5.461 0.29633,8.403167 1.56633,8.8265 0.127,0.04233 0.254,0.0635 0.381,0.0635 0.127,0 0.78317,-0.148167 1.9685,-0.4445 1.18533,-0.338667 2.43417,-0.508 3.7465,-0.508 0.381,0 0.86783,0.02117 1.4605,0.0635 0.508,0.04233 1.16417,0.105833 1.9685,0.1905 0.80433,0.08467 1.48167,0.148167 2.032,0.1905 0.59267,0.04233 1.10067,0.0635 1.524,0.0635 1.94733,0 2.94217,-0.592667 2.9845,-1.778 0.0423,-0.465667 0.0635,-0.889 0.0635,-1.27 0,-0.677333 -0.0423,-1.883833 -0.127,-3.6195 -0.0423,-1.735667 -0.0635,-3.005667 -0.0635,-3.81 0,-1.227667 0.0423,-2.074333 0.127,-2.54 0.16933,-0.762 0.27517,-1.397 0.3175,-1.905 v -0.0635 c 1.04195,-2.143001 -0.15974,-2.071757 -0.58855,-2.447681 -3.29195,-1.671465 -3.46421,-0.315774 -1.47505,-0.14949 z m -6.3184,13.455671 h -0.3175 c -0.635,0.04233 -1.0795,0.08467 -1.3335,0.127 h -0.0635 v -2.794 -0.0635 0 c 0.46567,-0.127 0.97367,-0.1905 1.524,-0.1905 h 0.127 c 0.59267,0.04233 0.97367,0.0635 1.143,0.0635 v 0 0.0635 0 c 0.0847,0.635 0.127,1.227667 0.127,1.778 0,0.508 -0.0423,0.867833 -0.127,1.0795 v 0.0635 l -0.0635,-0.0635 v 0 c -0.16933,-0.04233 -0.508,-0.0635 -1.016,-0.0635 z"
|
||||
sodipodi:nodetypes="sccccsccccccscsscscscscscsccccssccccccscccccsccccs" />
|
||||
<path
|
||||
transform="translate(0,47.625003)"
|
||||
sodipodi:nodetypes="ccccscsccccccscccccsscsscccscccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4617-1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:SoupLeaf;-inkscape-font-specification:'SoupLeaf, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26386419"
|
||||
d="m 113.41865,34.862971 c 0.27611,0.77181 1.38276,1.788884 1.57103,3.748148 -0.76664,-0.111392 -0.17479,-0.05401 -0.7371,-0.04986 -0.84437,0 -1.77317,-0.105539 -2.7864,-0.316636 -2.61753,-0.633273 -5.19285,-0.94991 -7.72595,-0.94991 -2.82864,0 -4.45404,0.464401 -4.87622,1.393201 -0.33774,0.717713 -0.50662,1.878717 -0.50662,3.48301 0,0.759928 0.0422,1.604295 0.12665,2.533085 0.0422,0.886585 0.12665,1.857613 0.25332,2.913066 0.0845,1.01324 0.14776,1.73095 0.18997,2.15313 0.12666,2.026474 0.42218,3.862968 0.88659,5.509481 3.30386,-0.09618 6.42529,-0.46272 9.87909,-0.759922 0.88658,0 1.70984,0.04222 2.46977,0.126649 0.75992,0.04222 1.41431,0.06327 1.96314,0.06327 0.92881,0 1.17291,-0.08444 1.46844,-0.253312 0.33775,-0.211093 0.61216,-0.591054 0.82326,-1.139902 v 0 l 0.0633,-0.06327 c 0,-0.675491 0.0211,-1.350988 0.0633,-2.026481 -1.85761,-0.506616 -4.02265,-0.759921 -7.23123,-0.759921 h -1.32987 c -0.42219,0 -0.78104,0.02105 -1.07657,0.06327 h -0.44329 c -0.16888,0 -0.25331,-0.189981 -0.25331,-0.569941 0,-0.464405 -0.0172,-1.655752 0.27837,-2.58455 1.29317,-0.233942 1.50635,-0.161062 1.7481,-0.159634 0.33775,0 2.34312,0.105539 6.0161,0.316629 h 0.12666 c 0.88658,0 1.51986,0.2111 1.89982,-0.422173 0.37997,-0.675498 0.59106,-1.350989 0.63328,-2.026481 0,-1.055453 -0.0225,-2.917481 -0.14917,-5.492795 -0.49568,-2.772882 -1.82493,-3.79722 -3.34452,-4.728181 z" />
|
||||
<g
|
||||
transform="translate(0,1.0583333)"
|
||||
id="g4625-2">
|
||||
<path
|
||||
id="path4619-7"
|
||||
transform="scale(0.26458333)"
|
||||
d="m 214.45117,317.70508 c -11.17785,0.12645 -1.97461,2.4707 -1.97461,2.4707 0.5209,-0.0102 2.12086,-0.88425 2.60352,0.20313 0.26759,0.60286 -0.0748,1.82552 -1.45899,1.83203 h -5.51953 c -4.96,0 -9.59992,-0.16046 -13.91992,-0.48047 -11.68,-1.12 -19.04008,-1.67969 -22.08008,-1.67969 -11.51999,0 -17.68046,2.55969 -18.48047,7.67969 -0.96,4.96 -1.43945,13.04023 -1.43945,20.24023 0,4.64 0.2407,9.27992 0.7207,13.91992 1.6,11.52001 0.87465,10.20499 4.56055,12.88086 5.95738,4.32492 14.39813,0.56055 22.07813,0.56055 h 0.24023 c 3.52,0 12.48086,-1.52055 14.88086,-0.56055 2.40375,0.40125 3.76805,-0.0327 4.56055,2.63868 1.01305,2.48837 1.88809,11.33815 1.19922,11.92187 -6.11795,3.16753 -15.65105,2.86885 -21.59961,3.19922 -2.72001,0.15999 -5.04094,0.24023 -6.96094,0.24023 -2.146,0 -3.82486,-0.13999 -5.15235,-0.3164 v 10.4043 c 4.6584,1.32321 9.90901,2.30468 15.89063,2.30468 4.32,0 8.3825,-0.71367 12.0625,-1.51367 10.24,-2.08 19.35937,-6.63969 27.35937,-13.67969 -0.63999,-6.72 -0.95898,-19.6789 -0.95898,-26.8789 0,-3.68 0.0803,-7.20055 0.24023,-10.56055 0.32002,-3.52 0.47852,-7.52 0.47852,-12 -0.0979,-5.3278 1.06159,-13.42096 -0.71875,-17.59961 -0.63059,-1.48005 -1.53866,-2.43226 -2.11523,-3.26758 -0.49509,-0.71725 -1.23668,-1.43299 -1.98047,-1.95312 -0.93513,-0.0126 -1.77044,-0.0143 -2.51563,-0.006 z m -18.58984,31.22656 c 0.8,1.6 1.28141,6.96008 1.4414,12.08008 v 0.24023 c -0.63999,0.15999 -2.00008,0.39875 -4.08007,0.71875 -1.44,0.15999 -9.55588,0.24024 -8.32032,0.24024 h -1.67968 -0.24024 -0.24023 l -1.43946,-11.83985 0.23829,-0.24023 c 1.28,-0.15999 6.96101,-0.32048 9.04101,-0.48047 2.72,-0.15999 4.4793,-0.39875 5.2793,-0.71875 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:25.39999962px;line-height:1.25;font-family:SoupLeaf;-inkscape-font-specification:'SoupLeaf, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.99999994"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccscscscassccccsccsccscccccccccccsccccccc" />
|
||||
<g
|
||||
style="fill:#ff0000"
|
||||
id="g4623-0"
|
||||
transform="matrix(-0.05837501,0.25245606,-0.25245606,-0.05837501,111.30736,132.11782)">
|
||||
<path
|
||||
sodipodi:nodetypes="cccsccccssccccccccscsccccscccc"
|
||||
id="path4621-9"
|
||||
transform="matrix(-0.23003668,-0.99484615,0.99484615,-0.23003668,-399.99545,533.38826)"
|
||||
d="m 164.75391,300.12109 c -3.24373,-0.0303 -6.11471,0.11966 -9.08594,0.44532 -18.35072,2.01124 -28.09411,10.14145 -33.32227,27.80273 -2.6272,8.875 -2.82125,22.83155 -0.63867,32.31445 1.58264,6.87629 3.85109,12.39088 7.49609,18.22266 3.58528,5.73621 7.79754,10.08127 13.26368,13.68359 4.77225,3.14504 12.34952,7.019 19.76953,9.14649 v -10.13867 c -3.53307,-0.904 -6.47332,-1.86856 -8.15821,-2.91993 -7.43035,-4.63653 -11.84095,-8.9198 -15.6914,-14.55859 -4.8805,-7.14724 -7.01189,-19.5916 -6.74805,-28.5293 0.0988,-3.34042 1.62817,-15.4335 2.95703,-18.24804 2.69245,-5.70267 4.69314,-9.15557 9.09375,-11.87305 6.5257,-3.76431 13.56281,-6.88759 28.22657,-5.90625 0.11861,0.0736 -4.60161,3.38467 -6.08008,4.93945 -2.3994,2.52323 -3.10156,3.78516 -3.10156,3.78516 -0.0161,0.0996 4.54165,-1.93431 6.35546,-2.87305 4.45264,-2.30444 8.02967,-4.57269 9.46875,-5.07422 0.74639,-0.11144 7.60841,-1.45597 15.38282,-2.23242 22.78632,-2.27574 24.21952,-2.50438 20.27343,-3.23047 -7.74703,-1.42545 -16.55392,-2.52304 -24.16992,-3.01367 -3.02668,-0.19498 -9.31417,-0.69271 -13.97265,-1.10547 -4.45786,-0.39497 -8.07464,-0.60645 -11.31836,-0.63672 z m -12.52149,4.77344 c 1.24553,-0.031 2.52435,0.0214 3.83203,0.16211 1.97529,0.21249 1.83203,0.58511 -0.33007,0.85547 -11.18977,1.39922 -20.74431,6.18752 -25.23047,14.72656 -0.43702,0.83185 -0.8817,1.4934 -0.98829,1.46875 -0.32103,-0.0742 0.58228,-3.32965 1.42188,-5.125 3.51572,-7.51775 12.57624,-11.87076 21.29492,-12.08789 z"
|
||||
style="fill:#ff0000;stroke-width:0.25911719"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>Gancio</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but eventi doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,83 +0,0 @@
|
||||
<template lang='pug'>
|
||||
#app
|
||||
Nav
|
||||
Home
|
||||
transition(name="fade" mode="out-in")
|
||||
router-view(name='modal')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'dayjs'
|
||||
import api from '@/api'
|
||||
import { mapActions } from 'vuex';
|
||||
import Register from '@/components/Register'
|
||||
import Login from '@/components/Login'
|
||||
import Settings from '@/components/Settings'
|
||||
import newEvent from '@/components/newEvent'
|
||||
import eventDetail from '@/components/EventDetail'
|
||||
import Home from '@/components/Home'
|
||||
import Nav from '@/components/Nav'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
mounted () {
|
||||
this.updateMeta()
|
||||
},
|
||||
methods: mapActions(['updateMeta']),
|
||||
components: { Nav, Register, Login, Home, Settings, newEvent, eventDetail },
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#logo {
|
||||
max-height: 40px;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#search,
|
||||
#search ul {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
html, body {
|
||||
scrollbar-face-color: #313543;
|
||||
scrollbar-track-color: rgba(0, 0, 0, 0.1);
|
||||
font-family: Lato,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
|
||||
color: #2c3e50;
|
||||
background: black;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px; }
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #313543;
|
||||
border: 0px none #ffffff;
|
||||
border-radius: 6px; }
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #353a49; }
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
background: #313543; }
|
||||
::-webkit-scrollbar-track {
|
||||
border: 0px none #ffffff;
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 0, 0, 0.1); }
|
||||
::-webkit-scrollbar-track:hover {
|
||||
background: #282c37; }
|
||||
::-webkit-scrollbar-track:active {
|
||||
background: #282c37; }
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent; }
|
||||
|
||||
|
||||
/* .column {
|
||||
margin-top: 3px;
|
||||
margin-right: 3px;
|
||||
margin-bottom: 3px;
|
||||
width: 350px;
|
||||
} */
|
||||
|
||||
</style>
|
||||
@@ -1,80 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import store from './store'
|
||||
const api = axios.create({
|
||||
baseURL: process.env.NODE_ENV === 'development' ? 'http://localhost:9000/api' : '/api',
|
||||
withCredentials: false,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
function get (path) {
|
||||
return api.get(path, { headers: { 'x-access-token': store.state.token } })
|
||||
.then(res => res.data)
|
||||
.catch(e => {
|
||||
if (e.response.status === 403) {
|
||||
store.commit('logout')
|
||||
return false
|
||||
}
|
||||
throw e.response && e.response.data &&
|
||||
e.response.data.errors && e.response.data.errors[0].message
|
||||
})
|
||||
}
|
||||
|
||||
function post (path, data) {
|
||||
return api.post(path, data, { headers: { 'x-access-token': store.state.token } })
|
||||
.then(res => res.data)
|
||||
.catch(e => {
|
||||
if (e.response.status === 403) {
|
||||
store.commit('logout')
|
||||
return false
|
||||
}
|
||||
throw e.response && e.response.data &&
|
||||
e.response.data.errors && e.response.data.errors[0].message
|
||||
})
|
||||
}
|
||||
function put (path, data) {
|
||||
return api.put(path, data, { headers: { 'x-access-token': store.state.token } })
|
||||
.then(ret => ret.data)
|
||||
}
|
||||
|
||||
function del (path) {
|
||||
return api.delete(path, { headers: { 'x-access-token': store.state.token } }).then(ret => ret.data)
|
||||
}
|
||||
|
||||
export default {
|
||||
login: (email, password) => post('/login', { email, password }),
|
||||
register: user => post('/user', user),
|
||||
|
||||
// password recovery
|
||||
forgotPassword: email => post('/user/recover', { email }),
|
||||
checkRecoverCode: recover_code => post('/user/check_recover_code', { recover_code }),
|
||||
recoverPassword: (recover_code, password) => post('/user/recover_password', { recover_code, password }),
|
||||
|
||||
getAllEvents: (month, year) => get(`/event/${year}/${month}`),
|
||||
getUnconfirmedEvents: () => get('/event/unconfirmed'),
|
||||
|
||||
confirmEvent: id => get(`/event/confirm/${id}`),
|
||||
unconfirmEvent: id => get(`/event/unconfirm/${id}`),
|
||||
|
||||
addNotification: notification => post('/event/notification', notification),
|
||||
delNotification: code => del(`/event/notification/${code}`),
|
||||
|
||||
addEvent: event => post('/user/event', event),
|
||||
updateEvent: event => put('/user/event', event),
|
||||
|
||||
updatePlace: place => put('/place', place),
|
||||
delEvent: eventId => del(`/user/event/${eventId}`),
|
||||
getEvent: eventId => get(`/event/${eventId}`),
|
||||
getMeta: () => get('/event/meta'),
|
||||
getUser: () => get('/user'),
|
||||
getUsers: () => get('/users'),
|
||||
updateTag: (tag) => put('/tag', tag),
|
||||
updateUser: user => put('/user', user),
|
||||
getAuthURL: mastodonInstance => post('/user/getauthurl', mastodonInstance),
|
||||
setCode: code => post('/user/code', code),
|
||||
getAdminSettings: () => get('/settings')
|
||||
// setAdminSetting: (key, value) => post('/settings', { key, value })
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/* html, body {
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
} */
|
||||
|
||||
pre {
|
||||
display: inline-block;
|
||||
color: unset;
|
||||
white-space: pre-line;
|
||||
font-family: unset;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
font-family: Lato;
|
||||
}
|
||||
|
||||
.el-tag--info {
|
||||
color: #909399
|
||||
}
|
||||
|
||||
.el-tag:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a, a:hover {
|
||||
color: red;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("About")'
|
||||
:visible='true' size='lg')
|
||||
h5 Chi siamo
|
||||
p.
|
||||
Gancio e' un progetto dell'<a href='https://autistici.org/underscore'>underscore hacklab</a> e uno dei
|
||||
servizi di <a href='https://cisti.org'>cisti.org</a>.
|
||||
|
||||
h5 Cos'è gancio?
|
||||
p.
|
||||
Uno strumento di condivisione di eventi per comunità radicali.
|
||||
Dentro gancio puoi trovare e inserire eventi.
|
||||
Gancio, come tutto <a href='https://cisti.org'>cisti.org</a> è uno strumento
|
||||
antisessista, antirazzista, antifascista e anticapitalista, riflettici quando
|
||||
pubblichi un evento.
|
||||
|
||||
h5 Ok, ma cosa vuol dire gancio?
|
||||
blockquote.
|
||||
Se vieni a Torino e dici: "ehi, ci diamo un gancio alle 8?" nessuno si presenterà con i guantoni per fare a mazzate.
|
||||
Darsi un gancio vuol dire beccarsi alle ore X in un posto Y
|
||||
p
|
||||
small A: a che ora è il gancio in radio per andare al presidio?
|
||||
p
|
||||
small B: non so ma domani non posso venire, ho gia' un gancio per caricare il bar.
|
||||
|
||||
|
||||
h5 Contatti
|
||||
p.
|
||||
Hai scritto una nuova interfaccia per gancio? Vuoi aprire un nuovo nodo di gancio nella tua città?
|
||||
C'è qualcosa che vorresti migliorare? Per contribuire i sorgenti sono liberi e disponibili
|
||||
<a href='https://git.lattuga.net/cisti/gancio'>qui</a>. Aiuti e suggerimenti sono sempre benvenuti, puoi scriverci
|
||||
su underscore chicciola autistici.org
|
||||
|
||||
</template>
|
||||
@@ -1,196 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Admin")'
|
||||
:visible='true' size='lg')
|
||||
el-tabs(tabPosition='left' v-model='tab')
|
||||
|
||||
//- USERS
|
||||
el-tab-pane.pt-1
|
||||
template(slot='label')
|
||||
v-icon(name='users')
|
||||
span.ml-1 {{$t('Users')}}
|
||||
el-table(:data='paginatedUsers' small)
|
||||
el-table-column(label='Email')
|
||||
template(slot-scope='data')
|
||||
el-popover(trigger='hover' :content='data.row.description' width='400')
|
||||
span(slot='reference') {{data.row.email}}
|
||||
el-table-column(label='Azioni')
|
||||
template(slot-scope='data')
|
||||
el-button.mr-1(size='mini'
|
||||
:type='data.row.is_active?"warning":"success"'
|
||||
@click='toggle(data.row)') {{data.row.is_active?$t('Deactivate'):$t('Activate')}}
|
||||
el-button(size='mini'
|
||||
:type='data.row.is_admin?"danger":"warning"'
|
||||
@click='toggleAdmin(data.row)') {{data.row.is_admin?$t('Remove Admin'):$t('Admin')}}
|
||||
el-pagination(:page-size='perPage' :currentPage.sync='userPage' :total='users.length')
|
||||
|
||||
//- PLACES
|
||||
el-tab-pane.pt-1
|
||||
template(slot='label')
|
||||
v-icon(name='map-marker-alt')
|
||||
span.ml-1 {{$t('Places')}}
|
||||
p {{$t('admin_place_explanation')}}
|
||||
el-form.mb-2(:inline='true' label-width='120px')
|
||||
el-form-item(:label="$t('Name')")
|
||||
el-input.mr-1(:placeholder='$t("Name")' v-model='place.name')
|
||||
el-form-item(:label="$t('Address')")
|
||||
el-input.mr-1(:placeholder='$t("Address")' v-model='place.address')
|
||||
el-button(variant='primary' @click='savePlace') {{$t('Save')}}
|
||||
b-table(selectable :items='places' :fields='placeFields' striped hover
|
||||
small selectedVariant='success' primary-key='id'
|
||||
select-mode="single" @row-selected='placeSelected'
|
||||
:per-page='perPage' :current-page='placePage')
|
||||
el-pagination(:page-size='perPage' :currentPage.sync='placePage' :total='places.length')
|
||||
|
||||
//- EVENTS
|
||||
el-tab-pane.pt-1
|
||||
template(slot='label')
|
||||
v-icon(name='calendar')
|
||||
span.ml-1 {{$t('Events')}}
|
||||
p {{$t('event_confirm_explanation')}}
|
||||
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")')
|
||||
template(slot-scope='data') {{data.row.place.name}}
|
||||
el-table-column(:label='$t("Confirm")')
|
||||
template(slot-scope='data')
|
||||
el-button(type='primary' @click='confirm(data.row.id)' size='mini') {{$t('Confirm')}}
|
||||
el-button(type='success' @click='preview(data.row.id)' size='mini') {{$t('Preview')}}
|
||||
|
||||
el-pagination(:page-size='perPage' :currentPage.sync='eventPage' :total='events.length')
|
||||
|
||||
//- TAGS
|
||||
el-tab-pane.pt-1
|
||||
template(slot='label')
|
||||
v-icon(name='tag')
|
||||
span {{$t('Tags')}}
|
||||
p {{$t('admin_tag_explanation')}}
|
||||
el-tag(v-if='tag.tag' :color='tag.color || "grey"' size='mini') {{tag.tag}}
|
||||
el-form(:inline='true' label-width='120px')
|
||||
el-form-item(:label="$t('Color')")
|
||||
el-color-picker(v-model='tag.color' @change='updateColor')
|
||||
el-table(:data='paginatedTags' striped small hover
|
||||
highlight-current-row @current-change="tagSelected")
|
||||
el-table-column(label='Tag')
|
||||
template(slot-scope='data')
|
||||
el-tag(:color='data.row.color || "grey"' size='mini') {{data.row.tag}}
|
||||
el-pagination(:page-size='perPage' :currentPage.sync='tagPage' :total='tags.length')
|
||||
|
||||
//- SETTINGS
|
||||
el-tab-pane.pt-1
|
||||
template(slot='label')
|
||||
v-icon(name='tools')
|
||||
span {{$t('Settings')}}
|
||||
el-form(inline)
|
||||
span {{$t('admin_mastodon_explanation')}}
|
||||
el-input(v-model="mastodon_instance")
|
||||
span(slot='prepend') {{$t('Mastodon instance')}}
|
||||
el-button(slot='append' @click='associate' variant='success' type='success') {{$t('Associate')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import api from '@/api'
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
export default {
|
||||
name: 'Admin',
|
||||
data () {
|
||||
return {
|
||||
perPage: 10,
|
||||
users: [],
|
||||
userFields: ['email', 'action'],
|
||||
placeFields: ['name', 'address'],
|
||||
placePage: 1,
|
||||
userPage: 1,
|
||||
eventPage: 1,
|
||||
tagPage: 1,
|
||||
tagFields: ['tag', 'color'],
|
||||
description: '',
|
||||
place: {name: '', address: '' },
|
||||
tag: {name: '', color: ''},
|
||||
events: [],
|
||||
loading: false,
|
||||
mastodon_instance: '',
|
||||
settings: {},
|
||||
tab: "0",
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
const code = this.$route.query.code
|
||||
if (code) {
|
||||
this.tab = "4"
|
||||
const instance = await api.setCode({code, is_admin: true})
|
||||
}
|
||||
this.users = await api.getUsers()
|
||||
this.events = await api.getUnconfirmedEvents()
|
||||
this.settings = await api.getAdminSettings()
|
||||
this.mastodon_instance = this.settings.mastodon_auth && this.settings.mastodon_auth.instance
|
||||
},
|
||||
computed: {
|
||||
...mapState(['tags', 'places']),
|
||||
paginatedEvents () {
|
||||
return this.events.slice((this.eventPage-1) * this.perPage,
|
||||
this.eventPage * this.perPage)
|
||||
},
|
||||
paginatedTags () {
|
||||
return this.tags.slice((this.tagPage-1) * this.perPage,
|
||||
this.tagPage * this.perPage)
|
||||
},
|
||||
paginatedUsers () {
|
||||
return this.users.slice((this.userPage-1) * this.perPage,
|
||||
this.userPage * this.perPage)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
placeSelected (items) {
|
||||
if (items.length === 0 ) {
|
||||
this.place.name = this.place.address = ''
|
||||
return
|
||||
}
|
||||
const item = items[0]
|
||||
this.place.name = item.name
|
||||
this.place.address = item.address
|
||||
this.place.id = item.id
|
||||
},
|
||||
tagSelected (tag) {
|
||||
this.tag = tag
|
||||
},
|
||||
async savePlace () {
|
||||
const place = await api.updatePlace(this.place)
|
||||
},
|
||||
async toggle(user) {
|
||||
user.is_active = !user.is_active
|
||||
const newuser = await api.updateUser(user)
|
||||
},
|
||||
async toggleAdmin(user) {
|
||||
user.is_admin = !user.is_admin
|
||||
const newuser = await api.updateUser(user)
|
||||
},
|
||||
async updateColor () {
|
||||
const newTag = await api.updateTag(this.tag)
|
||||
},
|
||||
preview (id) {
|
||||
this.$router.push(`/event/${id}`)
|
||||
},
|
||||
async associate () {
|
||||
if (!this.mastodon_instance) return
|
||||
const url = await api.getAuthURL({instance: this.mastodon_instance, admin: true})
|
||||
setTimeout( () => window.location.href=url, 100);
|
||||
},
|
||||
async confirm (id) {
|
||||
try {
|
||||
this.loading = true
|
||||
await api.confirmEvent(id)
|
||||
this.loading = false
|
||||
Message({
|
||||
message: this.$t('event_confirmed'),
|
||||
type: 'success'
|
||||
})
|
||||
this.events = this.events.filter(e => e.id !== id)
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,93 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-calendar#calendar.card(
|
||||
show-caps
|
||||
:popover-expanded='true'
|
||||
:attributes='attributes'
|
||||
:from-page.sync='page'
|
||||
is-expanded is-inline)
|
||||
div(slot='popover', slot-scope='{ customData }')
|
||||
router-link(:to="`/event/${customData.id}`") {{customData.start_datetime|hour}} - {{customData.title}} @{{customData.place.name}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import filters from '@/filters'
|
||||
import moment from 'dayjs'
|
||||
import { intersection } from 'lodash'
|
||||
|
||||
export default {
|
||||
name: 'Calendar',
|
||||
filters,
|
||||
data () {
|
||||
const month = moment().month()+1
|
||||
const year = moment().year()
|
||||
return {
|
||||
page: { month, year},
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
await this.updateEvents(this.page)
|
||||
},
|
||||
watch: {
|
||||
page () {
|
||||
this.updateEvents(this.page)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateEvents']),
|
||||
eventToAttribute(event) {
|
||||
let e = {
|
||||
key: event.id,
|
||||
customData: event,
|
||||
order: event.start_datetime,
|
||||
popover: {
|
||||
slot: 'popover',
|
||||
visibility: 'hover'
|
||||
}
|
||||
}
|
||||
|
||||
let color = event.tags.length && event.tags[0].color ? event.tags[0].color : 'rgba(170,170,250,0.7)'
|
||||
if (event.past) color = 'rgba(200,200,200,0.5)'
|
||||
if (event.multidate) {
|
||||
e.dates = {
|
||||
start: event.start_datetime, end: event.end_datetime
|
||||
}
|
||||
e.highlight = { backgroundColor: color,
|
||||
borderColor: 'transparent',
|
||||
borderWidth: '4px' }
|
||||
} else {
|
||||
e.dates = event.start_datetime
|
||||
e.dot = { backgroundColor: color, borderColor: color, borderWidth: '3px' }
|
||||
}
|
||||
return e
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredEvents () {
|
||||
return this.$store.getters.filteredEvents
|
||||
},
|
||||
...mapState(['events', 'filters', 'user', 'logged']),
|
||||
attributes () {
|
||||
return [
|
||||
{ key: 'todaly', dates: new Date(),
|
||||
highlight: {
|
||||
backgroundColor: '#aaffaa'
|
||||
},
|
||||
popover: {label: this.$t('Today')}
|
||||
},
|
||||
...this.filteredEvents.map(this.eventToAttribute)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#calendar {
|
||||
margin-bottom: 0em;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
#calendar a {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
@@ -1,39 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Remove notification")'
|
||||
:visible='true' size='sm' ref='modal')
|
||||
div( v-loading='loading')
|
||||
p Vuoi eliminare le notifiche?
|
||||
el-button.float-right(type='success' @click='remove') {{$t('Yes')}}
|
||||
el-button.float-right.mr-1(type='danger' @click='$refs.modal.hide()') {{$t('No')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { Message } from 'element-ui';
|
||||
|
||||
export default {
|
||||
name: 'DelNotification',
|
||||
data () {
|
||||
return {
|
||||
code: '',
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.code = this.$route.params.code
|
||||
},
|
||||
methods: {
|
||||
async remove () {
|
||||
this.loading = true
|
||||
try {
|
||||
await api.delNotification(this.code)
|
||||
Message({message: this.$t('Email notification removed'), type: 'success'})
|
||||
} catch(e) {
|
||||
Message({message: this.$t('Error removing email notification'), type: 'error'})
|
||||
}
|
||||
this.$refs.modal.hide()
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,65 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-card(bg-variant='dark' text-variant='white' :class="{ withImg: event.image_path ? true : false }"
|
||||
@click='$router.push("/event/" + event.id)'
|
||||
:img-src='imgPath')
|
||||
strong {{event.title}}
|
||||
div <v-icon name='clock'/> {{event.start_datetime|datetime}}
|
||||
span <v-icon name='map-marker-alt'/> {{event.place.name}}
|
||||
br
|
||||
el-tag.mr-1(:color='tag.color || "grey"' v-for='tag in event.tags' :key='tag.tag'
|
||||
size='small' @click.stop='addSearchTag(tag)') {{tag.tag}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import api from '@/api'
|
||||
import filters from '@/filters'
|
||||
|
||||
export default {
|
||||
props: ['event'],
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
imgPath () {
|
||||
return this.event.image_path && process.env.VUE_APP_API + '/uploads/thumb/' + this.event.image_path
|
||||
},
|
||||
mine () {
|
||||
return this.event.userId === this.user.id
|
||||
}
|
||||
},
|
||||
filters,
|
||||
methods: {
|
||||
...mapActions(['delEvent', 'addSearchTag']),
|
||||
async remove () {
|
||||
await api.delEvent(this.event.id)
|
||||
this.delEvent(this.event.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
/* .card::before {
|
||||
border-top: 4px solid black;
|
||||
content: ''
|
||||
} */
|
||||
|
||||
.el-card {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-card img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-columns .card {
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
.card-img {
|
||||
height: 180px;
|
||||
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-modal#eventDetail(ref='eventDetail' hide-body hide-header hide-footer @hidden='$router.replace("/")' size='lg' :visible='true')
|
||||
b-card(no-body, :img-src='imgPath' v-loading='loading')
|
||||
el-button.close_button(circle icon='el-icon-close' type='success'
|
||||
@click='$refs.eventDetail.hide()')
|
||||
b-card-header
|
||||
h3 {{event.title}}
|
||||
v-icon(name='clock')
|
||||
span {{event.start_datetime|datetime}}
|
||||
br
|
||||
v-icon(name='map-marker-alt')
|
||||
span {{event.place.name}} - {{event.place.address}}
|
||||
br
|
||||
b-card-body(v-if='event.description || event.tags')
|
||||
pre(v-html='event.description')
|
||||
br
|
||||
el-tag.mr-1(:color='tag.color || "grey"' v-for='tag in event.tags'
|
||||
size='mini' :key='tag.tag') {{tag.tag}}
|
||||
.ml-auto(v-if='mine')
|
||||
hr
|
||||
el-button(v-if='event.is_visible' plain type='warning' @click.prevents='toggle' icon='el-icon-view') {{$t('Unconfirm')}}
|
||||
el-button(v-else plain type='success' @click.prevents='toggle' icon='el-icon-view') {{$t('Confirm')}}
|
||||
el-button(plain type='danger' @click.prevent='remove' icon='el-icon-remove') {{$t('Remove')}}
|
||||
el-button(plain type='primary' @click='$router.replace("/edit/"+event.id)') <v-icon color='orange' name='edit'/> {{$t('Edit')}}
|
||||
|
||||
//- COMMENTS ...
|
||||
//- b-navbar(type="dark" variant="dark" toggleable='lg')
|
||||
//- template(slot='footer')
|
||||
//- b-navbar-nav
|
||||
//- b-button(variant='success') {{$t('Share')}} <v-icon name='share'/>
|
||||
//- b-nav-item( {{$t('')}})
|
||||
//- b-card-footer.text-right
|
||||
//- span.mr-3 {{event.comments.length}} <v-icon name='comments'/>
|
||||
//- a(href='#', @click='remove')
|
||||
v-icon(color='orange' name='times')
|
||||
//- b-card-footer(v-for='comment in event.comments')
|
||||
strong {{comment.author}}
|
||||
div(v-html='comment.text')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import api from '@/api'
|
||||
import filters from '@/filters'
|
||||
|
||||
export default {
|
||||
name: 'EventDetail',
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
imgPath () {
|
||||
return this.event.image_path && process.env.VUE_APP_API + '/uploads/' + this.event.image_path
|
||||
},
|
||||
mine () {
|
||||
return this.event.userId === this.user.id || this.user.is_admin
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
event: { comments: [], place: {}, title: ''},
|
||||
id: null,
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.id = this.$route.params.id
|
||||
this.load()
|
||||
},
|
||||
filters,
|
||||
methods: {
|
||||
...mapActions(['delEvent']),
|
||||
async load () {
|
||||
const event = await api.getEvent(this.id)
|
||||
this.event = event
|
||||
this.loading = false
|
||||
},
|
||||
async remove () {
|
||||
await api.delEvent(this.event.id)
|
||||
this.delEvent(this.event.id)
|
||||
this.$refs.eventDetail.hide()
|
||||
},
|
||||
async toggle () {
|
||||
try {
|
||||
if (this.event.is_visible) {
|
||||
|
||||
await api.unconfirmEvent(this.id)
|
||||
this.event.is_visible = false
|
||||
} else {
|
||||
await api.confirmEvent(this.id)
|
||||
this.event.is_visible = true
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#eventDetail .modal-body {
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#eventDetail .close_button:hover {
|
||||
background-color: rgba(200, 100, 100, 0.4);
|
||||
}
|
||||
|
||||
#eventDetail .card {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
#eventDetail .close_button {
|
||||
background-color: rgba(100, 100, 100, 0.4);
|
||||
color: red;
|
||||
font-size: 20px;
|
||||
border: none;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-modal(ref='modal' @hidden='$router.replace("/")'
|
||||
:title='$t("Export")' :visible='true' size='lg' hide-footer)
|
||||
p {{$t('export_intro')}}
|
||||
|
||||
li(v-if='filters.tags.length') {{$t('Tags')}}:
|
||||
el-tag.ml-1(color='#409EFF' size='mini' v-for='tag in filters.tags' :key='tag.tag') {{tag}}
|
||||
li(v-if='filters.places.length') {{$t('Places')}}:
|
||||
el-tag.ml-1(color='#409EFF' size='mini' v-for='place in filters.places' :key='place.id') {{place}}
|
||||
el-tabs.mt-2(tabPosition='left' v-model='type')
|
||||
|
||||
el-tab-pane.pt-1(label='email' name='email')
|
||||
p(v-html='$t(`export_email_explanation`)')
|
||||
el-form(@submit.native.prevent)
|
||||
//- el-switch(v-model='notification.notify_on_add' :active-text="$t('notify_on_insert')")
|
||||
//- br
|
||||
//- el-switch.mt-2(v-model='notification.send_notification' :active-text="$t('send_notification')")
|
||||
el-input.mt-2(v-model='notification.email' :placeholder="$t('Insert your address')" ref='email')
|
||||
el-button.mt-2.float-right(native-type= 'submit' type='success' @click='add_notification') {{$t('Send')}}
|
||||
|
||||
el-tab-pane.pt-1(label='feed rss' name='feed')
|
||||
span(v-html='$t(`export_feed_explanation`)')
|
||||
el-input(v-model='link')
|
||||
el-button(slot='append' plain type="primary" icon='el-icon-document' v-clipboard:copy="link") {{$t("Copy")}}
|
||||
|
||||
el-tab-pane.pt-1(label='ics/ical' name='ics')
|
||||
p(v-html='$t(`export_ical_explanation`)')
|
||||
el-input(v-model='link')
|
||||
el-button(slot='append' plain type="primary" icon='el-icon-document' v-clipboard:copy="link") {{$t("Copy")}}
|
||||
|
||||
el-tab-pane.pt-1(label='list' name='list')
|
||||
p(v-html='$t(`export_list_explanation`)')
|
||||
el-card.mb-1(no-body header='Eventi')
|
||||
b-list-group#list(flush)
|
||||
b-list-group-item.flex-column.align-items-start(v-for="event in filteredEvents" :key='event.id'
|
||||
:to='`/event/${event.id}`')
|
||||
//- b-media
|
||||
img(v-if='event.image_path' slot="aside" :src="imgPath(event)" alt="Meia Aside" style='max-height: 60px')
|
||||
small.float-right {{event.start_datetime|datetime}}
|
||||
strong.mb-1 {{event.title}}
|
||||
br
|
||||
small.float-right {{event.place.name}}
|
||||
el-tag.mr-1(:color='tag.color || "grey"' size='mini' v-for='tag in event.tags' :key='tag.tag') {{tag.tag}}
|
||||
el-input.mb-1(type='textarea' v-model='script')
|
||||
el-button.float-right(plain type="primary" icon='el-icon-document' v-clipboard:copy="script") Copy
|
||||
|
||||
|
||||
el-tab-pane.pt-1(label='calendar' name='calendar')
|
||||
p(v-html='$t(`export_calendar_explanation`)')
|
||||
Calendar.mb-1
|
||||
el-input.mb-1(type='textarea' v-model='script')
|
||||
el-button.float-right(plain type="primary" icon='el-icon-document' v-clipboard:copy="script") Copy
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import path from 'path'
|
||||
import filters from '../filters'
|
||||
import Calendar from '@/components/Calendar'
|
||||
import {intersection} from 'lodash'
|
||||
import api from '@/api'
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
export default {
|
||||
name: 'Export',
|
||||
components: { Calendar },
|
||||
data () {
|
||||
return {
|
||||
type: 'email',
|
||||
link: '',
|
||||
export_list: true,
|
||||
script: `<iframe>Ti piacerebbe</iframe>`,
|
||||
notification: { email: '' },
|
||||
}
|
||||
},
|
||||
filters,
|
||||
mounted () {
|
||||
this.link = this.loadLink()
|
||||
},
|
||||
watch: {
|
||||
type (value) {
|
||||
this.link = this.loadLink()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async add_notification () {
|
||||
if (!this.notification.email){
|
||||
Message({message:'Inserisci una mail', type: 'error'})
|
||||
return this.$refs.email.focus()
|
||||
}
|
||||
await api.addNotification({ ...this.notification, filters: this.filters})
|
||||
this.$refs.modal.hide()
|
||||
Message({message: this.$t('email_notification_activated'), type: 'success'})
|
||||
},
|
||||
loadLink () {
|
||||
const tags = this.filters.tags.join(',')
|
||||
const places = this.filters.places.join(',')
|
||||
let query = ''
|
||||
if (tags || places) {
|
||||
query = '?'
|
||||
if (tags) {
|
||||
query += 'tags=' + tags
|
||||
if (places) { query += '&places=' + places }
|
||||
} else {
|
||||
query += 'places=' + places
|
||||
}
|
||||
}
|
||||
|
||||
return `${process.env.VUE_APP_API}/api/export/${this.type}${query}`
|
||||
},
|
||||
imgPath (event) {
|
||||
return event.image_path && event.image_path
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['filters', 'user', 'logged', 'events']),
|
||||
filteredEvents () {
|
||||
return this.$store.getters.filteredEvents.filter(e => !e.past)
|
||||
},
|
||||
showLink () {
|
||||
return (['feed', 'ics'].indexOf(this.type)>-1)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#list {
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<template lang="pug">
|
||||
magic-grid(:animate="false" useMin :gap=5 :maxCols=4
|
||||
:maxColWidth='400' ref='magicgrid')
|
||||
div.mt-1.item
|
||||
Search#search
|
||||
Calendar
|
||||
Event.item.mt-1(v-for='event in filteredEvents'
|
||||
:key='event.id'
|
||||
:event='event')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import filters from '@/filters.js'
|
||||
import Event from '@/components/Event'
|
||||
import Calendar from '@/components/Calendar'
|
||||
import {intersection} from 'lodash'
|
||||
import moment from 'dayjs'
|
||||
import Search from '@/components/Search'
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: { Event, Calendar, Search },
|
||||
watch: {
|
||||
filteredEvents () {
|
||||
this.$nextTick(this.$refs.magicgrid.positionItems)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['events', 'filters']),
|
||||
filteredEvents () {
|
||||
return this.$store.getters.filteredEvents
|
||||
.filter(e => !e.past)
|
||||
.sort((a, b) => { a.start_datetime > b.start_datetime})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
#search {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
column-count: 1;
|
||||
column-gap: 0.2em;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: none;
|
||||
}
|
||||
.card-columns {
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 950px) {
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
}
|
||||
.card-columns {
|
||||
column-count: 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* .item {
|
||||
transition: all .2s;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
} */
|
||||
/* .list-enter, .list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 0px;
|
||||
left: 0px;
|
||||
height: 0px;
|
||||
z-index: -10;
|
||||
} */
|
||||
</style>
|
||||
@@ -1,66 +0,0 @@
|
||||
<template lang='pug'>
|
||||
b-modal(@shown="$refs.email.focus()" :title='$t("Login")' hide-footer
|
||||
@hidden='$router.replace("/")' :visible='true' ref='modal')
|
||||
el-form(v-loading='loading')
|
||||
p(v-html="$t('login_explanation')")
|
||||
el-input.mb-2(v-model='email' type='email' :placeholder='$t("Email")' autocomplete='email' ref='email')
|
||||
v-icon(name='user' slot='prepend')
|
||||
el-input.mb-1(v-model='password' @keyup.enter.native="submit" type='password' :placeholder='$t("Password")')
|
||||
v-icon(name="lock" slot='prepend')
|
||||
el-button.mr-1(plain type="success" @click='submit') {{$t('Login')}}
|
||||
router-link(to='/register')
|
||||
el-button.mt-1(plain type="primary") {{$t('Not registered?')}}
|
||||
a.float-right(href='#' @click='forgot') {{$t('Forgot password?')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { mapActions } from 'vuex'
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data () {
|
||||
return {
|
||||
password: '',
|
||||
email: '',
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['login']),
|
||||
async forgot () {
|
||||
if (!this.email) {
|
||||
Message({ message: this.$t('Insert your email'), type: 'error' })
|
||||
this.$refs.email.focus()
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
await api.forgotPassword(this.email)
|
||||
this.loading = false
|
||||
Message({ message: this.$t('Check your email!'), type: 'success' })
|
||||
},
|
||||
async submit (e) {
|
||||
e.preventDefault()
|
||||
try {
|
||||
this.loading = true
|
||||
const user = await api.login(this.email, this.password)
|
||||
this.loading = false
|
||||
if (!user) {
|
||||
Message({ message: this.$t('Login error'), type: 'error' })
|
||||
return;
|
||||
}
|
||||
this.login(user)
|
||||
Message({ message: this.$t('Logged'), type: 'success' })
|
||||
} catch (e) {
|
||||
Message({ message: this.$t('Login error'), type: 'error' })
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.email = this.password = ''
|
||||
this.$refs.modal.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,50 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-navbar(type="dark" variant="dark" toggleable='md')
|
||||
b-navbar-toggle(target='nav_collapse')
|
||||
b-navbar-brand(to='/') <img id='logo' src='gancio_logo.svg'/>
|
||||
b-collapse#nav_collapse(is-nav)
|
||||
b-navbar-nav
|
||||
b-nav-item(v-if='!logged' to='/login' v-b-tooltip :title='$t("Login")') <v-icon color='lightgreen' name='lock' />
|
||||
span.d-md-none {{$t('User')}}
|
||||
b-nav-item(to='/new_event' v-b-tooltip :title='$t("Add Event")' ) <v-icon color='lightgreen' name='plus'/>
|
||||
span.d-md-none {{$t('Add Event')}}
|
||||
b-nav-item(v-if='logged' to='/settings' v-b-tooltip :title='$t("Settings")') <v-icon color='orange' name='cog'/>
|
||||
span.d-md-none {{$t('Settings')}}
|
||||
b-nav-item(v-if='user.is_admin' to='/admin' v-b-tooltip :title='$t("Admin")') <v-icon color='lightblue' name='tools'/>
|
||||
span.d-md-none {{$t('Admin')}}
|
||||
b-nav-item(to='/export' v-b-tooltip :title='$t("Export")') <v-icon name='file-export' color='yellow'/>
|
||||
span.d-md-none {{$t('Export')}}
|
||||
b-nav-item(v-if='logged' @click='logout' v-b-tooltip :title='$t("Logout")') <v-icon color='red' name='sign-out-alt'/>
|
||||
span.d-md-none {{$t('Logout')}}
|
||||
b-navbar-nav.ml-auto
|
||||
b-nav-item(to='/about')
|
||||
span {{$t('Info')}} <v-icon color='#ff9fc4' name='question-circle'/>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import {mapState, mapActions} from 'vuex'
|
||||
export default {
|
||||
name: 'Nav',
|
||||
computed: {
|
||||
...mapState(['logged', 'user','filters']),
|
||||
filters_tags: {
|
||||
set (value) {
|
||||
this.setSearchTags(value)
|
||||
},
|
||||
get () {
|
||||
return this.filters.tags
|
||||
}
|
||||
},
|
||||
filters_places: {
|
||||
set (value) {
|
||||
this.setSearchPlaces(value)
|
||||
},
|
||||
get () {
|
||||
return this.filters.places
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: mapActions(['logout']),
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<template lang='pug'>
|
||||
b-modal(@shown="$refs.password.focus()" :title='$t("Password reset")' hide-footer
|
||||
@hidden='$router.replace("/")' :visible='true' ref='modal')
|
||||
div(v-loading='loading')
|
||||
p(v-html="$t('recover_explanation')")
|
||||
span(v-if='user.email') {{user.email}}
|
||||
el-input.mb-2(v-model='password' type='password' :placeholder='$t("New password")' ref='password')
|
||||
v-icon(name='lock' slot='prepend')
|
||||
el-button.mr-1(plain type="success" @click='change') {{$t('Change')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { mapActions } from 'vuex'
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Recover',
|
||||
data () {
|
||||
return {
|
||||
password: '',
|
||||
loading: true,
|
||||
user: {}
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
try {
|
||||
this.user = await api.checkRecoverCode(this.$route.params.recover_code)
|
||||
console.log(this.user)
|
||||
} catch (e) {
|
||||
this.$refs.modal.hide()
|
||||
Message({message: this.$t('error_recover_code'), type: 'error'})
|
||||
}
|
||||
this.loading = false
|
||||
|
||||
},
|
||||
methods: {
|
||||
async change () {
|
||||
if (!this.password) return
|
||||
this.loading = true
|
||||
try {
|
||||
await api.recoverPassword(this.$route.params.recover_code, this.password)
|
||||
} catch (e) {
|
||||
this.loading = false
|
||||
Message({ message: this.$t('password_not_changed'), type: 'error'})
|
||||
return
|
||||
}
|
||||
this.loading = false
|
||||
// this.$refs.modal.hide()
|
||||
Message({ message: this.$t('password_changed'), type: 'success'})
|
||||
this.$router.replace('/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,60 +0,0 @@
|
||||
<template lang='pug'>
|
||||
b-modal(hide-footer @hidden='$router.replace("/")' ref='modal'
|
||||
:title="$t('Register')" :visible='true' @shown='$refs.email.focus()')
|
||||
el-form
|
||||
p(v-html="$t('register_explanation')")
|
||||
el-input.mb-2(ref='email' v-model='user.email' type='email'
|
||||
:placeholder='$t("Email")' autocomplete='email')
|
||||
span(slot='prepend') @
|
||||
|
||||
el-input.mb-2(v-model='user.password' type="password" placeholder="Password")
|
||||
v-icon(name='lock' slot='prepend')
|
||||
|
||||
el-input.mb-2(v-model='user.description' type="textarea" rows='3' :placeholder="$t('Description')")
|
||||
v-icon(name='envelope-open-text')
|
||||
|
||||
|
||||
el-button.float-right(plain type="success" icon='el-icon-arrow-right' @click='register') {{$t('Send')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { mapActions } from 'vuex';
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
export default {
|
||||
name: 'Register',
|
||||
data () {
|
||||
return {
|
||||
error: {},
|
||||
user: { }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['login']),
|
||||
async register () {
|
||||
try {
|
||||
const user = await api.register(this.user)
|
||||
if (!user.is_admin) {
|
||||
this.$refs.modal.hide()
|
||||
Message({
|
||||
message: this.$t('registration_complete'),
|
||||
type: 'success'
|
||||
})
|
||||
} else {
|
||||
Message({
|
||||
message: this.$t('admin_registration_complete'),
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
Message({
|
||||
message: e,
|
||||
type: 'error'
|
||||
})
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,39 +0,0 @@
|
||||
<template lang="pug">
|
||||
div
|
||||
el-select.mr-1(v-model='filters_places' multiple filterable collapse-tags
|
||||
default-first-option :placeholder='$t("Where")')
|
||||
el-option(v-for='place in places' :value='place.name'
|
||||
:label='place.name' :key='place.id')
|
||||
el-select(v-model='filters_tags' multiple filterable collapse-tags
|
||||
default-first-option :placeholder='$t("Tags")')
|
||||
el-option(v-for='tag in tags' :key='tag.tag'
|
||||
:label='tag.tag' :value='tag.tag')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState, mapActions} from 'vuex'
|
||||
|
||||
export default {
|
||||
name :'Search',
|
||||
methods: mapActions(['setSearchPlaces', 'setSearchTags']),
|
||||
computed: {
|
||||
...mapState(['tags', 'places', 'filters']),
|
||||
filters_tags: {
|
||||
set (value) {
|
||||
this.setSearchTags(value)
|
||||
},
|
||||
get () {
|
||||
return this.filters.tags
|
||||
}
|
||||
},
|
||||
filters_places: {
|
||||
set (value) {
|
||||
this.setSearchPlaces(value)
|
||||
},
|
||||
get () {
|
||||
return this.filters.places
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,60 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-modal(:title="$t('Settings')" hide-footer @hidden='$router.replace("/")' :visible='true')
|
||||
h5 {{user.name}}
|
||||
el-input(v-model="mastodon_instance" @enter.native='associate')
|
||||
span(slot='prepend') {{$t('Mastodon instance')}}
|
||||
el-button(v-if='!user.mastodon_auth' slot='append' @click='associate' type='success') {{$t('Associate')}}
|
||||
el-button(v-else slot='append' @click='deassociate' variant='success') {{$t('De-associate')}}
|
||||
el-input.mt-2(v-model='password' type='password')
|
||||
span(slot='prepend') {{$t('Change password')}}
|
||||
el-button(slot='append' @click='change' type='success') {{$t('Change')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import api from '@/api'
|
||||
export default {
|
||||
props: ['code'],
|
||||
data () {
|
||||
return {
|
||||
mastodon_instance: '',
|
||||
password: '',
|
||||
user: {}
|
||||
}
|
||||
},
|
||||
computed: mapState(['oauth', 'user']),
|
||||
async mounted () {
|
||||
const code = this.$route.query.code
|
||||
if (code) {
|
||||
const res = await api.setCode({code})
|
||||
}
|
||||
|
||||
const user = await api.getUser()
|
||||
this.user = user
|
||||
this.mastodon_instance = user.mastodon_auth.instance
|
||||
},
|
||||
methods: {
|
||||
async change () {
|
||||
if (!this.password) return
|
||||
const user = this.user
|
||||
user.password = this.password
|
||||
try {
|
||||
await api.updateUser(user)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
},
|
||||
async deassociate () {
|
||||
const user = this.user
|
||||
user.mastodon_auth = ''
|
||||
this.mastodon_instance = ''
|
||||
await api.updateUser(user)
|
||||
},
|
||||
async associate () {
|
||||
if (!this.mastodon_instance) return
|
||||
const url = await api.getAuthURL({instance: this.mastodon_instance})
|
||||
setTimeout( () => window.location.href=url, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// <template lang="pug">
|
||||
// b-card.column.pl-1(bg-variant='dark' text-variant='white' no-body)
|
||||
// b-card-header
|
||||
// strong Public events
|
||||
// b-btn.float-right(v-if='logged' variant='success' size='sm' to='/newEvent') <v-icon name="plus"/> Add Event
|
||||
// event(v-for='event in events', :event='event' :key='event.id')
|
||||
// </template>
|
||||
// <script>
|
||||
// import api from '@/api'
|
||||
// import event from './Event'
|
||||
// import { mapState } from 'vuex';
|
||||
|
||||
// export default {
|
||||
// components: {event},
|
||||
// computed: mapState(['events', 'logged'])
|
||||
// }
|
||||
// </script>
|
||||
@@ -1,9 +0,0 @@
|
||||
import Register from '@/components/Register'
|
||||
import Login from '@/components/Login'
|
||||
import Settings from '@/components/Settings'
|
||||
import newEvent from '@/components/newEvent'
|
||||
import eventDetail from '@/components/EventDetail'
|
||||
import Home from '@/components/Home'
|
||||
import Event from '@/components/Event'
|
||||
|
||||
export default { Home, eventDetail, newEvent, Settings, Login, Register, Event }
|
||||
@@ -1,215 +0,0 @@
|
||||
<template lang="pug">
|
||||
b-modal(ref='modal' @hidden='$router.replace("/")' size='lg' :visible='true'
|
||||
:title="edit?$t('Edit event'):$t('New event')" hide-footer)
|
||||
el-form
|
||||
el-tabs.mb-2(v-model='activeTab' v-loading='sending')
|
||||
|
||||
//- NOT LOGGED EVENT
|
||||
el-tab-pane(v-if='!logged')
|
||||
span(slot='label') {{$t('anon_newevent')}} <v-icon name='user-secret'/>
|
||||
p(v-html="$t('anon_newevent_explanation')")
|
||||
el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}}
|
||||
|
||||
//- WHERE
|
||||
el-tab-pane
|
||||
span(slot='label') {{$t('Where')}} <v-icon name='map-marker-alt'/>
|
||||
div {{$t('where_explanation')}}
|
||||
el-select.mb-3(v-model='event.place.name' @change='placeChoosed' filterable allow-create default-first-option)
|
||||
el-option(v-for='place in places_name' :label='place' :value='place' :key='place.id')
|
||||
div {{$t("Address")}}
|
||||
el-input.mb-3(ref='address' v-model='event.place.address' @keydown.native.enter='next')
|
||||
el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}}
|
||||
|
||||
//- WHEN
|
||||
el-tab-pane
|
||||
span(slot='label') {{$t('When')}} <v-icon name='clock'/>
|
||||
span {{event.multidate ? $t('dates_explanation') : $t('date_explanation')}}
|
||||
el-switch.float-right(v-model='event.multidate' :active-text="$t('multidate_explanation')")
|
||||
v-date-picker.mb-3(:mode='event.multidate ? "range" : "single"' v-model='date' is-inline
|
||||
is-expanded :min-date='new Date()' @input='date ? $refs.time_start.focus() : false')
|
||||
div {{$t('time_start_explanation')}}
|
||||
el-time-select.mb-3(ref='time_start'
|
||||
v-model="time.start"
|
||||
:picker-options="{ start: '00:00', step: '00:30', end: '24:00'}")
|
||||
div {{$t('time_end_explanation')}}
|
||||
el-time-select(v-model='time.end'
|
||||
:picker-options="{start: '00:00', step: '00:30', end: '24:00'}")
|
||||
el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}}
|
||||
|
||||
//- WHAT
|
||||
el-tab-pane
|
||||
span(slot='label') {{$t('What')}} <v-icon name='file-alt'/>
|
||||
span {{$t('what_explanation')}}
|
||||
el-input.mb-3(v-model='event.title' ref='title')
|
||||
span {{$t('description_explanation')}}
|
||||
el-input.mb-3(v-model='event.description' type='textarea' :rows='9')
|
||||
span {{$t('tag_explanation')}}
|
||||
br
|
||||
el-select(v-model='event.tags' multiple filterable allow-create
|
||||
default-first-option placeholder='Tag')
|
||||
el-option(v-for='tag in tags' :key='tag.tag'
|
||||
:label='tag' :value='tag')
|
||||
|
||||
el-button.float-right(@click.native='next' :disabled='!couldProceed') {{$t('Next')}}
|
||||
|
||||
el-tab-pane
|
||||
span(slot='label') {{$t('Media')}} <v-icon name='image'/>
|
||||
span {{$t('media_explanation')}}
|
||||
b-form-file.mb-2(v-model='event.image', :placeholder='$t("Poster")' accept='image/*')
|
||||
el-button.float-right(@click='done') {{edit?$t('Edit'):$t('Send')}}
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import moment from 'dayjs'
|
||||
import Calendar from './Calendar'
|
||||
import { Message } from 'element-ui'
|
||||
export default {
|
||||
components: { Calendar },
|
||||
data() {
|
||||
return {
|
||||
event: {
|
||||
place: { name: '', address: '' },
|
||||
title: '', description: '', tags: [],
|
||||
multidate: false,
|
||||
},
|
||||
visible: true,
|
||||
id: null,
|
||||
activeTab: "0",
|
||||
date: null,
|
||||
time: { start: '20:00', end: null },
|
||||
edit: false,
|
||||
sending: false,
|
||||
}
|
||||
},
|
||||
name: 'newEvent',
|
||||
watch: {
|
||||
'time.start' (value) {
|
||||
let [h, m] = value.split(':')
|
||||
this.time.end = (Number(h)+1) + ':' + m
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
if (this.$route.params.id) {
|
||||
this.id = this.$route.params.id
|
||||
this.edit = true
|
||||
const event = await api.getEvent(this.id)
|
||||
// this.event.place = {name: event.place.name, address: event.place.address }
|
||||
this.event.place.name = event.place.name
|
||||
this.event.place.address = event.place.address || ''
|
||||
this.event.multidate = event.multidate
|
||||
this.date = event.start_datetime
|
||||
this.time.start = moment(event.start_datetime).format('HH:mm')
|
||||
this.time.end = moment(event.end_datetime).format('HH:mm')
|
||||
this.event.title = event.title
|
||||
this.event.description = event.description.replace(/(<([^>]+)>)/ig, '')
|
||||
this.event.id = event.id
|
||||
if (event.tags) {
|
||||
this.event.tags = event.tags.map(t => t.tag)
|
||||
}
|
||||
|
||||
}
|
||||
this.updateMeta()
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
tags: state => state.tags.map(t => t.tag ),
|
||||
places_name: state => state.places.map(p => p.name ),
|
||||
places: state => state.places,
|
||||
user: state => state.user,
|
||||
logged: state => state.logged
|
||||
}),
|
||||
couldProceed () {
|
||||
const t = this.logged ? -1 : 0
|
||||
switch(Number(this.activeTab)) {
|
||||
case 0+t:
|
||||
return true
|
||||
case 1+t:
|
||||
return this.event.place.name.length>0 &&
|
||||
this.event.place.address.length>0
|
||||
case 2+t:
|
||||
if (this.date && this.time.start) return true
|
||||
break
|
||||
case 3+t:
|
||||
return this.event.title.length>0
|
||||
break
|
||||
case 4+t:
|
||||
return true
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['addEvent', 'updateEvent', 'updateMeta']),
|
||||
next () {
|
||||
this.activeTab = String(Number(this.activeTab)+1)
|
||||
if (this.activeTab === "2") {
|
||||
this.$refs.title.focus()
|
||||
}
|
||||
},
|
||||
prev () {
|
||||
this.activeTab = String(Number(this.activeTab-1))
|
||||
},
|
||||
placeChoosed () {
|
||||
const place = this.places.find( p => p.name === this.event.place.name )
|
||||
if (place && place.address) {
|
||||
this.event.place.address = place.address
|
||||
}
|
||||
this.$refs.address.focus()
|
||||
},
|
||||
async done () {
|
||||
let start_datetime, end_datetime
|
||||
const [ start_hour, start_minute ] = this.time.start.split(':')
|
||||
if (!this.time.end) {
|
||||
this.time.end = this.time.start
|
||||
}
|
||||
const [ end_hour, end_minute ] = this.time.end.split(':')
|
||||
if (this.event.multidate) {
|
||||
start_datetime = moment(this.date.start)
|
||||
.set('hour', start_hour).set('minute', start_minute)
|
||||
end_datetime = moment(this.date.end)
|
||||
.set('hour', end_hour).set('minute', end_minute)
|
||||
} else {
|
||||
console.log(this.date)
|
||||
start_datetime = moment(this.date).set('hour', start_hour).set('minute', start_minute)
|
||||
end_datetime = moment(this.date).set('hour', end_hour).set('minute', end_minute)
|
||||
}
|
||||
const formData = new FormData()
|
||||
|
||||
if (this.event.image) {
|
||||
formData.append('image', this.event.image, this.event.image.name)
|
||||
}
|
||||
formData.append('title', this.event.title)
|
||||
formData.append('place_name', this.event.place.name)
|
||||
formData.append('place_address', this.event.place.address)
|
||||
formData.append('description', this.event.description)
|
||||
formData.append('multidate', this.event.multidate)
|
||||
formData.append('start_datetime', start_datetime)
|
||||
formData.append('end_datetime', end_datetime)
|
||||
if (this.edit) {
|
||||
formData.append('id', this.event.id)
|
||||
}
|
||||
if (this.event.tags)
|
||||
this.event.tags.forEach(tag => formData.append('tags[]', tag))
|
||||
this.sending = true
|
||||
try {
|
||||
if (this.edit) {
|
||||
await this.updateEvent(formData)
|
||||
} else {
|
||||
await this.addEvent(formData)
|
||||
}
|
||||
this.updateMeta()
|
||||
this.sending = false
|
||||
this.$refs.modal.hide()
|
||||
Message({ type: 'success', message: this.logged ? this.$t('new_event_added') : this.$t('new_anon_event_added')})
|
||||
} catch (e) {
|
||||
this.sending = false
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,15 +0,0 @@
|
||||
import moment from 'dayjs'
|
||||
import 'dayjs/locale/it'
|
||||
moment.locale('it')
|
||||
|
||||
export default {
|
||||
datetime (value) {
|
||||
return moment(value).format('ddd, D MMMM HH:mm')
|
||||
},
|
||||
short_datetime (value) {
|
||||
return moment(value).format('D/MM HH:mm')
|
||||
},
|
||||
hour (value) {
|
||||
return moment(value).format('HH:mm')
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
const en = {
|
||||
where_explanation: 'Specify event\' place',
|
||||
address_explanation: 'Insert address',
|
||||
multidate_explanation: 'Multiple date?',
|
||||
when_explanation: 'Select a day',
|
||||
what_explanation: 'Event\'s title',
|
||||
description_explanation: 'Describe the event',
|
||||
date_explanation: 'Select the day',
|
||||
dates_explanation: 'Select the days',
|
||||
time_start_explanation: 'Insert start time',
|
||||
time_end_explanation: 'You could insert end time',
|
||||
media_explanation: 'You can upload a media',
|
||||
tag_explanation: 'Insert a tag',
|
||||
export_intro: `Sharing is caring.`,
|
||||
export_feed_explanation: `To follow updates from pc and mobile with, we reccomend the use of RSS.<br/>
|
||||
Thanks to RSS you can use any application able to receive updates from the websites you like.
|
||||
Using RSS you can to follow in a quick way all the websites you like with no need to create an account<br/>
|
||||
|
||||
If you have Android, you can use <a href="https://play.google.com/store/apps/details?id=net.frju.flym">Flym</a> or Feeder<br />
|
||||
If you have iOS you can use <a href="https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8">Feed4U</a><br />
|
||||
For computers you can use the Browser plugin Feedbro,
|
||||
<a href="https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/">[ for Firefox ] </a>
|
||||
and <a href="https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa">[ for Chrome ]</a> </br>
|
||||
Feedbro is compatible with every computer using Firefox or Chrome.</p><br/>
|
||||
Using the link above you will be updated on the following events:`,
|
||||
SignIn: 'Sign In',
|
||||
registration_email: `Hi, your registration will be confirmed soon.`,
|
||||
register_explanation: `..`
|
||||
}
|
||||
|
||||
export default en
|
||||
@@ -1,109 +0,0 @@
|
||||
const it = {
|
||||
'Add Event': 'Nuovo evento',
|
||||
Where: 'Dove',
|
||||
When: 'Quando',
|
||||
What: 'Cosa',
|
||||
Media: 'Locandina',
|
||||
where_explanation: 'Specifica il luogo dell\'evento',
|
||||
address_explanation: 'Inserisci l\'indirizzo',
|
||||
multidate_explanation: 'Dura tanti giorni?',
|
||||
when_explanation: 'Seleziona un giorno',
|
||||
what_explanation: 'Titolo dell\'evento (es. Corteo Antimilitarista),',
|
||||
description_explanation: 'Descrivi l\'evento, dajene di copia/incolla',
|
||||
date_explanation: 'Seleziona il giorno',
|
||||
dates_explanation: 'Seleziona i giorni',
|
||||
time_start_explanation: 'Orario di inizio',
|
||||
time_end_explanation: 'Orario di fine',
|
||||
media_explanation: 'Se vuoi puoi mettere una locandina/manifesto',
|
||||
tag_explanation: 'Puoi inserire un tag (es. concerto, corteo)',
|
||||
export_intro: `Contrariamente alle piattaforme del capitalismo, che fanno di tutto per tenere
|
||||
i dati e gli utenti al loro interno, crediamo che le informazioni, come le persone,
|
||||
debbano essere libere. Per questo puoi rimanere aggiornata sugli eventi che vuoi, come meglio credi, senza necessariamente passare da questo sito.`,
|
||||
export_feed_explanation: `Per seguire gli aggiornamenti da computer o smartphone senza la necessità di aprire periodicamente il sito, il metodo consigliato è quello dei Feed RSS.</p>
|
||||
|
||||
<p>Con i feed rss utilizzi un'apposita applicazione per ricevere aggiornamenti dai siti che più ti interessano. È un buon metodo per seguire anche molti siti in modo molto rapido, senza necessità di creare un account o altre complicazioni.</p>
|
||||
|
||||
<li>Se hai Android, ti consigliamo <a href="https://play.google.com/store/apps/details?id=net.frju.flym">Flym</a> o Feeder</li>
|
||||
<li>Per iPhone/iPad puoi usare <a href="https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8">Feed4U</a></li>
|
||||
<li>Per il computer fisso/portatile consigliamo Feedbro, da installare all'interno <a href="https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/">di Firefox </a>o <a href="https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa">di Chrome</a> e compatibile con tutti i principali sistemi operativi.</li>
|
||||
<br/>
|
||||
Aggiungendo questo link al tuo lettore di feed, rimarrai aggiornata.`,
|
||||
export_email_explanation: `Puoi ricevere via mail gli eventi che ti interessano.`,
|
||||
export_list_explanation: `Se hai un sito web e vuoi mostrare una lista di eventi, puoi usare il seguente codice (ehm, questa funzionalita' la stiamo ancora sviluppando)`,
|
||||
export_calendar_explanation: `Se hai un sito web e vuoi mostrare un calendario di eventi, puoi usare il seguente codice (ehm, questa funzionalita' la stiamo ancora sviluppando)`,
|
||||
export_ical_explanation: `I computer e gli smartphone sono comunemente attrezzati con un'applicazione per gestire un calendario. A questi programmi solitamente è possibile far importare un calendario remoto.`,
|
||||
Poster: 'Locandina',
|
||||
Settings: 'Impostazioni',
|
||||
'Change password': 'Cambia password',
|
||||
Change: 'Cambia',
|
||||
Search: 'Cerca',
|
||||
Send: 'Invia',
|
||||
Register: 'Registrati',
|
||||
Logout: 'Esci',
|
||||
Login: 'Entra',
|
||||
SignIn: 'Registrati',
|
||||
Cancel: 'Annulla',
|
||||
Copy: 'Copia',
|
||||
Next: 'Continua',
|
||||
Prev: 'Indietro',
|
||||
Username: 'Utente',
|
||||
Description: 'Descrizione',
|
||||
Deactivate: 'Disattiva',
|
||||
Activate: 'Attiva',
|
||||
'Remove Admin': 'Rimuovi Admin',
|
||||
'Remove notification': 'Rimuovi notifiche',
|
||||
Yes: 'Sì',
|
||||
Users: 'Utenti',
|
||||
Places: 'Luoghi',
|
||||
Tags: 'Etichette',
|
||||
Name: 'Nome',
|
||||
Preview: 'Visualizza',
|
||||
Save: 'Salva',
|
||||
Address: 'Indirizzo',
|
||||
Remove: 'Elimina',
|
||||
Password: 'Password',
|
||||
Email: 'Email',
|
||||
User: 'Utente',
|
||||
Unconfirm: 'Disabilita',
|
||||
Confirm: 'Conferma',
|
||||
Events: 'Eventi',
|
||||
Color: 'Colore',
|
||||
Edit: 'Modifica',
|
||||
Admin: 'Amministra',
|
||||
Today: 'Oggi',
|
||||
Export: 'Esporta',
|
||||
'Forgot password?': 'Hai dimenticato la password?',
|
||||
'Mastodon instance': 'Istanza Mastodon',
|
||||
'admin_mastodon_explanation': 'Puoi associare un account mastodon a questa istanza di gancio, ogni evento verrà pubblicato lì',
|
||||
Associate: 'Associa',
|
||||
admin_tag_explanation: `Seleziona un'etichetta per cambiarne il colore`,
|
||||
event_confirmed: 'Evento confermato!',
|
||||
notify_on_insert: `Notifica all'inserimento`,
|
||||
'event_confirm_explanation': 'Puoi approvare gli eventi inseriti da utenti non registrati',
|
||||
'admin_place_explanation': 'Puoi modificare i luoghi inseriti',
|
||||
'Edit event': 'Modifica evento',
|
||||
'New event': 'Nuovo evento',
|
||||
anon_newevent: 'Evento senza autore',
|
||||
anon_newevent_explanation: `Puoi inserire un evento senza rigistrarti o fare il login, ma in questo caso dovrai aspettare che qualcuno lo legga confermando che si tratta di un evento adatto a questo spazio, delegando questa scelta.<br/><br/> Puoi fare il <a href='/login'>login</a> o <a href='/registrarti'>registrarti</a>, altrimenti vai avanti e riceverai una risposta il prima possibile!`,
|
||||
'Insert your address': 'Inserisci il tuo indirizzo',
|
||||
registration_complete: 'Controlla la tua posta (anche la cartella spam)',
|
||||
registration_email: `Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere.`,
|
||||
register_explanation: `I movimenti hanno bisogno di organizzarsi e autofinanziarsi. <br/>Questo è un dono per voi, usatelo solo per eventi non commerciali e ovviamente antifascisti, antisessisti, antirazzisti.
|
||||
<br/>Prima di poter pubblicare <strong>dobbiamo approvare l'account</strong>, considera che <strong>dietro questo sito ci sono delle persone</strong> di
|
||||
carne e sangue, scrivici quindi due righe per farci capire che eventi vorresti pubblicare.`,
|
||||
'Not registered?': `Non sei registrata?`,
|
||||
new_event_added: `Evento aggiunto.`,
|
||||
new_anon_event_added: `Evento inserito, verrà confermato quanto prima.`,
|
||||
login_explanation: `Entrando puoi pubblicare nuovi eventi.`,
|
||||
recover_explanation: `Inserisci una nuova password:`,
|
||||
'New password': 'Nuova password',
|
||||
'Password reset': 'Password reset',
|
||||
password_changed: 'Password cambiata con successo',
|
||||
password_not_changed: 'Errore nella modifica della password!',
|
||||
'Insert your email': 'Inserisci la tua email',
|
||||
error_recover_code: `Ops, c'è stato un problema, hai richiesto più reset password?`,
|
||||
email_notification_activated: 'Notifiche via mail attivate. Per disattivarle puoi usare il link in fondo ad ogni email che ricevi.'
|
||||
|
||||
}
|
||||
|
||||
export default it
|
||||
@@ -1,112 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import BootstrapVue from 'bootstrap-vue'
|
||||
import VCalendar from 'v-calendar'
|
||||
|
||||
import 'vue-awesome/icons/lock'
|
||||
import 'vue-awesome/icons/user'
|
||||
import 'vue-awesome/icons/plus'
|
||||
import 'vue-awesome/icons/cog'
|
||||
import 'vue-awesome/icons/tools'
|
||||
import 'vue-awesome/icons/file-export'
|
||||
import 'vue-awesome/icons/sign-out-alt'
|
||||
import 'vue-awesome/icons/clock'
|
||||
import 'vue-awesome/icons/map-marker-alt'
|
||||
import 'vue-awesome/icons/file-alt'
|
||||
import 'vue-awesome/icons/image'
|
||||
import 'vue-awesome/icons/tag'
|
||||
import 'vue-awesome/icons/users'
|
||||
import 'vue-awesome/icons/calendar'
|
||||
import 'vue-awesome/icons/edit'
|
||||
import 'vue-awesome/icons/envelope-open-text'
|
||||
import 'vue-awesome/icons/user-secret'
|
||||
import 'vue-awesome/icons/question-circle'
|
||||
|
||||
import Icon from 'vue-awesome/components/Icon'
|
||||
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
|
||||
import 'v-calendar/lib/v-calendar.min.css'
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import { Button, Select, Tag, Option, Table, FormItem, Card,
|
||||
Form, Tabs, TabPane, Switch, Input, Loading, TimeSelect,
|
||||
TableColumn, ColorPicker, Pagination, Popover } from 'element-ui'
|
||||
import ElementLocale from 'element-ui/lib/locale'
|
||||
import MagicGrid from 'vue-magic-grid'
|
||||
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
|
||||
import itElementLocale from 'element-ui/lib/locale/lang/it'
|
||||
import enElementLocale from 'element-ui/lib/locale/lang/en'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
import './assets/main.css'
|
||||
|
||||
import itLocale from '@/locale/it'
|
||||
import enLocale from '@/locale/en'
|
||||
|
||||
Vue.use(Button)
|
||||
Vue.use(Popover)
|
||||
Vue.use(Card)
|
||||
Vue.use(Select)
|
||||
Vue.use(Tag)
|
||||
Vue.use(Input)
|
||||
Vue.use(Tabs)
|
||||
Vue.use(TabPane)
|
||||
Vue.use(Option)
|
||||
Vue.use(Switch)
|
||||
Vue.use(ColorPicker)
|
||||
Vue.use(Table)
|
||||
Vue.use(TableColumn)
|
||||
Vue.use(Pagination)
|
||||
Vue.use(FormItem)
|
||||
Vue.use(Form)
|
||||
Vue.use(TimeSelect)
|
||||
Vue.use(Loading.directive)
|
||||
|
||||
Vue.use(MagicGrid)
|
||||
|
||||
// Use v-calendar, v-date-picker & v-popover components
|
||||
Vue.use(VCalendar, {
|
||||
firstDayOfWeek: 2
|
||||
})
|
||||
Vue.use(BootstrapVue)
|
||||
Vue.use(VueI18n)
|
||||
Vue.use(VueClipboard)
|
||||
Vue.component('v-icon', Icon)
|
||||
const messages = {
|
||||
en: {
|
||||
...enElementLocale,
|
||||
...enLocale
|
||||
},
|
||||
it: {
|
||||
...itElementLocale,
|
||||
...itLocale
|
||||
}
|
||||
}
|
||||
|
||||
// Create VueI18n instance with options
|
||||
const i18n = new VueI18n({
|
||||
locale: 'it', // set locale
|
||||
messages // set locale messages
|
||||
})
|
||||
|
||||
// Vue.use(ElementUI, { i18n: (key, value) => i18n.t(key, value) })
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.config.lang = 'it'
|
||||
// Vue.locale('en', enLocale)
|
||||
Vue.config.devtools = true
|
||||
Vue.config.silent = false
|
||||
ElementLocale.i18n((key, value) => i18n.t(key, value))
|
||||
new Vue({
|
||||
i18n,
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
@@ -1,70 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
|
||||
import Settings from './components/Settings'
|
||||
import newEvent from './components/newEvent'
|
||||
import EventDetail from './components/EventDetail'
|
||||
import Login from './components/Login'
|
||||
import Register from './components/Register'
|
||||
import Export from './components/Export'
|
||||
import Admin from './components/Admin'
|
||||
import About from './components/About'
|
||||
import DelNotification from './components/DelNotification'
|
||||
import Recover from './components/Recover'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
routes: [
|
||||
{
|
||||
path: '/admin',
|
||||
components: { modal: Admin }
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
components: { modal: Register }
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
components: { modal: Login }
|
||||
},
|
||||
{
|
||||
path: '/new_event',
|
||||
components: { modal: newEvent }
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
components: { modal: Settings }
|
||||
},
|
||||
{
|
||||
path: '/event/:id',
|
||||
components: { modal: EventDetail }
|
||||
},
|
||||
{
|
||||
path: '/edit/:id',
|
||||
components: { modal: newEvent },
|
||||
props: { edit: true }
|
||||
},
|
||||
{
|
||||
path: '/export',
|
||||
components: { modal: Export }
|
||||
},
|
||||
{
|
||||
path: '/admin/oauth',
|
||||
components: { modal: Admin }
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
components: { modal: About }
|
||||
},
|
||||
{
|
||||
path: '/del_notification/:code',
|
||||
components: { modal: DelNotification }
|
||||
},
|
||||
{
|
||||
path: '/recover/:recover_code',
|
||||
components: { modal: Recover }
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -1,147 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import VuexPersistence from 'vuex-persist'
|
||||
import { intersection } from 'lodash'
|
||||
import api from './api'
|
||||
import moment from 'dayjs'
|
||||
Vue.use(Vuex)
|
||||
|
||||
const vuexLocal = new VuexPersistence({
|
||||
storage: window.localStorage,
|
||||
reducer: state => ({ logged: state.logged, user: state.user, token: state.token })
|
||||
})
|
||||
|
||||
export default new Vuex.Store({
|
||||
plugins: [vuexLocal.plugin],
|
||||
getters: {
|
||||
token: state => state.token,
|
||||
filteredEvents: state => {
|
||||
const events = state.events.map(e => {
|
||||
const end_datetime = e.end_datetime || moment(e.start_datetime).add('3', 'hour')
|
||||
const past = (moment().diff(end_datetime, 'minutes') > 0)
|
||||
e.past = past
|
||||
return e
|
||||
})
|
||||
if (!state.filters.tags.length && !state.filters.places.length) {
|
||||
return events
|
||||
}
|
||||
return events.filter(e => {
|
||||
if (state.filters.tags.length) {
|
||||
const m = intersection(e.tags.map(t => t.tag), state.filters.tags)
|
||||
if (m.length > 0) return true
|
||||
}
|
||||
if (state.filters.places.length) {
|
||||
if (state.filters.places.find(p => p === e.place.name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
},
|
||||
state: {
|
||||
logged: false,
|
||||
user: {},
|
||||
token: '',
|
||||
events: [],
|
||||
tags: [],
|
||||
places: [],
|
||||
//
|
||||
filters: {
|
||||
tags: [],
|
||||
places: []
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
logout (state) {
|
||||
state.logged = false
|
||||
state.token = ''
|
||||
state.user = {}
|
||||
},
|
||||
login (state, user) {
|
||||
state.logged = true
|
||||
state.user = user.user
|
||||
state.token = user.token
|
||||
},
|
||||
setEvents (state, events) {
|
||||
state.events = events
|
||||
},
|
||||
addEvent (state, event) {
|
||||
state.events.push(event)
|
||||
},
|
||||
updateEvent (state, event) {
|
||||
state.events = state.events.map(e => {
|
||||
if (e.id !== event.id) return e
|
||||
return event
|
||||
})
|
||||
},
|
||||
delEvent (state, eventId) {
|
||||
state.events = state.events.filter(ev => ev.id !== eventId)
|
||||
},
|
||||
update (state, { tags, places }) {
|
||||
state.tags = tags
|
||||
state.places = places
|
||||
},
|
||||
// search
|
||||
addSearchTag (state, tag) {
|
||||
if (!state.filters.tags.find(t => t === tag.tag)) {
|
||||
state.filters.tags.push(tag.tag)
|
||||
} else {
|
||||
state.filters.tags = state.filters.tags.filter(t => t !== tag.tag)
|
||||
}
|
||||
},
|
||||
setSearchTags (state, tags) {
|
||||
state.filters.tags = tags
|
||||
},
|
||||
addSearchPlace (state, place) {
|
||||
if (state.filters.places.find(p => p.name === place.name)) {
|
||||
state.filters.places.push(place)
|
||||
}
|
||||
},
|
||||
setSearchPlaces (state, places) {
|
||||
state.filters.places = places
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async updateEvents ({ commit }, date) {
|
||||
const events = await api.getAllEvents(date.month - 1, date.year)
|
||||
commit('setEvents', events)
|
||||
},
|
||||
async updateMeta ({ commit }) {
|
||||
const { tags, places } = await api.getMeta()
|
||||
commit('update', { tags, places })
|
||||
},
|
||||
async addEvent ({ commit }, formData) {
|
||||
const event = await api.addEvent(formData)
|
||||
if (this.state.logged) {
|
||||
commit('addEvent', event)
|
||||
}
|
||||
},
|
||||
async updateEvent ({ commit }, formData) {
|
||||
const event = await api.updateEvent(formData)
|
||||
commit('updateEvent', event)
|
||||
},
|
||||
delEvent ({ commit }, eventId) {
|
||||
commit('delEvent', eventId)
|
||||
},
|
||||
login ({ commit }, user) {
|
||||
commit('login', user)
|
||||
},
|
||||
logout ({ commit }) {
|
||||
commit('logout')
|
||||
},
|
||||
// search
|
||||
addSearchTag ({ commit }, tag) {
|
||||
commit('addSearchTag', tag)
|
||||
},
|
||||
setSearchTags ({ commit }, tags) {
|
||||
commit('setSearchTags', tags)
|
||||
},
|
||||
addSearchPlace ({ commit }, place) {
|
||||
commit('addSearchPlace', place)
|
||||
},
|
||||
setSearchPlaces ({ commit }, places) {
|
||||
commit('setSearchPlaces', places)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,21 +0,0 @@
|
||||
const webpack = require('webpack')
|
||||
|
||||
process.env.VUE_APP_API = process.env.NODE_ENV === 'production' ? process.env.BASE_URL || 'http://localhost:9000' : 'http://localhost:9000'
|
||||
process.env.VUE_APP_TITLE = process.env.TITLE || 'Gancio'
|
||||
process.env.VUE_APP_DESCRIPTION = process.env.DESCRIPTION || 'Event manager for radical movements'
|
||||
|
||||
module.exports = {
|
||||
publicPath: process.env.BASE_URL,
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
new webpack.NormalModuleReplacementPlugin(/element-ui[/\\]lib[/\\]locale[/\\]lang[/\\]zh-CN/, 'element-ui/lib/locale/lang/en')
|
||||
]
|
||||
},
|
||||
devServer: {
|
||||
disableHostCheck: true
|
||||
},
|
||||
transpileDependencies: [
|
||||
/\bvue-awesome\b/,
|
||||
'vuex-persist'
|
||||
]
|
||||
}
|
||||
10896
client/yarn.lock
10896
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: 'postgres:latest'
|
||||
environment:
|
||||
POSTGRES_PASSWORD: docker
|
||||
POSTGRES_USER: docker
|
||||
POSTGRES_DB: gancio
|
||||
volumes:
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
|
||||
app:
|
||||
env_file: .env.production
|
||||
build: .
|
||||
ports:
|
||||
- '12300:12300'
|
||||
volumes:
|
||||
- ./uploads:/usr/src/app/uploads
|
||||
|
||||
links:
|
||||
- db
|
||||
|
||||
596
init.sql
596
init.sql
@@ -1,596 +0,0 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 11.2 (Debian 11.2-1.pgdg90+1)
|
||||
-- Dumped by pg_dump version 11.2 (Debian 11.2-1.pgdg90+1)
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
--
|
||||
-- Name: enum_EventNotifications_status; Type: TYPE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TYPE public."enum_EventNotifications_status" AS ENUM (
|
||||
'new',
|
||||
'sent',
|
||||
'error'
|
||||
);
|
||||
|
||||
|
||||
ALTER TYPE public."enum_EventNotifications_status" OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: enum_notifications_type; Type: TYPE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TYPE public.enum_notifications_type AS ENUM (
|
||||
'mail',
|
||||
'admin_email',
|
||||
'mastodon'
|
||||
);
|
||||
|
||||
|
||||
ALTER TYPE public.enum_notifications_type OWNER TO docker;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- Name: EventNotifications; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public."EventNotifications" (
|
||||
status public."enum_EventNotifications_status" DEFAULT 'new'::public."enum_EventNotifications_status",
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
"eventId" integer NOT NULL,
|
||||
"notificationId" integer NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public."EventNotifications" OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: comments; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public.comments (
|
||||
id integer NOT NULL,
|
||||
activitypub_id integer,
|
||||
author character varying(255),
|
||||
text character varying(255),
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
"eventId" integer
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.comments OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: comments_id_seq; Type: SEQUENCE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.comments_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.comments_id_seq OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.comments_id_seq OWNED BY public.comments.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: events; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public.events (
|
||||
id integer NOT NULL,
|
||||
title character varying(255),
|
||||
description text,
|
||||
multidate boolean,
|
||||
start_datetime timestamp with time zone,
|
||||
end_datetime timestamp with time zone,
|
||||
image_path character varying(255),
|
||||
activitypub_id integer,
|
||||
is_visible boolean,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
"userId" integer,
|
||||
"placeId" integer
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.events OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: events_id_seq; Type: SEQUENCE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.events_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.events_id_seq OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: events_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.events_id_seq OWNED BY public.events.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: notifications; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public.notifications (
|
||||
id integer NOT NULL,
|
||||
filters json,
|
||||
email character varying(255),
|
||||
remove_code character varying(255),
|
||||
type public.enum_notifications_type,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.notifications OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: notifications_id_seq; Type: SEQUENCE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.notifications_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.notifications_id_seq OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: notifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.notifications_id_seq OWNED BY public.notifications.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: places; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public.places (
|
||||
id integer NOT NULL,
|
||||
name character varying(255),
|
||||
address character varying(255),
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.places OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: places_id_seq; Type: SEQUENCE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.places_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.places_id_seq OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: places_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.places_id_seq OWNED BY public.places.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: settings; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public.settings (
|
||||
key character varying(255) NOT NULL,
|
||||
value json,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.settings OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: tagEvent; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public."tagEvent" (
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
"eventId" integer NOT NULL,
|
||||
"tagTag" character varying(255) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public."tagEvent" OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: tags; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public.tags (
|
||||
tag character varying(255) NOT NULL,
|
||||
color character varying(255),
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.tags OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE TABLE public.users (
|
||||
id integer NOT NULL,
|
||||
email character varying(255) NOT NULL,
|
||||
description text,
|
||||
password character varying(255),
|
||||
recover_code character varying(255),
|
||||
is_admin boolean,
|
||||
is_active boolean,
|
||||
mastodon_auth json,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.users OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.users_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.users_id_seq OWNER TO docker;
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: comments id; Type: DEFAULT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.comments ALTER COLUMN id SET DEFAULT nextval('public.comments_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: events id; Type: DEFAULT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.events ALTER COLUMN id SET DEFAULT nextval('public.events_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notifications id; Type: DEFAULT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.notifications ALTER COLUMN id SET DEFAULT nextval('public.notifications_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: places id; Type: DEFAULT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.places ALTER COLUMN id SET DEFAULT nextval('public.places_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users id; Type: DEFAULT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: EventNotifications; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public."EventNotifications" (status, "createdAt", "updatedAt", "eventId", "notificationId") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: comments; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public.comments (id, activitypub_id, author, text, "createdAt", "updatedAt", "eventId") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: events; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public.events (id, title, description, multidate, start_datetime, end_datetime, image_path, activitypub_id, is_visible, "createdAt", "updatedAt", "userId", "placeId") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: notifications; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public.notifications (id, filters, email, remove_code, type, "createdAt", "updatedAt") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: places; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public.places (id, name, address, "createdAt", "updatedAt") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: settings; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public.settings (key, value, "createdAt", "updatedAt") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: tagEvent; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public."tagEvent" ("createdAt", "updatedAt", "eventId", "tagTag") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: tags; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public.tags (tag, color, "createdAt", "updatedAt") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
COPY public.users (id, email, description, password, recover_code, is_admin, is_active, mastodon_auth, "createdAt", "updatedAt") FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Name: comments_id_seq; Type: SEQUENCE SET; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.comments_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: events_id_seq; Type: SEQUENCE SET; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.events_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notifications_id_seq; Type: SEQUENCE SET; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.notifications_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: places_id_seq; Type: SEQUENCE SET; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.places_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.users_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: EventNotifications EventNotifications_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."EventNotifications"
|
||||
ADD CONSTRAINT "EventNotifications_pkey" PRIMARY KEY ("eventId", "notificationId");
|
||||
|
||||
|
||||
--
|
||||
-- Name: comments comments_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.comments
|
||||
ADD CONSTRAINT comments_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: events events_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.events
|
||||
ADD CONSTRAINT events_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notifications notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.notifications
|
||||
ADD CONSTRAINT notifications_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: places places_name_key; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.places
|
||||
ADD CONSTRAINT places_name_key UNIQUE (name);
|
||||
|
||||
|
||||
--
|
||||
-- Name: places places_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.places
|
||||
ADD CONSTRAINT places_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.settings
|
||||
ADD CONSTRAINT settings_pkey PRIMARY KEY (key);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tagEvent tagEvent_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."tagEvent"
|
||||
ADD CONSTRAINT "tagEvent_pkey" PRIMARY KEY ("eventId", "tagTag");
|
||||
|
||||
|
||||
--
|
||||
-- Name: tags tags_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.tags
|
||||
ADD CONSTRAINT tags_pkey PRIMARY KEY (tag);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_email_key UNIQUE (email);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: EventNotifications EventNotifications_eventId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."EventNotifications"
|
||||
ADD CONSTRAINT "EventNotifications_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES public.events(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- Name: EventNotifications EventNotifications_notificationId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."EventNotifications"
|
||||
ADD CONSTRAINT "EventNotifications_notificationId_fkey" FOREIGN KEY ("notificationId") REFERENCES public.notifications(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- Name: comments comments_eventId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.comments
|
||||
ADD CONSTRAINT "comments_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES public.events(id) ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
|
||||
|
||||
--
|
||||
-- Name: events events_placeId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.events
|
||||
ADD CONSTRAINT "events_placeId_fkey" FOREIGN KEY ("placeId") REFERENCES public.places(id) ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
|
||||
|
||||
--
|
||||
-- Name: events events_userId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.events
|
||||
ADD CONSTRAINT "events_userId_fkey" FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tagEvent tagEvent_eventId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."tagEvent"
|
||||
ADD CONSTRAINT "tagEvent_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES public.events(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tagEvent tagEvent_tagTag_fkey; Type: FK CONSTRAINT; Schema: public; Owner: docker
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."tagEvent"
|
||||
ADD CONSTRAINT "tagEvent_tagTag_fkey" FOREIGN KEY ("tagTag") REFERENCES public.tags(tag) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere.",
|
||||
"confirm_email": "confirm_email",
|
||||
"recover_email": "recover_email",
|
||||
"press here": "press here"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"registration_email": "registration_email",
|
||||
"confirm_email": "confirm_email",
|
||||
"recover_email": "recover_email",
|
||||
"press here": "press here"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere.",
|
||||
"confirm_email": "Abbiamo attivato il tuo utente su gancio. Ora puoi aggiungere eventi.",
|
||||
"recover_email": "Qualcuno ha richiesto un reset della password su gancio, se non sei stata tu, ignora pure questa email altrimenti visita questo link:",
|
||||
"press here": "Premi qui"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"registration_email": "registration_email",
|
||||
"confirm_email": "confirm_email",
|
||||
"recover_email": "recover_email",
|
||||
"press here": "press here"
|
||||
}
|
||||
35
package.json
35
package.json
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"name": "gancio",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"serve": "NODE_ENV=production nodemon app/server.js",
|
||||
"dev": "NODE_ENV=development nodemon app/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",
|
||||
"pg": "^7.8.1",
|
||||
"pug": "^2.0.3",
|
||||
"sequelize": "^4.41.0",
|
||||
"sharp": "^0.21.3",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
12
pm2.json
12
pm2.json
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"name": "GANCIO",
|
||||
"script": "./app/server.js"
|
||||
},
|
||||
{
|
||||
"name": "WORKER",
|
||||
"script": "./app/cron.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user