diff --git a/assets/style.less b/assets/style.less index 205b410e..7199a8df 100644 --- a/assets/style.less +++ b/assets/style.less @@ -30,7 +30,7 @@ html, body { } #admin.el-card { - max-width: 850px; + max-width: 870px; } .el-dialog { diff --git a/docs/Gemfile b/docs/Gemfile index 0eee28f3..2fa11740 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -13,7 +13,7 @@ gem "jekyll", "~> 3.8.6" # This is the default theme for new Jekyll sites. You may change this to anything you like. #gem "minima", "~> 2.0" gem "just-the-docs" -gem "jemoji" +gem "mini_magick" # If you want to use GitHub Pages, remove the "gem "jekyll"" above and # uncomment the line below. To upgrade, run `bundle update github-pages`. @@ -22,6 +22,7 @@ gem "jemoji" # If you have any plugins, put them here! group :jekyll_plugins do gem "jekyll-feed", "~> 0.6" + gem "jemoji" end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index e0b1ecac..7341ecbe 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -59,6 +59,7 @@ GEM rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) mercenary (0.3.6) + mini_magick (4.9.5) mini_portile2 (2.4.0) minitest (5.11.3) nokogiri (1.10.3) @@ -93,6 +94,7 @@ DEPENDENCIES jekyll-feed (~> 0.6) jemoji just-the-docs + mini_magick tzinfo (~> 1.2) tzinfo-data wdm (~> 0.1.0) diff --git a/docs/_site/404.html b/docs/_site/404.html index 604d849d..c527cd81 100644 --- a/docs/_site/404.html +++ b/docs/_site/404.html @@ -5,38 +5,27 @@ - - Gancio - - - - -Gancio | A shared agenda for local communities - - - - - - - - - - + + + + + + diff --git a/docs/_site/admin.html b/docs/_site/admin.html index e4261b07..cef5b604 100644 --- a/docs/_site/admin.html +++ b/docs/_site/admin.html @@ -5,38 +5,27 @@ - Admin - Gancio - - - - -Admin | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/config.html b/docs/_site/config.html index eb8cca16..60b677f5 100644 --- a/docs/_site/config.html +++ b/docs/_site/config.html @@ -5,38 +5,27 @@ - Configuration - Gancio - - - - -Configuration | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/contribute.html b/docs/_site/contribute.html index ab4b3e2b..d25e775e 100644 --- a/docs/_site/contribute.html +++ b/docs/_site/contribute.html @@ -5,38 +5,27 @@ - Contribute - Gancio - - - - -Contribute | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/dev.html b/docs/_site/dev.html index 9ea85879..63ca3ae6 100644 --- a/docs/_site/dev.html +++ b/docs/_site/dev.html @@ -5,38 +5,27 @@ - Hacking - Gancio - - - - -Hacking | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/dev/migration.html b/docs/_site/dev/migration.html index 52bb9317..3d10e700 100644 --- a/docs/_site/dev/migration.html +++ b/docs/_site/dev/migration.html @@ -5,38 +5,27 @@ - Migration - Gancio - - - - -Migration | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/dev/structure.html b/docs/_site/dev/structure.html index ed2757d5..a655bd5e 100644 --- a/docs/_site/dev/structure.html +++ b/docs/_site/dev/structure.html @@ -5,38 +5,27 @@ - Project Structure - Gancio - - - - -Project Structure | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/feed.xml b/docs/_site/feed.xml index ba00d9cc..253d1272 100644 --- a/docs/_site/feed.xml +++ b/docs/_site/feed.xml @@ -1 +1 @@ -Jekyll2019-07-27T13:03:35+02:00https://gancio.org/feed.xmlGancioA shared agenda for local communities +Jekyll2019-07-29T21:13:52+02:00https://gancio.org/feed.xmlGancioA shared agenda for local communities \ No newline at end of file diff --git a/docs/_site/index.html b/docs/_site/index.html index b51bde48..a6995405 100644 --- a/docs/_site/index.html +++ b/docs/_site/index.html @@ -5,40 +5,29 @@ - Home - Gancio - - - - -Home | Gancio - - - - - - - - - - + + + + + + @@ -300,7 +289,11 @@

Get started now Demo Source

-

screenshoot

+

diocane +diocane +diocane +diocane +diocane

About the project

