07 / 08
Extend news feed dapp
Once the core is ready, you can let users follow each other and read a feed of posts from the accounts they follow. Both Solidity and Rell handle this without much difficulty. Start with the Solidity contract.
Add a follow function that accepts two addresses: the user doing the following and the user being followed. The function emits an event that records this relationship. For now, both users are passed in as parameters. The authentication section later shows how to tighten this by relying on the authenticated user instead.
event FollowUser(address indexed user, address indexed userToFollow);function followUser(address user, address userToFollow) public { emit FollowUser(user, userToFollow);}In Rell, you define a follow entity that models a many-to-many relationship between a user and a follower. You then write a function that inserts a new row into this relationship table. As before, every piece of data stays on-chain in the dapp.
entity follow { index user; index follows: user; key user, follows;}// Then we add a follow method to follow a useroperation follow_user(user_id: byte_array, user_id_to_follow: byte_array) { val user = user @ { .id == user_id }; val to_follow = user @ { .id == user_id_to_follow }; create follow ( user = user, follows = to_follow );}The follow_user operation first looks up the entities for both users. It then creates a follow record that links the user to the account they follow. You can shorten this by placing the user queries inline:
operation follow_user(user_id: byte_array, user_id_to_follow: byte_array) { create follow ( user = user @ { .id == user_id }, follows = user @ { .id == user_id_to_follow } );}Fetching posts from followers
With the model and follow operation done, the next step is retrieving a feed from followed users. Solidity requires several steps for this. You first query the event log for FollowUser events that match the user's address to get the list of followed users. You then run a second query that loops over those users and collects their posts.
const contract = new web3.eth.Contract(contractABI, contractAddress);async function getFollowedUsers(web3, contract, userAddress) { const followEvents = await contract.getPastEvents('FollowUser', { filter: { user: userAddress }, fromBlock: 0, toBlock: 'latest' }); return followEvents.map(event => event.returnValues.userToFollow);}async function getPostsOfFollowedUsers(web3, contract, followedUsers) { let posts = []; for (const user of followedUsers) { const userPosts = await contract.getPastEvents('PostCreated', { filter: { user: user }, fromBlock: 0, toBlock: 'latest' }); posts = posts.concat(userPosts.map(event => ({ user: event.returnValues.user, text: event.returnValues.text, timestamp: new web3.utils.BN(event.returnValues.timestamp).toNumber(), }))); } return posts;}const followedUsers = await getFollowedUsers(web3, contract, 'userAddress');const posts = await getPostsOfFollowedUsers(web3, contract, followedUsers);Retrieving relational data this way is awkward. You can push the data into a relational database for the frontend to query, but that adds complexity and moves the solution toward centralization. Rell's relational model lets you do the same work efficiently on-chain. The following query returns every post written by users that the given user follows.
query get_posts_from_followers(user_id: byte_array) { val posts = (user, follow, post) @* { .id == user_id, // Join on user id follow.user == user, // Join on who the user follows post.author == follow.follows // Join on posts for who the user follows } ( post.timestamp, post.content, user_id = user_id ); return ( posts = posts );}The query uses joins to collect all the data in a single pass. Because Chromia's blockchain is relational and built on PostgreSQL, this query runs very efficiently. This example begins to show the advantages of a relational blockchain on Chromia. The next section covers authentication in Solidity and Rell.