diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index cc64284..10a07fc 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -1,14 +1,17 @@ import React, { useContext, useEffect, useState } from 'react'; import { List } from 'semantic-ui-react'; import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import AppContext from '../../AppContext'; +import { FETCH_USER_TOPIC } from '../../../redux/actions/peerDbReplicationActions'; const TopicListRow = (props) => { const { topicData, topicId } = props; const { breeze: { orbit } } = useContext(AppContext.Context); const [topicSubject, setTopicSubject] = useState(); const userAddress = useSelector((state) => state.user.address); + const topics = useSelector((state) => state.orbitData.topics); + const dispatch = useDispatch(); useEffect(() => { if (userAddress === topicData.userAddress) { @@ -18,24 +21,21 @@ const TopicListRow = (props) => { return; } - // TODO: this is not correct, each TopicListRow inside the TopicList overrides the on.replicated callback. As a - // result, for the topics of each user, only the callback of the last TopicListRow in the list survives and gets - // executed. Moving these calls up on the TopicList helps with this issue but introduces other problems. - orbit - .determineAddress('topics', 'keyvalue', { accessController: { write: [topicData.userAddress] } }) - .then((ipfsMultihash) => { - const peerDbAddress = `/orbitdb/${ipfsMultihash.root}/topics`; + dispatch({ + type: FETCH_USER_TOPIC, + orbit, + userAddress: topicData.userAddress, + topicId, + }); + }, [dispatch, orbit, topicData.userAddress, topicId, userAddress]); - return orbit.keyvalue(peerDbAddress) - .then((keyValueStore) => { - keyValueStore.events.on('replicated', () => { - setTopicSubject(keyValueStore.get(topicId)); - }); - }) - .catch((error) => console.error(`Error opening a peer's db: ${error}`)); - }) - .catch((error) => console.error(`Error opening a peer's db: ${error}`)); - }, [orbit, topicData.userAddress, topicId, userAddress]); + useEffect(() => { + const topicFound = topics.find((topic) => topic.topicId === topicId); + + if (topicFound) { + setTopicSubject(topicFound); + } + }, [topicId, topics]); return ( <> diff --git a/packages/concordia-app/src/options/breezeOptions.js b/packages/concordia-app/src/options/breezeOptions.js index ca8ab7b..c06ac0b 100644 --- a/packages/concordia-app/src/options/breezeOptions.js +++ b/packages/concordia-app/src/options/breezeOptions.js @@ -32,11 +32,11 @@ const breezeOptions = { databases: [ { name: 'topics', - type: orbitConstants.ORBIT_TYPE_KEYVALUE, + type: 'keyvalue', }, { name: 'posts', - type: orbitConstants.ORBIT_TYPE_KEYVALUE, + type: 'keyvalue', }, ], }, diff --git a/packages/concordia-app/src/redux/actions/peerDbReplicationActions.js b/packages/concordia-app/src/redux/actions/peerDbReplicationActions.js new file mode 100644 index 0000000..c9b2190 --- /dev/null +++ b/packages/concordia-app/src/redux/actions/peerDbReplicationActions.js @@ -0,0 +1,9 @@ +export const FETCH_USER_TOPIC = 'FETCH_USER_TOPIC'; +export const ADD_USER_TOPIC = 'ADD_USER_TOPIC'; +export const UPDATE_USER_TOPICS = 'UPDATE_USER_TOPICS'; + +export const FETCH_USER_POST = 'FETCH_USER_POST'; +export const ADD_USER_POST = 'ADD_USER_POST'; +export const UPDATE_USER_POSTS = 'UPDATE_USER_POSTS'; + +export const UPDATE_ORBIT_DATA = 'UPDATE_ORBIT_DATA'; diff --git a/packages/concordia-app/src/redux/reducers/peerDbReplicationReducer.js b/packages/concordia-app/src/redux/reducers/peerDbReplicationReducer.js new file mode 100644 index 0000000..5ebd70b --- /dev/null +++ b/packages/concordia-app/src/redux/reducers/peerDbReplicationReducer.js @@ -0,0 +1,52 @@ +import { ADD_USER_POST, ADD_USER_TOPIC, UPDATE_ORBIT_DATA } from '../actions/peerDbReplicationActions'; + +const initialState = { + topics: [], + posts: [], +}; + +const peerDbReplicationReducer = (state = initialState, action) => { + const { type } = action; + + if (type === ADD_USER_TOPIC) { + const { topic } = action; + + return { + ...state, + topics: [ + ...state.topics, + topic, + ], + }; + } + + if (type === ADD_USER_POST) { + const { post } = action; + + return { + ...state, + posts: [ + ...state.posts, + post, + ], + }; + } + + if (type === UPDATE_ORBIT_DATA) { + const { topics, posts } = action; + + return { + ...state, + topics: [ + ...topics, + ], + posts: [ + ...posts, + ], + }; + } + + return state; +}; + +export default peerDbReplicationReducer; diff --git a/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js b/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js new file mode 100644 index 0000000..1fe41db --- /dev/null +++ b/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js @@ -0,0 +1,100 @@ +import { + call, put, select, takeEvery, +} from 'redux-saga/effects'; +import { + createOrbitDatabase, + ORBIT_DATABASE_READY, + ORBIT_DATABASE_REPLICATED, +} from '@ezerous/breeze/src/orbit/orbitActions'; +import determineDBAddress from '../../orbit/orbitUtils'; +import { + ADD_USER_POST, + ADD_USER_TOPIC, + FETCH_USER_POST, + FETCH_USER_TOPIC, + UPDATE_ORBIT_DATA, +} from '../actions/peerDbReplicationActions'; + +function* fetchUserDb({ orbit, peerDbAddress }) { + yield put(createOrbitDatabase(orbit, { name: peerDbAddress, type: 'keyvalue' })); +} + +function* fetchTopic({ orbit, userAddress, topicId }) { + const previousTopics = yield select((state) => state.orbitData.topics); + const peerDbAddress = yield call(determineDBAddress, { + orbit, dbName: 'topics', type: 'keyvalue', identityId: userAddress, + }); + + if (previousTopics === undefined || !previousTopics.some((topic) => topic.topicId === topicId)) { + yield put({ + type: ADD_USER_TOPIC, + topic: { + userAddress, + dbAddress: peerDbAddress, + topicId, + subject: null, + }, + }); + } + + yield call(fetchUserDb, { orbit, peerDbAddress }); +} + +function* fetchUserPost({ orbit, userAddress, postId }) { + const previousPosts = yield select((state) => state.orbitData.posts); + const peerDbAddress = yield call(determineDBAddress, { + orbit, dbName: 'posts', type: 'keyvalue', identityId: userAddress, + }); + + if (previousPosts === undefined || !previousPosts.some((post) => post.postId === postId)) { + yield put({ + type: ADD_USER_POST, + posts: { + userAddress, + dbAddress: peerDbAddress, + postId, + subject: null, + message: null, + }, + }); + } + + yield call(fetchUserDb, { orbit, peerDbAddress }); +} + +function* updateReduxState({ database }) { + const { topics, posts } = yield select((state) => ({ topics: state.orbitData.topics, posts: state.orbitData.posts })); + + yield put({ + type: UPDATE_ORBIT_DATA, + topics: topics.map((topic) => { + if (database.id === topic.dbAddress) { + return ({ + ...topic, + ...database.get(topic.topicId), + }); + } + + return { ...topic }; + }), + posts: posts.map((post) => { + if (database.id === post.dbAddress) { + return ({ + ...post, + ...database.get(post.postId), + }); + } + + return { ...post }; + }), + }); +} + +function* peerDbReplicationSaga() { + yield takeEvery(FETCH_USER_TOPIC, fetchTopic); + yield takeEvery(FETCH_USER_POST, fetchUserPost); + yield takeEvery(ORBIT_DATABASE_REPLICATED, updateReduxState); + yield takeEvery(ORBIT_DATABASE_READY, updateReduxState); +} + +export default peerDbReplicationSaga; diff --git a/packages/concordia-app/src/redux/sagas/rootSaga.js b/packages/concordia-app/src/redux/sagas/rootSaga.js index beead57..7f5bfb4 100644 --- a/packages/concordia-app/src/redux/sagas/rootSaga.js +++ b/packages/concordia-app/src/redux/sagas/rootSaga.js @@ -3,6 +3,7 @@ import { drizzleSagas } from '@ezerous/drizzle'; import { breezeSagas } from '@ezerous/breeze'; import orbitSaga from './orbitSaga'; import userSaga from './userSaga'; +import peerDbReplicationSaga from './peerDbReplicationSaga'; export default function* root() { const sagas = [ @@ -10,6 +11,7 @@ export default function* root() { ...breezeSagas, orbitSaga, userSaga, + peerDbReplicationSaga, ]; yield all( sagas.map((saga) => fork(saga)), diff --git a/packages/concordia-app/src/redux/store.js b/packages/concordia-app/src/redux/store.js index 23ff9fc..d0f75f0 100644 --- a/packages/concordia-app/src/redux/store.js +++ b/packages/concordia-app/src/redux/store.js @@ -5,6 +5,7 @@ import createSagaMiddleware from 'redux-saga'; import userReducer from './reducers/userReducer'; import rootSaga from './sagas/rootSaga'; import drizzleOptions from '../options/drizzleOptions'; +import peerDbReplicationReducer from './reducers/peerDbReplicationReducer'; const initialState = { contracts: generateContractsInitialState(drizzleOptions), @@ -13,7 +14,9 @@ const initialState = { const sagaMiddleware = createSagaMiddleware(); const store = configureStore({ - reducer: { ...drizzleReducers, ...breezeReducers, user: userReducer }, + reducer: { + ...drizzleReducers, ...breezeReducers, user: userReducer, orbitData: peerDbReplicationReducer, + }, middleware: getDefaultMiddleware({ // https://redux.js.org/style-guide/style-guide/#do-not-put-non-serializable-values-in-state-or-actions serializableCheck: false,