Browse Source

OrbitDB init

develop
Ezerous 6 years ago
parent
commit
779ab9453b
  1. 2
      app/package.json
  2. 13
      app/src/config/ipfsOptions.js
  3. 8
      app/src/containers/SignUpContainer.js
  4. 5
      app/src/index.js
  5. 55
      app/src/orbit.js
  6. 49
      app/src/redux/reducers/orbitReducer.js
  7. 2
      app/src/redux/reducers/rootReducer.js
  8. 37
      app/src/redux/sagas/drizzleUtilsSaga.js
  9. 41
      app/src/redux/sagas/orbitSaga.js
  10. 4
      app/src/redux/sagas/rootSaga.js
  11. 32
      app/src/redux/sagas/userSaga.js
  12. 0
      app/src/router/PrivateRoute.js
  13. 10
      app/src/router/routes.js
  14. 146
      contracts/Forum.sol

2
app/package.json

@ -13,6 +13,8 @@
"connected-react-router": "^6.3.1", "connected-react-router": "^6.3.1",
"drizzle": "^1.3.3", "drizzle": "^1.3.3",
"history": "^4.7.2", "history": "^4.7.2",
"ipfs": "^0.34.4",
"orbit-db": "^0.19.9",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.8.1", "react": "^16.8.1",
"react-dom": "^16.8.1", "react-dom": "^16.8.1",

13
app/src/config/ipfsOptions.js

@ -0,0 +1,13 @@
// OrbitDB uses Pubsub which is an experimental feature and need to be turned on manually.
const ipfsOptions = {
EXPERIMENTAL: {
pubsub: true
}, config: {
Addresses: {
Swarm: [
]
}
},
};
export default ipfsOptions;

8
app/src/containers/SignUpContainer.js

@ -1,8 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Header } from 'semantic-ui-react'; import { Header } from 'semantic-ui-react';
import {bindActionCreators} from "redux";
import {push} from "connected-react-router";
import {connect} from "react-redux"; import {connect} from "react-redux";
class SignUp extends Component { class SignUp extends Component {
@ -29,10 +27,6 @@ class SignUp extends Component {
); );
} }
} }
const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: () => push()
}, dispatch);
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
@ -40,7 +34,7 @@ const mapStateToProps = state => {
} }
}; };
const SignUpContainer = connect(mapStateToProps, mapDispatchToProps)(SignUp); const SignUpContainer = connect(mapStateToProps)(SignUp);
export default SignUpContainer; export default SignUpContainer;

5
app/src/index.js

