Ezerous 6 years ago
parent
commit
dadc615e3c
  1. 2
      app/src/components/Post.js
  2. 95
      app/src/components/Topic.js
  3. 6
      app/src/config/drizzleOptions.js
  4. 2
      app/src/containers/BoardContainer.js
  5. 107
      app/src/containers/LoadingContainer.js
  6. 3
      app/src/index.js
  7. 17
      app/src/redux/actions/orbitActions.js
  8. 28
      app/src/redux/reducers/orbitReducer.js
  9. 2
      app/src/redux/reducers/userReducer.js
  10. 10
      app/src/redux/sagas/drizzleUtilsSaga.js
  11. 22
      app/src/redux/sagas/eventSaga.js
  12. 44
      app/src/redux/sagas/orbitSaga.js
  13. 2
      app/src/redux/sagas/rootSaga.js
  14. 4
      app/src/redux/sagas/transactionsSaga.js
  15. 6
      app/src/redux/sagas/userSaga.js
  16. 37
      app/src/utils/orbitUtils.js
  17. 85
      contracts/Forum.sol
  18. 6
      contracts/Posting.sol
  19. 5
      migrations/2_deploy_contracts.js

2
app/src/components/Post.js

@ -82,7 +82,7 @@ class Post extends Component {
orbitPostData = orbitDB.postsDB.get(postID); orbitPostData = orbitDB.postsDB.get(postID);
} else { } else {
const fullAddress = `/orbitdb/${postData.value[0]}/posts`; const fullAddress = `/orbitdb/${postData.value[0]}/posts`;
const store = await orbitDB.orbitdb.open(fullAddress, {type: 'keyvalue'}); const store = await orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
const localOrbitData = store.get(postID); const localOrbitData = store.get(postID);

95
app/src/components/Topic.js

@ -9,71 +9,28 @@ import { Card } from 'semantic-ui-react';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import epochTimeConverter from '../helpers/EpochTimeConverter'; import epochTimeConverter from '../helpers/EpochTimeConverter';
import { addPeerDatabase } from '../redux/actions/orbitActions';
class Topic extends Component { class Topic extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.fetchSubject = this.fetchSubject.bind(this);
this.state = { this.state = {
topicSubject: null, askedForReplication: false,
topicSubjectFetchStatus: 'pending' fetchedSubject: false
}; };
} }
componentDidMount() {
this.fetchSubject(this.props.topicID);
}
componentDidUpdate() { componentDidUpdate() {
this.fetchSubject(this.props.topicID); const { dispatch, topicData, topicSubject, orbitDB } = this.props;
} const { askedForReplication } = this.state;
if(!askedForReplication && orbitDB.ipfsInitialized && orbitDB.orbitdb && dispatch && !topicSubject && topicData) {
async fetchSubject(topicID) { this.setState({ askedForReplication: true });
const { topicSubjectFetchStatus } = this.state; dispatch(addPeerDatabase(`/orbitdb/${topicData.value[0]}/topics`));
const { topicData, user, orbitDB } = this.props;
if (topicData !== null
&& topicSubjectFetchStatus === 'pending'
&& orbitDB.ipfsInitialized
&& orbitDB.orbitdb) {
let topicSubject;
if (topicData.value[1] === user.address) {
const orbitData = orbitDB.topicsDB.get(topicID);
if(orbitData)
topicSubject = orbitData.subject;
} else {
const fullAddress = `/orbitdb/${topicData.value[0]}/topics`;
const store = await orbitDB.orbitdb.open(fullAddress, {type: 'keyvalue'});
await store.load();
const localOrbitData = store.get(topicID);
if (localOrbitData)
topicSubject = localOrbitData.subject;
store.events.on('replicate', () => {
console.log("Initiated OrbitDB data replication.");
});
store.events.on('replicated', () => {
console.log("OrbitDB data replicated successfully.");
topicSubject = store.get(topicID).subject;
});
}
this.setState({
topicSubject,
topicSubjectFetchStatus: 'fetched'
});
} }
} }
render() { render() {
const { topicSubject } = this.state; const { history, topicID, topicData, topicSubject } = this.props;
const { history, topicID, topicData } = this.props;
return ( return (
<Card <Card
@ -146,9 +103,37 @@ Topic.propTypes = {
topicID: PropTypes.number.isRequired topicID: PropTypes.number.isRequired
}; };
const mapStateToProps = state => ({ function getTopicSubject(state, props){
const { user, orbit } = state;
if (orbit.ipfsInitialized && orbit.orbitdb) {
const { topicData, topicID } = props;
if (topicData){
if(user && topicData.value[1] === user.address) {
const orbitData = orbit.topicsDB.get(topicID);
if(orbitData && orbitData.subject)
return orbitData.subject;
}
const db = orbit.replicatedDatabases.find(db => db.fullAddress === `/orbitdb/${topicData.value[0]}/topics`);
if(db && db.ready && db.store){
const localOrbitData = db.store.get(topicID);
if (localOrbitData){
return localOrbitData.subject;
}
}
}
}
return null;
}
function mapStateToProps(state, ownProps) {
return {
user: state.user, user: state.user,
orbitDB: state.orbit orbitDB: state.orbit,
}); topicSubject: getTopicSubject(state, ownProps)
}
}
export default withRouter(connect(mapStateToProps)(Topic)); export default withRouter(connect(mapStateToProps)(Topic));

6
app/src/config/drizzleOptions.js

@ -1,5 +1,4 @@
import Forum from '../contracts/Forum.json'; import Forum from '../contracts/Forum.json';
import Posting from '../contracts/Posting.json';
const drizzleOptions = { const drizzleOptions = {
web3: { web3: {
@ -8,10 +7,9 @@ const drizzleOptions = {
url: 'ws://127.0.0.1:9545' url: 'ws://127.0.0.1:9545'
} }
}, },
contracts: [Forum, Posting], contracts: [Forum],
events: { events: {
Forum: ['UserSignedUp', 'UsernameUpdated'], Forum: ['UserSignedUp', 'UsernameUpdated', 'TopicCreated', 'PostCreated']
Posting: ['TopicCreated', 'PostCreated']
}, },
polls: { polls: {
accounts: 2000, accounts: 2000,

2
app/src/containers/BoardContainer.js

@ -11,7 +11,7 @@ import FloatingButton from '../components/FloatingButton';
/* import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions'; */ /* import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions'; */
const contract = 'Posting'; const contract = 'Forum';
const getNumberOfTopicsMethod = 'getNumberOfTopics'; const getNumberOfTopicsMethod = 'getNumberOfTopics';
class BoardContainer extends Component { class BoardContainer extends Component {

107
app/src/containers/LoadingContainer.js

@ -0,0 +1,107 @@
import React, { Children, Component } from 'react';
import { connect } from 'react-redux';
import ipfs_logo from '../assets/images/ipfs_logo.png';
//TODO: Add OrbitDB Loading thingy
class LoadingContainer extends Component {
render() {
if (this.props.web3.status === 'failed' || !this.props.web3.networkId) {
//TODO: wtf is this
if (this.props.errorComp)
return this.props.errorComp;
return (
<main className="container loading-screen">
<div className="pure-g">
<div className="pure-u-1-1">
<h1><span role="img" aria-label="Fox Face">🦊</span></h1>
<p><strong>This browser has no connection to the Ethereum network.</strong></p>
<p>
Please make sure that:
<ul>
<li>You use MetaMask or a dedicated Ethereum browser (e.g. Mist or Parity)</li>
<li>They are pointed to the correct network</li>
<li>Your account is unlocked and the app has the rights to access it</li>
</ul>
</p>
</div>
</div>
</main>
);
}
if (this.props.web3.status === 'initialized' && Object.keys(this.props.accounts).length === 0) {
return(
<main className="loading-screen">
<div>
<div>
<h1><span role="img" aria-label="Fox Face">🦊</span></h1>
<p><strong>We can't find any Ethereum accounts!</strong>.</p>
</div>
</div>
</main>
)
}
if (!this.props.contractInitialized) {
return(
<main className="loading-screen">
<div>
<div>
<h1><span role="img" aria-label="Gear"></span></h1>
<p><strong>Initializing contracts...</strong></p>
<p>If this takes too long please make sure they are deployed to the network.</p>
</div>
</div>
</main>
)
}
if (!this.props.ipfsInitialized) {
return(
<main className="loading-screen">
<div>
<div>
<img src={ipfs_logo} alt="ipfs_logo" height="50"/>
<p><strong>Initializing IPFS...</strong></p>
</div>
</div>
</main>
)
}
//TODO: wtf is this
if (this.props.drizzleStatus.initialized)
return Children.only(this.props.children);
//TODO: wtf is this
if (this.props.loadingComp)
return this.props.loadingComp;
return(
<main className="container loading-screen">
<div>
<div>
<h1><span role="img" aria-label="Gear"></span></h1>
<p>Loading dapp...</p>
</div>
</div>
</main>
)
}
}
const mapStateToProps = state => {
return {
accounts: state.accounts,
drizzleStatus: state.drizzleStatus,
web3: state.web3,
ipfsInitialized: state.orbit.ipfsInitialized,
contractInitialized: state.contracts.Forum.initialized
}
};
export default connect(mapStateToProps)(LoadingContainer);

3
app/src/index.js

@ -11,6 +11,7 @@ import * as serviceWorker from './utils/serviceWorker';
import './assets/css/index.css'; import './assets/css/index.css';
import drizzleOptions from './config/drizzleOptions'; import drizzleOptions from './config/drizzleOptions';
import LoadingContainer from './containers/LoadingContainer';
initIPFS(); initIPFS();
@ -20,9 +21,11 @@ export { drizzle };
render( render(
<Provider store={store}> <Provider store={store}>
<LoadingContainer>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
{routes} {routes}
</ConnectedRouter> </ConnectedRouter>
</LoadingContainer>
</Provider>, </Provider>,
document.getElementById('root'), document.getElementById('root'),
); );

17
app/src/redux/actions/orbitActions.js

@ -2,6 +2,9 @@ const IPFS_INITIALIZED = 'IPFS_INITIALIZED';
const DATABASES_CREATED = 'DATABASES_CREATED'; const DATABASES_CREATED = 'DATABASES_CREATED';
const DATABASES_LOADED = 'DATABASES_LOADED'; const DATABASES_LOADED = 'DATABASES_LOADED';
const DATABASES_NOT_READY = 'DATABASES_NOT_READY'; const DATABASES_NOT_READY = 'DATABASES_NOT_READY';
const ADD_PEER_DATABASE = 'ADD_PEER_DATABASE';
const OPENING_PEER_DATABASE = 'OPENING_PEER_DATABASE';
const PEER_DATABASE_LOADED = 'PEER_DATABASE_LOADED';
const UPDATE_PEERS = 'UPDATE_PEERS'; const UPDATE_PEERS = 'UPDATE_PEERS';
function updateDatabases(type, orbitdb, topicsDB, postsDB) { function updateDatabases(type, orbitdb, topicsDB, postsDB) {
@ -14,9 +17,21 @@ function updateDatabases(type, orbitdb, topicsDB, postsDB) {
}; };
} }
function addPeerDatabase(fullAddress) {
return {
type: ADD_PEER_DATABASE,
fullAddress
};
}
export { DATABASES_CREATED, export { DATABASES_CREATED,
DATABASES_LOADED, DATABASES_LOADED,
DATABASES_NOT_READY, DATABASES_NOT_READY,
IPFS_INITIALIZED, IPFS_INITIALIZED,
UPDATE_PEERS, UPDATE_PEERS,
updateDatabases }; ADD_PEER_DATABASE,
OPENING_PEER_DATABASE,
PEER_DATABASE_LOADED,
addPeerDatabase,
updateDatabases
};

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

@ -1,8 +1,8 @@
import { import {
DATABASES_CREATED, DATABASES_CREATED,
DATABASES_LOADED, DATABASES_LOADED,
DATABASES_NOT_READY, DATABASES_NOT_READY, OPENING_PEER_DATABASE,
IPFS_INITIALIZED, UPDATE_PEERS IPFS_INITIALIZED, UPDATE_PEERS, PEER_DATABASE_LOADED
} from '../actions/orbitActions'; } from '../actions/orbitActions';
const initialState = { const initialState = {
@ -12,8 +12,8 @@ const initialState = {
orbitdb: null, orbitdb: null,
topicsDB: null, topicsDB: null,
postsDB: null, postsDB: null,
topicsDBPeers: [], pubsubPeers: {topicsDBPeers:[], postsDBPeers:[]},
postsDBPeers: [], replicatedDatabases: [],
id: null id: null
}; };
@ -52,11 +52,27 @@ const orbitReducer = (state = initialState, action) => {
postsDB: null, postsDB: null,
id: null id: null
}; };
case OPENING_PEER_DATABASE:
if(state.replicatedDatabases.find(db => db.fullAddress === action.fullAddress))
return state;
return {
...state,
replicatedDatabases:[...state.replicatedDatabases,
{fullAddress: action.fullAddress, ready: false, store: null}]
};
case PEER_DATABASE_LOADED:
return {
...state,
replicatedDatabases: [...state.replicatedDatabases.map((db) => {
if (db.fullAddress !== action.fullAddress)
return db; // This isn't the item we care about - keep it as-is
return { ...db, ready: true, store: action.store} // Otherwise return an updated value
})]
};
case UPDATE_PEERS: case UPDATE_PEERS:
return { return {
...state, ...state,
topicsDBPeers: action.topicsDBPeers, pubsubPeers: {topicsDBPeers:action.topicsDBPeers, postsDBPeers:action.postsDBPeers}
postsDBPeers: action.postsDBPeers
}; };
default: default:
return state; return state;

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

@ -2,7 +2,7 @@ const initialState = {
username: '', username: '',
address: '0x0', address: '0x0',
avatarUrl: '', avatarUrl: '',
hasSignedUp: null hasSignedUp: false
}; };
const userReducer = (state = initialState, action) => { const userReducer = (state = initialState, action) => {

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

@ -5,20 +5,20 @@ import Forum from '../../contracts/Forum';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
const accounts = state => state.accounts; const accounts = state => state.accounts;
let initFlag; let web3; let let initFlag, web3, forumContract;
contract;
function* init() { function* init() {
if (!initFlag) { if (!initFlag) {
web3 = yield call(getWeb3); web3 = yield call(getWeb3);
contract = yield call(getContractInstance, { forumContract = yield call(getContractInstance, {
web3, artifact: Forum web3, artifact: Forum
}); });
initFlag = true; initFlag = true;
yield put({ yield put({
type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[] type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[]
}); });
} else console.warn('Attempted to reinitialize drizzleUtilsSaga!'); }
else console.warn('Attempted to reinitialize drizzleUtilsSaga!');
} }
// If the method below proves to be problematic/ineffective (i.e. getting current account // If the method below proves to be problematic/ineffective (i.e. getting current account
@ -32,6 +32,6 @@ function* drizzleUtilsSaga() {
yield takeLatest('DRIZZLE_INITIALIZED', init); yield takeLatest('DRIZZLE_INITIALIZED', init);
} }
export { web3, contract, getCurrentAccount }; export { web3, forumContract, getCurrentAccount };
export default drizzleUtilsSaga; export default drizzleUtilsSaga;

22
app/src/redux/sagas/eventSaga.js

@ -0,0 +1,22 @@
import { put, takeEvery } from 'redux-saga/effects';
const EVENT_FIRED = 'EVENT_FIRED'; // This is fired internally by drizzle
const CONTRACT_EVENT_FIRED = 'CONTRACT_EVENT_FIRED';
let eventSet = new Set();
// Entire purpose of this saga is to bypass a strange bug where EVENT_FIRED is called
// multiple times for the same event
function* sanitizeEvent(action) {
const size = eventSet.size;
eventSet.add(action.event.transactionHash + action.name);
if(eventSet.size>size)
yield put({ ...action, type: CONTRACT_EVENT_FIRED });
}
function* eventSaga() {
yield takeEvery(EVENT_FIRED, sanitizeEvent);
}
export default eventSaga;
export { CONTRACT_EVENT_FIRED }

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

@ -1,9 +1,14 @@
import { all, call, put, select, take, takeEvery, takeLatest } from 'redux-saga/effects'; import { all, call, put, select, take, takeEvery, takeLatest } from 'redux-saga/effects';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash.isequal';
import { contract, getCurrentAccount } from './drizzleUtilsSaga'; import { forumContract, getCurrentAccount } from './drizzleUtilsSaga';
import { loadDatabases } from '../../utils/orbitUtils'; import { loadDatabases, orbitSagaOpen } from '../../utils/orbitUtils';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
import { DATABASES_NOT_READY, IPFS_INITIALIZED, UPDATE_PEERS } from '../actions/orbitActions'; import {
ADD_PEER_DATABASE, PEER_DATABASE_LOADED,
DATABASES_NOT_READY,
IPFS_INITIALIZED, OPENING_PEER_DATABASE,
UPDATE_PEERS
} from '../actions/orbitActions';
let latestAccount; let latestAccount;
@ -13,19 +18,19 @@ function* getOrbitDBInfo() {
}); });
const account = yield call(getCurrentAccount); const account = yield call(getCurrentAccount);
if (account !== latestAccount) { if (account !== latestAccount) {
const txObj1 = yield call(contract.methods.hasUserSignedUp, const txObj1 = yield call(forumContract.methods.hasUserSignedUp,
...[account]); ...[account]);
try { try {
const callResult = yield call(txObj1.call, { const callResult = yield call(txObj1.call, {
address: account address: account
}); });
if (callResult) { if (callResult) {
const txObj2 = yield call(contract.methods.getOrbitIdentityInfo, const txObj2 = yield call(forumContract.methods.getOrbitIdentityInfo,
...[account]); ...[account]);
const orbitIdentityInfo = yield call(txObj2.call, { const orbitIdentityInfo = yield call(txObj2.call, {
address: account address: account
}); });
const txObj3 = yield call(contract.methods.getOrbitDBInfo, const txObj3 = yield call(forumContract.methods.getOrbitDBInfo,
...[account]); ...[account]);
const orbitDBInfo = yield call(txObj3.call, { const orbitDBInfo = yield call(txObj3.call, {
address: account address: account
@ -50,6 +55,28 @@ function* getOrbitDBInfo() {
} }
} }
let peerOrbitAddresses = new Set();
function* addPeerDatabase(action) {
const fullAddress = action.fullAddress;
const size = peerOrbitAddresses.size;
peerOrbitAddresses.add(fullAddress);
if(peerOrbitAddresses.size>size){
const { orbitdb } = yield select(state => state.orbit);
if(orbitdb){
yield put.resolve({
type: OPENING_PEER_DATABASE, fullAddress
});
const store = yield call(orbitSagaOpen, orbitdb, fullAddress);
yield put({
type: PEER_DATABASE_LOADED, fullAddress, store: store
});
}
}
}
//Keeps track of connected pubsub peers in Redux store
function* updatePeersState() { function* updatePeersState() {
const orbit = yield select(state => state.orbit); const orbit = yield select(state => state.orbit);
if(orbit.ready){ if(orbit.ready){
@ -57,8 +84,8 @@ function* updatePeersState() {
const postsDBAddress = orbit.postsDB.address.toString(); const postsDBAddress = orbit.postsDB.address.toString();
const topicsDBPeers = yield call(orbit.ipfs.pubsub.peers, topicsDBAddress); const topicsDBPeers = yield call(orbit.ipfs.pubsub.peers, topicsDBAddress);
const postsDBPeers = yield call(orbit.ipfs.pubsub.peers, postsDBAddress); const postsDBPeers = yield call(orbit.ipfs.pubsub.peers, postsDBAddress);
if(!isEqual(topicsDBPeers.sort(), orbit.topicsDBPeers.sort()) || if(!isEqual(topicsDBPeers.sort(), orbit.pubsubPeers.topicsDBPeers.sort()) ||
!isEqual(postsDBPeers.sort(), orbit.postsDBPeers.sort())){ !isEqual(postsDBPeers.sort(), orbit.pubsubPeers.postsDBPeers.sort())){
yield put({ yield put({
type: UPDATE_PEERS, topicsDBPeers, postsDBPeers type: UPDATE_PEERS, topicsDBPeers, postsDBPeers
}); });
@ -72,6 +99,7 @@ function* orbitSaga() {
take(IPFS_INITIALIZED) take(IPFS_INITIALIZED)
]); ]);
yield takeLatest('ACCOUNT_CHANGED', getOrbitDBInfo); yield takeLatest('ACCOUNT_CHANGED', getOrbitDBInfo);
yield takeEvery(ADD_PEER_DATABASE, addPeerDatabase);
yield takeEvery('ACCOUNTS_FETCHED', updatePeersState); yield takeEvery('ACCOUNTS_FETCHED', updatePeersState);
} }

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

@ -4,6 +4,7 @@ import drizzleUtilsSaga from './drizzleUtilsSaga';
import userSaga from './userSaga'; import userSaga from './userSaga';
import orbitSaga from './orbitSaga'; import orbitSaga from './orbitSaga';
import transactionsSaga from './transactionsSaga'; import transactionsSaga from './transactionsSaga';
import eventSaga from './eventSaga';
export default function* root() { export default function* root() {
const sagas = [ const sagas = [
@ -11,6 +12,7 @@ export default function* root() {
drizzleUtilsSaga, drizzleUtilsSaga,
orbitSaga, orbitSaga,
userSaga, userSaga,
eventSaga,
transactionsSaga]; transactionsSaga];
yield all( yield all(
sagas.map(saga => fork(saga)), sagas.map(saga => fork(saga)),

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

@ -3,6 +3,7 @@ import { call, select, take, takeEvery } from 'redux-saga/effects';
import { drizzle } from '../../index'; import { drizzle } from '../../index';
import { orbitSagaPut } from '../../utils/orbitUtils'; import { orbitSagaPut } from '../../utils/orbitUtils';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
import { CONTRACT_EVENT_FIRED } from './eventSaga';
const transactionsHistory = Object.create(null); const transactionsHistory = Object.create(null);
@ -19,7 +20,6 @@ function* initTransaction(action) {
function* handleEvent(action) { function* handleEvent(action) {
const transactionStack = yield select(state => state.transactionStack); const transactionStack = yield select(state => state.transactionStack);
const dataKey = transactionStack.indexOf(action.event.transactionHash); const dataKey = transactionStack.indexOf(action.event.transactionHash);
switch (action.event.event) { switch (action.event.event) {
case 'TopicCreated': case 'TopicCreated':
if (dataKey !== -1 if (dataKey !== -1
@ -75,7 +75,7 @@ function* handleError() {
function* transactionsSaga() { function* transactionsSaga() {
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED); yield take(DRIZZLE_UTILS_SAGA_INITIALIZED);
yield takeEvery('INIT_TRANSACTION', initTransaction); yield takeEvery('INIT_TRANSACTION', initTransaction);
yield takeEvery('EVENT_FIRED', handleEvent); yield takeEvery(CONTRACT_EVENT_FIRED, handleEvent);
yield takeEvery('TX_ERROR', handleError); yield takeEvery('TX_ERROR', handleError);
} }

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

@ -1,6 +1,6 @@
import { call, put, select, take, takeEvery } from 'redux-saga/effects'; import { call, put, select, take, takeEvery } from 'redux-saga/effects';
import { contract, getCurrentAccount } from './drizzleUtilsSaga'; import { forumContract, getCurrentAccount } from './drizzleUtilsSaga';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
let account; let account;
@ -13,14 +13,14 @@ function* updateUserData() {
type: 'ACCOUNT_CHANGED', ...[] type: 'ACCOUNT_CHANGED', ...[]
}); });
} }
const txObj1 = yield call(contract.methods.hasUserSignedUp, ...[account]); const txObj1 = yield call(forumContract.methods.hasUserSignedUp, ...[account]);
try { try {
const userState = yield call(getUserState); const userState = yield call(getUserState);
const callResult = yield call(txObj1.call, { const callResult = yield call(txObj1.call, {
address: account address: account
}); });
if (callResult) { if (callResult) {
const txObj2 = yield call(contract.methods.getUsername, ...[account]); const txObj2 = yield call(forumContract.methods.getUsername, ...[account]);
const username = yield call(txObj2.call, { const username = yield call(txObj2.call, {
address: account address: account
}); });

37
app/src/utils/orbitUtils.js

@ -13,14 +13,14 @@ function initIPFS() {
store.dispatch({ store.dispatch({
type: IPFS_INITIALIZED, ipfs type: IPFS_INITIALIZED, ipfs
}); });
console.log('IPFS initialized.'); console.debug('IPFS initialized.');
}); });
} }
async function createDatabases() { async function createDatabases() {
console.log("Deleting local storage..."); // Else we are in danger of reusing an existing orbit console.debug("Deleting local storage..."); // Else we are in danger of reusing an existing orbit
localStorage.clear(); // Perhaps not needed at all when orbit ids are used in Orbit 0.20.x+ localStorage.clear(); // Perhaps not needed at all when orbit ids are used in Orbit 0.20.x+
console.log('Creating databases...'); console.debug('Creating databases...');
const ipfs = getIPFS(); const ipfs = getIPFS();
const orbitdb = await new OrbitDB(ipfs); const orbitdb = await new OrbitDB(ipfs);
const topicsDB = await orbitdb.keyvalue('topics'); const topicsDB = await orbitdb.keyvalue('topics');
@ -67,7 +67,21 @@ async function loadDatabases(identityId, identityPublicKey, identityPrivateKey,
await topicsDB.load().catch((error) => console.error(`TopicsDB loading error: ${error}`)); await topicsDB.load().catch((error) => console.error(`TopicsDB loading error: ${error}`));
await postsDB.load().catch((error) => console.error(`PostsDB loading error: ${error}`)); await postsDB.load().catch((error) => console.error(`PostsDB loading error: ${error}`));
console.log('Orbit databases loaded successfully.'); //It is possible that we lack our own data and need to replicate them from somewhere else
topicsDB.events.on('replicate', (address) => {
console.log(`TopicsDB Replicating (${address}).`);
});
topicsDB.events.on('replicated', (address) => {
console.log(`TopicsDB replicated (${address}).`);
});
postsDB.events.on('replicate', (address) => {
console.log(`PostsDB replicating (${address}).`);
});
postsDB.events.on('replicated', (address) => {
console.log(`PostsDB replicated (${address}).`);
});
console.debug('Orbit databases loaded successfully.');
store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB)); store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB));
} }
@ -79,4 +93,17 @@ async function orbitSagaPut(db, key, value) {
await db.put(key, value).catch((error) => console.error(`Orbit put error: ${error}`)); await db.put(key, value).catch((error) => console.error(`Orbit put error: ${error}`));
} }
export { initIPFS, createDatabases, loadDatabases, orbitSagaPut }; async function orbitSagaOpen(orbitdb, address) {
const store = await orbitdb.keyvalue(address)
.catch((error) => console.error(`Error opening a peer's db: ${error}`));
await store.load().catch((error) => console.log(error));
store.events.on('replicate', (address) => {
console.log(`A peer's DB is being replicated (${address}).`);
});
store.events.on('replicated', (address) => {
console.log(`A peer's DB was replicated (${address}).`);
});
return store;
}
export { initIPFS, createDatabases, loadDatabases, orbitSagaPut, orbitSagaOpen };

85
contracts/Forum.sol

@ -1,14 +1,6 @@
pragma solidity >=0.5.6 <0.6.0; pragma solidity >=0.5.6 <0.6.0;
import "./Posting.sol";
contract Forum { contract Forum {
Posting posting;
constructor(address addr) public {
posting = Posting(addr);
posting.setForumContractAddress();
}
//----------------------------------------USER---------------------------------------- //----------------------------------------USER----------------------------------------
struct User { struct User {
@ -163,41 +155,86 @@ contract Forum {
); );
} }
//----POSTING----- //----------------------------------------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) { function createTopic() public returns (uint, uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create topics require(hasUserSignedUp(msg.sender)); // Only registered users can create topics
(uint topicID, uint postID) = posting.createTopic(msg.sender); //Creates topic
uint topicID = numTopics++;
topics[topicID] = Topic(topicID, msg.sender, block.timestamp, new uint[](0));
users[msg.sender].topicIDs.push(topicID); 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); users[msg.sender].postIDs.push(postID);
emit TopicCreated(topicID, postID);
return (topicID, postID); return (topicID, postID);
} }
function createPost(uint topicID) public returns (uint) { function createPost(uint topicID) public returns (uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create posts require(hasUserSignedUp(msg.sender)); // Only registered users can create posts
uint postID = posting.createPost(topicID, msg.sender); 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); users[msg.sender].postIDs.push(postID);
emit PostCreated(postID, topicID);
return postID; 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) { function getTopic(uint topicID) public view returns (string memory, address, string memory, uint, uint[] memory) {
(address author, uint timestamp, uint[] memory postIDs) = posting.getTopicInfo(topicID); //require(hasUserSignedUp(msg.sender)); needed?
return (getOrbitTopicsDB(author), require(topicID<numTopics);
author, return (getOrbitTopicsDB(topics[topicID].author),
users[author].username, topics[topicID].author,
timestamp, users[topics[topicID].author].username,
postIDs 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) { function getPost(uint postID) public view returns (string memory, address, string memory, uint, uint) {
(address author, uint timestamp, uint topicID) = posting.getPostInfo(postID); //require(hasUserSignedUp(msg.sender)); needed?
return (getOrbitPostsDB(author), require(postID<numPosts);
author, return (getOrbitPostsDB(posts[postID].author),
users[author].username, posts[postID].author,
timestamp, users[posts[postID].author].username,
topicID posts[postID].timestamp,
posts[postID].topicID
); );
} }
} }

6
contracts/Posting.sol

@ -28,9 +28,6 @@ contract Posting {
mapping (uint => Topic) topics; mapping (uint => Topic) topics;
mapping (uint => Post) posts; mapping (uint => Post) posts;
event TopicCreated(uint topicID, uint postID);
event PostCreated(uint postID, uint topicID);
function createTopic(address author) public returns (uint, uint) { function createTopic(address author) public returns (uint, uint) {
require(msg.sender==forumContractAddress); require(msg.sender==forumContractAddress);
//Creates topic //Creates topic
@ -42,7 +39,6 @@ contract Posting {
posts[postID] = Post(postID, author, block.timestamp, topicID); posts[postID] = Post(postID, author, block.timestamp, topicID);
topics[topicID].postIDs.push(postID); topics[topicID].postIDs.push(postID);
emit TopicCreated(topicID, postID);
return (topicID, postID); return (topicID, postID);
} }
@ -52,7 +48,7 @@ contract Posting {
uint postID = numPosts++; uint postID = numPosts++;
posts[postID] = Post(postID, author, block.timestamp, topicID); posts[postID] = Post(postID, author, block.timestamp, topicID);
topics[topicID].postIDs.push(postID); topics[topicID].postIDs.push(postID);
emit PostCreated(postID, topicID);
return postID; return postID;
} }

5
migrations/2_deploy_contracts.js

@ -1,8 +1,5 @@
const Forum = artifacts.require("Forum"); const Forum = artifacts.require("Forum");
const Posting = artifacts.require("Posting");
module.exports = function(deployer) { module.exports = function(deployer) {
deployer.deploy(Posting).then(function() { deployer.deploy(Forum);
return deployer.deploy(Forum, Posting.address)
});
}; };

Loading…
Cancel
Save