diff --git a/.gitignore b/.gitignore index dbd17a1..83cc904 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -node_modules/ +example_user/ +server/node_modules/ test/ diff --git a/README.md b/README.md index f856264..5620e86 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,7 @@ __Administrations can be a lot more than filters:__ An administration can personalize almost everything in how content is viewed, what content is allowed (acting a filter), whitelists, blacklists, eventually even the CSS or Layout of the front page, post tags and a lot more. -`admin` is the administrator of the repo. He decides the rules. He can name moderators and their modifications -will be merged with his boards' profile by his Admin Node. +`admin` is the administrator of the repo. He decides the rules. You (the user) will be able to choose a main _administration_ for a board and then also include content from other administrations. @@ -93,7 +92,7 @@ If this gets implemented it should go in the Admin Node. __About private boards__ They probably will be possible but are not included for now, because hidden content is far away in the IPFS roadmap. -Administration can forbit people to write, but not to read. +Administrations can forbit people to write, but not to read. __Important Note:__ due to how the system works, an _administration_'s rules and decisions are just _guidelines_ for your computer. Your computer will always be able to choose what to see and what to hide, it just uses your administration's guidelines _by default_. @@ -104,12 +103,14 @@ That's why there are __Cache Servers__. They monitor _administrations_ and cache all the content (or some of it) as soon as it becomes available on the network, making sure it never gets lost. They are completely optional but they help out in serving the users. +Cache servers also act as gateways and provide an HTTP API to access the boards. + Also note that due to how IPFS works, the more popular some content gets, the _faster it downloads_ and the _easier it is for your computer to find it_. Censorship is impractical in such a system and data is almost impossible to take down. That's why IPFS is also called _the permanent web_. ## Faq -#### Can I be anonymous? +#### Can I be anonymous? What if someone monitors my IPFS node? Will they know what content I'm seeing and everything I post and link it to my IP? @@ -117,7 +118,7 @@ __Yes, but there is a solution.__ You can access a Cache Server's HTTP(s) gateway via Tor for read only access to content. -To post while mantaining anonimity, you would need to run IPFS via Tor. This is probably not easily done at the moment but [it is planned]( +To post while mantaining anonimity, you would need to run IPFS via Tor. This is probably not easily done at the moment but it is planned. ## Components @@ -141,25 +142,30 @@ Each user exposes via IPNS a folder containing: - boards - _board name(s)_ + - settings.json - the board's settings + - whitelist - contains links to all whitelisted users + - blacklist - contains links to all blacklisted users + - approved - contains links to all approved content - posts - _board name(s)_ - - _admin name(s)_ - - _post(s)_ + - _post(s)_ - comments - _board name(s)_ - - _admin name(s)_ - - _comment(s)_ + - _comment(s)_ - votes - _board name(s)_ - - _admin name(s)_ - - _vote(s)_ -- compatibility: could be used to store compatibility information + - _vote(s)_ +- name - stores the user's screen name (also stored in profile?) +- profile.json - user's additional profile data +- ipfs-boards-version.txt - used to store compatibility information #### Post { "title": "Title of the post", "date": "date of the post", + "op": "id_of_the_original_poster", + "preference": "id_of_the_preferred_administration", "text": "Content of the post" } @@ -168,16 +174,21 @@ possible for lange texts without duplicating data. #### Comment - Comment text + { + "parent": "id_of_the_parent_object", + "date": "date of the comment" + "preference": "id_of_the_preferred_administration", + "op": "id_of_the_original_poster", + "text": "Content of the post" + } #### Vote - ipfs-board:vote-for:object_url + ipfs:boards:vote-for:object_url -### Versioning +#### Versioning -a `version` file or something should be included in the user's files to ensure compatibility between different -versions or forks. + just the version ID written in the version file ### License diff --git a/lib/boards-api.js b/lib/boards-api.js new file mode 100644 index 0000000..73d0b59 --- /dev/null +++ b/lib/boards-api.js @@ -0,0 +1,138 @@ +// Write an API to aggregate data without duplication and making accessing content easy. Use the IPFS http api + +function asObj(str,done){ + var obj + try { + obj = JSON.parse(str) + } catch (e) { + done(e,null) + } + if(obj != undefined) done(null,obj) +} + +function replyAsObj(res,isJson,done){ + if(res.readable){ + // Is a stream + res.setEncoding('utf8') + var data = '' + res.on('data',d => { + data += d + }) + res.on('end',() => { + if(isJson) { + asObj(data,done) + } else { + done(null,data) + } + }) + } else { + // Is a string + if(isJson){ + asObj(res,done) + } else { + done(null,res) + } + } +} + +function BoardsAPI(ipfs){ + this.ipfs = ipfs + this.version = 'dev' +} + +BoardsAPI.prototype.searchUsers = function(done){ + // Look at our peers + this.ipfs.swarm.peers(function(err,r){ + var peers = r.Strings.forEach(function(s){ + var ss = s.split('/') + var addr = ss[ss.length-1] + // Try to see if they run IPFS Boards + this.ipfs.cat(addr+'/ipfs-boards-version.txt',function(err,r){ + if(err) return console.log('Search Err:',err) + replyAsObj(r,false,(_,res) => { + // He does! + // TODO: store found users in a list? + console.log('Found user:',addr,'using version',res) + }) + }) + }) + }) +} + +BoardsAPI.prototype.getProfile = function(userID,done){ + this.ipfs.cat(userID+'/profile.json',(err,res) => { + if(err){ + done(err,null) + } else { + replyAsObj(res,true,done) + } + }) +} + +BoardsAPI.prototype.getName = function(userID,done){ + this.ipfs.cat(userID+'/name',(err,res) => { + if(err){ + done(err,null) + } else { + replyAsObj(res,false,done) + } + }) +} + +BoardsAPI.prototype.getBoardSettings = function(userID,board,done){ + var url = userID+'/boards/'+board+'/settings.json' + this.ipfs.cat(url,function(err,res){ + if(err){ + done(err,{}) + } else { + replyAsObj(res,true,done) + } + }) +} + +BoardsAPI.prototype.getBoardPosts(board,administratorID,done){ + // Returns a stream +} + +BoardsAPI.prototype.getUserPosts(user,board,done){ + // Returns a stream +} + +BoardsAPI.prototype.getComments(parent,board,done){ + // Returns a stream +} + +BoardsAPI.prototype.createPost(post,board,done){ + +} + +BoardsAPI.prototype.createComment(parent,comment,done){ + +} + +BoardsAPI.prototype.createUpvote(parent,done){ + +} + +// API for managing the administrations to be done later + +// Initialize API +BoardsAPI.prototype.init = function(done){ + this.ipfs.id( (err, res) => { + if(err){ + console.log(err) + done(err) + } else { + console.log('I am',res.ID) + this.id = res.ID + console.log('Version is',this.version) + this.ipfs.add(new Buffer('ipfs:boards:version:'+this.version),(err,r) => { + this.version_hash = r[0].Hash + console.log('Version hash is',this.version_hash) + done(null) + }) + } + }) +} + +module.exports = BoardsAPI diff --git a/package.json b/package.json index dcb3f70..a80a42e 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,24 @@ "name": "ipfs-board", "version": "0.1.0", "description": "decentralized discussion board", - "main": "server.js", "scripts": { - "start": "node server.js" + "start": "node cli/server.js" }, + "bin": { + "ipfs-board": "cli/server.js" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/fazo96/ipfs-board.git" + }, + "bugs": { + "url": "https://github.com/fazo96/ipfs-board/issues" + }, + "homepage": "https://github.com/fazo96/ipfs-board#readme", "author": "Enrico Fasoli (fazo96)", "license": "MIT", "dependencies": { + "commander": "^2.9.0", "express": "^4.13.3", "ipfs-api": "^2.6.2" } diff --git a/server.js b/server.js deleted file mode 100755 index bb21e7f..0000000 --- a/server.js +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env node - -var ipfs = require('ipfs-api')('localhost','5001') -var express = require('express') -var app = express() - -// Serve files in ./static -app.use(express.static('static')) - -app.get('/@:user/:board',function(req,res){ - ipfs.cat(req.params.user+'/'+req.params.board,function(err,res){ - - }) -}) - -// CatchAll route: serve the angular app -/*app.get('*',function(req,res){ - res.sendFile(__dirname+'/static/index.html') -})*/ - -// Start http server -app.listen(3000,function(){ - console.log('Started') -}) diff --git a/server/server.js b/server/server.js new file mode 100755 index 0000000..0e95bea --- /dev/null +++ b/server/server.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +var ipfs = require('ipfs-api')('localhost','5001') +var BoardsAPI = require('../lib/boards-api.js') +var express = require('express') +var app = express() + +var boards = new BoardsAPI(ipfs) + +// Serve web app +app.use(express.static('../webapp')) + +// Create gateways to access the BoardsAPI + +function startWebServer(){ + // Start http server + app.listen(3000,function(){ + console.log('Started Web Server') + }) +} + +boards.init(function(err){ + if(err){ + console.log(err) + } else { + startWebServer() + } +}) diff --git a/static/app.js b/webapp/app.js similarity index 94% rename from static/app.js rename to webapp/app.js index fb7d2cc..f7d9816 100644 --- a/static/app.js +++ b/webapp/app.js @@ -1,5 +1,3 @@ -console.log(require('ipfs-api')) - var boards = angular.module('boards',['ui.router']) boards.config(function($stateProvider,$urlRouterProvider,$locationProvider){ diff --git a/static/board.html b/webapp/board.html similarity index 100% rename from static/board.html rename to webapp/board.html diff --git a/static/home.html b/webapp/home.html similarity index 100% rename from static/home.html rename to webapp/home.html diff --git a/static/index.html b/webapp/index.html similarity index 100% rename from static/index.html rename to webapp/index.html