@ -4,11 +4,14 @@ import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router' import { ConnectedRouter } from 'connected-react-router'
import store, {history} from './redux/store'; import store, {history} from './redux/store';
import routes from './routes' import routes from './router/routes'
import { initIPFS } from './orbit'
import * as serviceWorker from './utils/serviceWorker'; import * as serviceWorker from './utils/serviceWorker';
import './assets/css/index.css'; import './assets/css/index.css';
initIPFS();
render( render(
<Provider store={store}> <Provider store={store}>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>

55
app/src/orbit.js

@ -0,0 +1,55 @@
import IPFS from 'ipfs';
import OrbitDB from 'orbit-db';
import Keystore from 'orbit-db-keystore';
import path from 'path';
import store from './redux/store';
import ipfsOptions from './config/ipfsOptions'
let ipfs, orbitdb, topicsDB, postsDB;
function initIPFS(){
ipfs = new IPFS(ipfsOptions);
ipfs.on('ready', async () => {
store.dispatch({type: "IPFS_INITIALIZED"});
});
}
async function createDatabases() {
orbitdb = new OrbitDB(ipfs);
topicsDB = await orbitdb.keyvalue('topics');
postsDB = await orbitdb.keyvalue('posts');
store.dispatch({
type: "DATABASES_CREATED",
orbitdb: orbitdb,
topicsDB: topicsDB,
postsDB: postsDB,
id: orbitdb.id
});
return {id: orbitdb.id, topicsDB: topicsDB.address.root, postsDB: postsDB.address.root,
publicKey: orbitdb.key.getPublic('hex'), privateKey:orbitdb.key.getPrivate('hex')};
}
async function loadDatabases(id,mTopicsDB, mPostsDB,publicKey,privateKey) {
let directory = "./orbitdb";
let keystore = Keystore.create(path.join(directory, id, '/keystore'));
keystore._storage.setItem(id, JSON.stringify({
publicKey: publicKey,
privateKey: privateKey
}));
orbitdb = new OrbitDB(ipfs,directory,{peerId:id, keystore:keystore});
topicsDB = await orbitdb.keyvalue('/orbitdb/' + mTopicsDB +'/topics');
postsDB = await orbitdb.keyvalue('/orbitdb/' + mPostsDB +'/posts');
topicsDB.load();
postsDB.load();
store.dispatch({
type: "DATABASES_LOADED",
orbitdb: orbitdb,
topicsDB: topicsDB,
postsDB: postsDB,
id: orbitdb.id
});
}
export { initIPFS, createDatabases, loadDatabases };

49
app/src/redux/reducers/orbitReducer.js

@ -0,0 +1,49 @@
const initialState = {
ipfsInitialized: false,
ready: false,
orbitdb: null,
topicsDB: null,
postsDB: null,
id: null
};
const orbitReducer = (state = initialState, action) => {
switch (action.type) {
case 'IPFS_INITIALIZED':
return {
...state,
ipfsInitialized: true
};
case 'DATABASES_CREATED':
return {
...state,
ready: true,
orbitdb: action.orbitdb,
topicsDB: action.topicsDB,
postsDB: action.postsDB,
id: action.id
};
case 'DATABASES_LOADED':
return {
...state,
ready: true,
orbitdb: action.orbitdb,
topicsDB: action.topicsDB,
postsDB: action.postsDB,
id: action.id
};
case 'DATABASES_NOT_READY':
return {
...state,
ready: false,
orbitdb: null,
topicsDB: null,
postsDB: null,
id: null
};
default:
return state
}
};
export default orbitReducer;

2
app/src/redux/reducers/rootReducer.js

@ -2,9 +2,11 @@ import { combineReducers } from 'redux';
import { drizzleReducers } from 'drizzle'; import { drizzleReducers } from 'drizzle';
import { connectRouter } from 'connected-react-router' import { connectRouter } from 'connected-react-router'
import userReducer from './userReducer'; import userReducer from './userReducer';
import orbitReducer from "./orbitReducer";
export default (history) => combineReducers({ export default (history) => combineReducers({
router: connectRouter(history), router: connectRouter(history),
user: userReducer, user: userReducer,
orbit: orbitReducer,
...drizzleReducers ...drizzleReducers
}) })

37
app/src/redux/sagas/drizzleUtilsSaga.js

@ -0,0 +1,37 @@
import getWeb3 from '@drizzle-utils/get-web3';
import getContractInstance from '@drizzle-utils/get-contract-instance';
import { call, put, takeLatest, select } from 'redux-saga/effects'
import Forum from '../../contracts/Forum';
const accounts = (state) => state.accounts;
let initFlag, web3, contract;
function* init() {
if(!initFlag) {
web3 = yield call(getWeb3);
contract = yield call(getContractInstance,{
web3,
artifact: Forum
});
initFlag=true;
yield put({type: 'DRIZZLE_UTILS_SAGA_INITIALIZED', ...[]});
}
else
console.warn("Attempted to reinitialize drizzleUtilsSaga!");
}
// If the method below proves to be problematic/ineffective (i.e. getting current account
// from state), consider getting it from @drizzle-utils/get-accounts instead
// with (yield call(getAccounts, {web3}))[0];
function* getCurrentAccount(){
return (yield select(accounts))[0];
}
function* drizzleUtilsSaga() {
yield takeLatest("DRIZZLE_INITIALIZED", init);
}
export { web3, contract, getCurrentAccount }
export default drizzleUtilsSaga;

41
app/src/redux/sagas/orbitSaga.js

@ -0,0 +1,41 @@
import { call, put, take, takeLatest } from 'redux-saga/effects'
import { contract, getCurrentAccount} from './drizzleUtilsSaga';
import { loadDatabases } from '../../orbit'
let latestAccount;
function* getOrbitDBInfo() {
yield put({type: 'ORRBIT_GETTING_INFO', ...[]});
const account = yield call(getCurrentAccount);
if(account!==latestAccount) {
console.log("Deleting local storage..");
localStorage.clear();
const txObj1 = yield call(contract.methods["hasUserSignedUp"], ...[account]);
try {
const callResult = yield call(txObj1.call, {address:account});
if(callResult) {
const txObj2 = yield call(contract.methods["getOrbitDBInfo"], ...[account]);
const info = yield call(txObj2.call, {address: account});
//TODO: update localStorage OrbitDB stuff
yield call(loadDatabases, info[0], info[1], info[2],info[3], info[4]);
}
else
yield put({type: 'DATABASES_NOT_READY', ...[]});
latestAccount=account;
}
catch (error) {
console.error(error);
yield put({type: 'ORBIT_SAGA_ERROR', ...[]});
}
}
}
function* orbitSaga() {
yield take("DRIZZLE_UTILS_SAGA_INITIALIZED");
yield take('IPFS_INITIALIZED');
yield takeLatest("ACCOUNT_CHANGED", getOrbitDBInfo);
}
export default orbitSaga;

4
app/src/redux/sagas/rootSaga.js

@ -1,9 +1,11 @@
import { all, fork } from 'redux-saga/effects' import { all, fork } from 'redux-saga/effects'
import { drizzleSagas } from 'drizzle' import { drizzleSagas } from 'drizzle'
import drizzleUtilsSaga from './drizzleUtilsSaga'
import userSaga from './userSaga'; import userSaga from './userSaga';
import orbitSaga from "./orbitSaga";
export default function* root() { export default function* root() {
let sagas = [...drizzleSagas, userSaga]; let sagas = [...drizzleSagas, drizzleUtilsSaga, orbitSaga, userSaga];
yield all( yield all(
sagas.map(saga => fork(saga)) sagas.map(saga => fork(saga))
) )

32
app/src/redux/sagas/userSaga.js

@ -1,29 +1,11 @@
import getWeb3 from "@drizzle-utils/get-web3"; import { call, put, take, takeEvery } from 'redux-saga/effects'
import getContractInstance from "@drizzle-utils/get-contract-instance";
import getAccounts from "@drizzle-utils/get-accounts";
import { call, put, take, takeLatest, takeEvery } from 'redux-saga/effects'
import Forum from "../../contracts/Forum.json"; import { contract, getCurrentAccount } from './drizzleUtilsSaga';
let initFlag, web3, contract, account; let account;
function* initUser() {
if(!initFlag) {
web3 = yield call(getWeb3);
contract = yield call(getContractInstance,{
web3,
artifact: Forum
});
initFlag=true;
yield put({type: 'USER_SAGA_INITIALIZED', ...[]});
}
else
console.warn("Attempted to reinitialize userSaga!");
}
function* updateUserData() { function* updateUserData() {
if(initFlag){ const currentAccount = yield call(getCurrentAccount);
const currentAccount = (yield call(getAccounts, {web3}))[0];
if(currentAccount!==account) { if(currentAccount!==account) {
account = currentAccount; account = currentAccount;
yield put({type: 'ACCOUNT_CHANGED', ...[]}); yield put({type: 'ACCOUNT_CHANGED', ...[]});
@ -51,15 +33,11 @@ function* updateUserData() {
console.error(error); console.error(error);
yield put({type: 'USER_FETCHING_ERROR', ...[]}) yield put({type: 'USER_FETCHING_ERROR', ...[]})
} }
}
else
console.warn("Attempted to fetch data without initializing!");
} }
function* userSaga() { function* userSaga() {
yield takeLatest("DRIZZLE_INITIALIZED", initUser); yield take("DRIZZLE_UTILS_SAGA_INITIALIZED");
yield take("USER_SAGA_INITIALIZED");
yield takeEvery("ACCOUNTS_FETCHED", updateUserData); yield takeEvery("ACCOUNTS_FETCHED", updateUserData);
} }

0
app/src/PrivateRoute.js → app/src/router/PrivateRoute.js

10
app/src/routes.js → app/src/router/routes.js

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom' import { Route, Switch } from 'react-router-dom'
import PrivateRoute from './PrivateRoute.js'; import PrivateRoute from './PrivateRoute.js';
import NavBarContainer from './containers/NavBarContainer'; import NavBarContainer from '../containers/NavBarContainer';
import HomeContainer from './containers/HomeContainer' import HomeContainer from '../containers/HomeContainer'
import SignUpContainer from './containers/SignUpContainer' import SignUpContainer from '../containers/SignUpContainer'
import NotFound from './components/NotFound' import NotFound from '../components/NotFound'
const routes = ( const routes = (

146
contracts/Forum.sol

@ -4,7 +4,8 @@ contract Forum {
//----------------------------------------USER---------------------------------------- //----------------------------------------USER----------------------------------------
struct User { struct User {
string username; string username; // TODO: set an upper bound instead of arbitrary string
OrbitDB orbitdb;
uint[] topicIDs; // IDs of the topics the user created uint[] topicIDs; // IDs of the topics the user created
uint[] postIDs; // IDs of the posts the user created uint[] postIDs; // IDs of the posts the user created
uint timestamp; uint timestamp;
@ -17,10 +18,11 @@ contract Forum {
event UserSignedUp(string username, address userAddress); event UserSignedUp(string username, address userAddress);
event UsernameUpdated(string newName, string oldName,address userAddress); event UsernameUpdated(string newName, string oldName,address userAddress);
function signUp(string memory username) public returns (bool) { function signUp(string memory username, string memory orbitDBId, string memory orbitTopicsDB, string memory orbitPostsDB, string memory orbitPublicKey, string memory orbitPrivateKey) public returns (bool) {
require (!hasUserSignedUp(msg.sender), "User has already signed up."); require (!hasUserSignedUp(msg.sender), "User has already signed up.");
require(!isUserNameTaken(username), "Username is already taken."); require(!isUserNameTaken(username), "Username is already taken.");
users[msg.sender] = User(username, users[msg.sender] = User(username,
OrbitDB(orbitDBId,orbitTopicsDB, orbitPostsDB, orbitPublicKey, orbitPrivateKey),
new uint[](0), new uint[](0), block.timestamp, true); new uint[](0), new uint[](0), block.timestamp, true);
userAddresses[username] = msg.sender; userAddresses[username] = msg.sender;
emit UserSignedUp(username, msg.sender); emit UserSignedUp(username, msg.sender);
@ -57,9 +59,149 @@ contract Forum {
return false; return false;
} }
function getUserTopics(address userAddress) public view returns (uint[] memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].topicIDs;
}
function getUserPosts(address userAddress) public view returns (uint[] memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].postIDs;
}
function getUserDateOfRegister(address userAddress) public view returns (uint) { function getUserDateOfRegister(address userAddress) public view returns (uint) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet."); require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].timestamp; return users[userAddress].timestamp;
} }
//----------------------------------------OrbitDB----------------------------------------
struct OrbitDB {
string id; // TODO: set an upper bound instead of arbitrary string
string topicsDB; //TODO: not sure yet which of these are actually needed
string postsDB;
string publicKey;
string privateKey;
}
function getOrbitDBId(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.id;
}
function getOrbitTopicsDB(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.topicsDB;
}
function getOrbitPostsDB(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.postsDB;
}
function getOrbitPublicKey(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.publicKey;
}
//TODO: encrypt using Metamask in the future
function getOrbitPrivateKey(address userAddress) public view returns (string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return users[userAddress].orbitdb.privateKey;
}
function getOrbitDBInfo(address userAddress) public view returns (string memory, string memory, string memory, string memory, string memory) {
require (hasUserSignedUp(userAddress), "User hasn't signed up.");
return (
users[userAddress].orbitdb.id,
users[userAddress].orbitdb.topicsDB,
users[userAddress].orbitdb.postsDB,
users[userAddress].orbitdb.publicKey,
users[userAddress].orbitdb.privateKey
);
}
//----------------------------------------POSTING----------------------------------------
struct Topic {
uint topicID;
address author;
uint timestamp;
uint[] postIDs;
}
struct Post {
uint postID;
address author;
uint timestamp;
uint topicID;
}
uint numTopics; // Total number of topics
uint numPosts; // Total number of posts
mapping (uint => Topic) topics;
mapping (uint => Post) posts;
event TopicCreated(uint topicID, uint postID);
event PostCreated(uint postID, uint topicID);
function createTopic() public returns (uint, uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create topics
//Creates topic
uint topicID = numTopics++;
topics[topicID] = Topic(topicID, msg.sender, block.timestamp, new uint[](0));
users[msg.sender].topicIDs.push(topicID);
//Adds first post to topic
uint postID = numPosts++;
posts[postID] = Post(postID, msg.sender, block.timestamp, topicID);
topics[topicID].postIDs.push(postID);
users[msg.sender].postIDs.push(postID);
emit TopicCreated(topicID, postID);
return (topicID, postID);
}
function createPost(uint topicID) public returns (uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create posts
require(topicID<numTopics); // Only allow posting to a topic that exists
uint postID = numPosts++;
posts[postID] = Post(postID, msg.sender, block.timestamp, topicID);
topics[topicID].postIDs.push(postID);
users[msg.sender].postIDs.push(postID);
emit PostCreated(postID, topicID);
return postID;
}
function getNumberOfTopics() public view returns (uint) {
return numTopics;
}
function getTopic(uint topicID) public view returns (string memory, address, string memory, uint, uint[] memory) {
//require(hasUserSignedUp(msg.sender)); needed?
require(topicID<numTopics);
return (getOrbitTopicsDB(topics[topicID].author),
topics[topicID].author,
users[topics[topicID].author].username,
topics[topicID].timestamp,
topics[topicID].postIDs
);
}
function getTopicPosts(uint topicID) public view returns (uint[] memory) {
require(topicID<numTopics); // Topic should exist
return topics[topicID].postIDs;
}
function getPost(uint postID) public view returns (string memory, address, string memory, uint, uint) {
//require(hasUserSignedUp(msg.sender)); needed?
require(postID<numPosts);
return (getOrbitPostsDB(posts[postID].author),
posts[postID].author,
users[posts[postID].author].username,
posts[postID].timestamp,
posts[postID].topicID
);
}
} }
Loading…
Cancel
Save