diff --git a/docs/_site/install.html b/docs/_site/install.html index 75d34ef1..f8164e2b 100644 --- a/docs/_site/install.html +++ b/docs/_site/install.html @@ -5,38 +5,27 @@ - Install - Gancio - - - - -Install | Gancio - - - - - - - - - - + + + + + + @@ -294,9 +283,9 @@

Install

diff --git a/docs/_site/install/classic.html b/docs/_site/install/classic.html index 7a07393c..ef421ab8 100644 --- a/docs/_site/install/classic.html +++ b/docs/_site/install/classic.html @@ -5,38 +5,27 @@ - Classic - Gancio - - - - -Classic | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/install/docker.html b/docs/_site/install/docker.html index 31092230..bb1e02a5 100644 --- a/docs/_site/install/docker.html +++ b/docs/_site/install/docker.html @@ -5,38 +5,27 @@ - Docker - Gancio - - - - -Docker | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/install/nginx.html b/docs/_site/install/nginx.html index 2a802ffa..d916a233 100644 --- a/docs/_site/install/nginx.html +++ b/docs/_site/install/nginx.html @@ -5,38 +5,27 @@ - Nginx - Gancio - - - - -Nginx | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/instances.html b/docs/_site/instances.html index 62deffb4..d36b90bc 100644 --- a/docs/_site/instances.html +++ b/docs/_site/instances.html @@ -5,38 +5,27 @@ - Instances - Gancio - - - - -Instances | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/_site/usage.html b/docs/_site/usage.html index 1c0dc6d7..83d7498f 100644 --- a/docs/_site/usage.html +++ b/docs/_site/usage.html @@ -5,38 +5,27 @@ - Usage - Gancio - - - - -Usage | Gancio - - - - - - - - - - + + + + + + diff --git a/docs/index.md b/docs/index.md index 5580b68b..edeb7c2d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,7 +15,12 @@ A shared agenda for local communities. [Get started now](install){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [Demo](https://demo.gancio.org){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 } [Source](https://git.lattuga.net/cisti/gancio){: .btn .fs-5 } -![screenshoot](assets/home1.png) + +[![diocane](assets/thumbs/home1.png)](assets/home1.png){: data-fancybox="gallery" data-caption="

Home of first instance of gancio

" } +[![diocane](assets/thumbs/mobile1.png)](assets/mobile1.png){: data-fancybox="gallery" data-caption="

Home of first instance of gancio

" } +[![diocane](assets/thumbs/mobile2.png)](assets/mobile2.png){: data-fancybox="gallery" data-caption="

Home of first instance of gancio

" } +[![diocane](assets/thumbs/admin_users.png)](assets/admin_users.png){: data-fancybox="gallery" data-caption="

Home of first instance of gancio

" } +[![diocane](assets/thumbs/login.png)](assets/login.png){: data-fancybox="gallery" data-caption="

Home of first instance of gancio

" } ### About the project diff --git a/docs/install/install.md b/docs/install/install.md index add14a8a..48d8684d 100644 --- a/docs/install/install.md +++ b/docs/install/install.md @@ -10,7 +10,7 @@ has_toc: false # Install -- [Classic install](install/classic) -- [Using docker](install/docker) -- [Nginx as a proxy](install/nginx) +- [Classic install](/install/classic) +- [Using docker](/install/docker) +- [Nginx as a proxy](/install/nginx) - [Hacking & contribute](../dev) diff --git a/server/api/controller/fediverse.js b/server/api/controller/fediverse.js index 54ef4e68..c94c4816 100644 --- a/server/api/controller/fediverse.js +++ b/server/api/controller/fediverse.js @@ -1,69 +1,68 @@ -const fs = require('fs') -const path = require('path') -const moment = require('moment') -const { event: Event, comment: Comment } = require('../models') -const config = require('config') -const Mastodon = require('mastodon-api') -const settingsController = require('./settings') -const get = require('lodash/get') +// const fs = require('fs') +// const path = require('path') +// const moment = require('moment') +// const { event: Event, comment: Comment } = require('../models') +// const config = require('config') +// const settingsController = require('./settings') +// const get = require('lodash/get') -const botController = { - bots: null, - async initialize() { - const access_token = get(settingsController.secretSettings, 'mastodon_auth.access_token') - const instance = get(settingsController.settings, 'mastodon_instance') - if (!access_token || !instance) return - botController.bot = new Mastodon({ - access_token, - api_url: `https://${instance}/api/v1` - }) - const listener = botController.bot.stream('/streaming/user') - listener.on('message', botController.message) - listener.on('error', botController.error) - }, - async post(event) { - 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}` +// const botController = { +// bots: null, +// async initialize() { +// const access_token = get(settingsController.secretSettings, 'mastodon_auth.access_token') +// const instance = get(settingsController.settings, 'mastodon_instance') +// if (!access_token || !instance) return +// botController.bot = new Mastodon({ +// access_token, +// api_url: `https://${instance}/api/v1` +// }) +// const listener = botController.bot.stream('/streaming/user') +// listener.on('message', botController.message) +// listener.on('error', botController.error) +// }, +// async post(event) { +// 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.resolve(config.upload_path, event.image_path) - if (fs.statSync(file)) { - media = await botController.bot.post('/media', { file: fs.createReadStream(file) }) - } - } - return botController.bot.post('/statuses', { status, media_ids: media ? [media.data.id] : [] }) - }, +// let media +// if (event.image_path) { +// const file = path.resolve(config.upload_path, event.image_path) +// if (fs.statSync(file)) { +// media = await botController.bot.post('/media', { file: fs.createReadStream(file) }) +// } +// } +// return botController.bot.post('/statuses', { status, media_ids: media ? [media.data.id] : [] }) +// }, - async message(msg) { - const type = msg.event +// async message(msg) { +// const type = msg.event - if (type === 'delete') { - const activitypub_id = String(msg.data) - const event = await Comment.findOne({ where: { activitypub_id } }) - if (event) await event.destroy() - return - } +// if (type === 'delete') { +// const activitypub_id = String(msg.data) +// const event = await Comment.findOne({ where: { activitypub_id } }) +// if (event) await event.destroy() +// return +// } - const activitypub_id = String(msg.data.status.in_reply_to_id) - if (!activitypub_id) return - let event = await Event.findOne({ where: { activitypub_id } }) - if (!event) { - // check for comment.. - const comment = await Comment.findOne( { include: [Event], where: { activitypub_id }}) - if (!comment) return - event = comment.event - } - await Comment.create({ - activitypub_id: String(msg.data.status.id), - data: msg.data.status, - eventId: event.id - }) - }, - error(err) { - console.log('error ', err) - } -} +// const activitypub_id = String(msg.data.status.in_reply_to_id) +// if (!activitypub_id) return +// let event = await Event.findOne({ where: { activitypub_id } }) +// if (!event) { +// // check for comment.. +// const comment = await Comment.findOne( { include: [Event], where: { activitypub_id }}) +// if (!comment) return +// event = comment.event +// } +// await Comment.create({ +// activitypub_id: String(msg.data.status.id), +// data: msg.data.status, +// eventId: event.id +// }) +// }, +// error(err) { +// console.log('error ', err) +// } +// } -setTimeout(botController.initialize, 5000) -module.exports = botController +// setTimeout(botController.initialize, 5000) +// module.exports = botController diff --git a/server/api/controller/settings.js b/server/api/controller/settings.js index 377df571..c466125c 100644 --- a/server/api/controller/settings.js +++ b/server/api/controller/settings.js @@ -1,4 +1,3 @@ -const Mastodon = require('mastodon-api') const { setting: Setting } = require('../models') const config = require('config') const consola = require('consola') @@ -78,39 +77,6 @@ const settingsController = { } res.json(settings) }, - - // async getAuthURL(req, res) { - // const instance = req.body.instance - // const callback = `${config.baseurl}/api/settings/oauth` - // const { client_id, client_secret } = await Mastodon.createOAuthApp(`https://${instance}/api/v1/apps`, - // 'gancio', 'read write', callback) - // const url = await Mastodon.getAuthorizationUrl(client_id, client_secret, - // `https://${instance}`, 'read write', callback) - - // await settingsController.set('mastodon_instance', instance ) - // await settingsController.set('mastodon_auth', { client_id, client_secret }, true) - // res.json(url) - // }, - - // async code(req, res) { - // const code = req.query.code - // const callback = `${config.baseurl}/api/settings/oauth` - // const client_id = settingsController.secretSettings.mastodon_auth.client_id - // const client_secret = settingsController.secretSettings.mastodon_auth.client_secret - // const instance = settingsController.settings.mastodon_instance - - // try { - // const access_token = await Mastodon.getAccessToken(client_id, client_secret, code, - // `https://${instance}`, callback) - // const mastodon_auth = { client_id, client_secret, access_token } - // await settingsController.set('mastodon_auth', mastodon_auth, true) - // const botController = require('./fediverse') - // botController.initialize() - // res.redirect('/admin') - // } catch (e) { - // res.json(e) - // } - // }, } setTimeout(settingsController.initialize, 200) diff --git a/server/api/models/index.js b/server/api/models/index.js index a2624f25..4a02d06d 100644 --- a/server/api/models/index.js +++ b/server/api/models/index.js @@ -21,14 +21,15 @@ fs 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 + + Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db) + } + }) + + db.sequelize = sequelize + db.Sequelize = Sequelize + + module.exports = db + \ No newline at end of file diff --git a/server/api/models/user.js b/server/api/models/user.js index 5a050140..078081b0 100644 --- a/server/api/models/user.js +++ b/server/api/models/user.js @@ -25,7 +25,11 @@ module.exports = (sequelize, DataTypes) => { recover_code: DataTypes.STRING, is_admin: DataTypes.BOOLEAN, is_active: DataTypes.BOOLEAN, - rsa: DataTypes.JSONB + rsa: DataTypes.JSON, + followers: { + type: DataTypes.JSON, + defaultValue: [] + } }, { scopes: { withoutPassword: { diff --git a/server/cli.js b/server/cli.js index 451a52bd..6fd7d7a0 100755 --- a/server/cli.js +++ b/server/cli.js @@ -174,7 +174,7 @@ async function setup (options) { await firstrun.setup(config, options.config) consola.info(`You can edit '${options.config}' to modify your configuration. `) consola.info(`Run "gancio --config ${options.config}"`) - process.exit(0) + process.exit(0) } async function upgrade (options) { diff --git a/server/dbconfig.js b/server/dbconfig.js new file mode 100644 index 00000000..bb477ea6 --- /dev/null +++ b/server/dbconfig.js @@ -0,0 +1,3 @@ +const config = require('config') + +modules.exports = config.db diff --git a/server/federation/index.js b/server/federation/index.js index f4225190..d03002e6 100644 --- a/server/federation/index.js +++ b/server/federation/index.js @@ -2,6 +2,84 @@ const express = require('express') const router = express.Router() const { user: User } = require('../api/models') const config = require('config') +const get = require('lodash/get') +const crypto = require('crypto') +const request = require('request') + +function signAndSend(message, usre, domain, req, res, targetDomain) { + // get the URI of the actor object and append 'inbox' to it + let inbox = message.object.actor+'/inbox' + let inboxFragment = inbox.replace('https://'+targetDomain,'') + // get the private key + const privkey = user.rsa.privateKey + const signer = crypto.createSign('sha256') + let d = new Date() + let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}` + signer.update(stringToSign) + signer.end() + const signature = signer.sign(privkey) + const signature_b64 = signature.toString('base64') + let header = `keyId="https://${domain}/u/${name}",headers="(request-target) host date",signature="${signature_b64}"` + console.error('vado di request accept !') + request({ + url: inbox, + headers: { + 'Host': targetDomain, + 'Date': d.toUTCString(), + 'Signature': header + }, + method: 'POST', + json: true, + body: message + }, function (error, response){ + if (error) { + console.log('Error:', error, response.body) + } + else { + console.log('Response:', response.body) + } + }) + return res.status(200); +} + +function sendAcceptMessage (body, user, req, res, targetDomain) { + const guid = crypto.randomBytes(16).toString('hex') + let message = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `${config.baseurl}/federation/${guid}`, + 'type': 'Accept', + 'actor': `${config.baseurl}/federation/u/${user.username}`, + 'object': body, + } + signAndSend(message, user, domain, req, res, targetDomain) +} + +router.post('/inbox', async (req, res) => { + const b = req.body + console.error('> INBOX ', b) + const targetDomain = new URL(b.actor).host + const domain = new URL(config.baseurl).host + switch(b.type) { + case 'Follow': + if (typeof b.object !== 'string') return + const username = b.object.replace(`${config.baseurl}/federation/u/`, '') + console.error('someone wants to follow ' + username) + const user = await User.findOne({ where: { username }}) + if (!user) { + console.error('No user found!') + return + } + sendAcceptMessage(b, user, domain, req, res, targetDomain) + console.error('FOLLOWERS ', user.followers) + if (user.followers.indexOf(b.actor) === -1) { + console.error('ok this is a new follower: ', b.actor) + user.followers.push(b.actor) + await user.save() + } + + break + } +}) router.get('/u/:name', async (req, res) => { const name = req.params.name @@ -13,17 +91,40 @@ router.get('/u/:name', async (req, res) => { 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1' ], - 'id': `${config.baseurl}/federation/u/${name}`, - 'type': 'Person', - 'preferredUsername': name, - 'inbox': `${config.baseurl}/federation/inbox`, - 'followers': `${config.baseurl}/federation/u/${name}/followers`, - 'publicKey': { - 'id': `${config.baseurl}/federation/u/${name}#main-key`, - 'owner': `${config.baseurl}/federation/u/${name}`, - 'publicKeyPem': user.rsa.publicKey + id: `${config.baseurl}/federation/u/${name}`, + type: 'Person', + preferredUsername: name, + inbox: `${config.baseurl}/federation/inbox`, + followers: `${config.baseurl}/federation/u/${name}/followers`, + publicKey: { + id: `${config.baseurl}/federation/u/${name}#main-key`, + owner: `${config.baseurl}/federation/u/${name}`, + publicKeyPem: get(user, 'rsa.publicKey', '') } } res.json(ret) }) -module.exports = router + +router.get('/u/:name/followers', async (req, res) => { + const name = req.params.name + if (!name) return res.status(400).send('Bad request.') + const user = await User.findOne({where: { username: name }}) + if (!user) return res.status(404).send(`No record found for ${name}`) + const ret = { + '@context': [ 'https://www.w3.org/ns/activitystreams' ], + id: `${config.baseurl}/federation/u/${name}/followers`, + type: 'OrderedCollection', + totalItems: user.followers.length, + first: { + id: `${config.baseurl}/federation/u/${name}/followers?page=1`, + type: 'OrderedCollectionPage', + totalItems: user.followers.length, + partOf: `${config.baseurl}/federation/u/${name}/followers`, + orderedItems: user.followers, + } + } + res.json(ret) +}) + + +module.exports = router \ No newline at end of file diff --git a/server/federation/webfinger.js b/server/federation/webfinger.js index 53760358..6bbf3bc5 100644 --- a/server/federation/webfinger.js +++ b/server/federation/webfinger.js @@ -4,6 +4,7 @@ const { user: User } = require('../api/models') const config = require('config') router.get('/', async (req, res) => { + console.error('ma sono dentro webfinger ?!?!') const resource = req.query.resource if (!resource || !resource.includes('acct:')) { return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.') diff --git a/server/firstrun.js b/server/firstrun.js index e09e87ba..9b16a9d8 100644 --- a/server/firstrun.js +++ b/server/firstrun.js @@ -26,8 +26,7 @@ module.exports = { try { await db.user.findAll() - consola.warn(`⚠️ Non empty db! Please move your current db elsewhere than retry. -If you want to `) + consola.warn(`⚠️ Non empty db! Please move your current db elsewhere than retry.`) return -1 } catch(e) { } @@ -40,7 +39,10 @@ If you want to `) // create admin user consola.info('Create admin user', admin) await db.user.create({ - ...admin, + email: admin.email, + password: admin.password, + username: admin.email, + display_name: config.title, is_admin: true, is_active: true }) diff --git a/server/migrations/20190729103119-add_rsa.js b/server/migrations/20190729103119-add_rsa.js index 97bdbac3..f01a81ca 100644 --- a/server/migrations/20190729103119-add_rsa.js +++ b/server/migrations/20190729103119-add_rsa.js @@ -3,7 +3,7 @@ module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.addColumn('users', 'rsa', { - type: Sequelize.JSONB + type: Sequelize.JSON }) /* Add altering commands here. diff --git a/server/migrations/20190729192753-add_followers.js b/server/migrations/20190729192753-add_followers.js new file mode 100644 index 00000000..9621bd26 --- /dev/null +++ b/server/migrations/20190729192753-add_followers.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn('users', 'followers', { + type: Sequelize.JSON + }) + /* + Add altering commands here. + Return a promise to correctly handle asynchronicity. + + Example: + return queryInterface.createTable('users', { id: Sequelize.INTEGER }); + */ + }, + + down: (queryInterface, Sequelize) => { + /* + Add reverting commands here. + Return a promise to correctly handle asynchronicity. + + Example: + return queryInterface.dropTable('users'); + */ + } +};