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