Lesson 14

Adding new tweets

1

In this chapter, we're going to make it possible for users to create new tweets straight from the web app. But first, we need to make some adjustments to our tweet contracts.

Polishing the Tweeting contracts

Right now, the createTweet function in our TweetController contract simply takes a parameter called _userId to determine who the tweeter is:

This is obviously not a very good security model, since we could just pass in anyone's ID. Instead, we want to deduce the user ID based on the sender's address.

For that, we're going to make a call to the UserStorage using the contract manager. The final code should look like this:

contracts/tweets/TweetController.sol

Now that that's changed, we obviously also need to update our integration test so that it calls the createTweet function with a single parameter.

test/integration/tweets.js

If we run the test now, we see that it... fails?


The reason for this is that a user with our address has not yet been created in the UserStorage, so when the integration test runs, it gets halted by the check we added (require(_userId != 0)).

We can fix this by creating the user in a new before() function, which runs before any test by convention:

test/integration/tweets.js


And now everything is fine again!


We also want to do a little change in the TweetStorage. In our DApp, we want to be able to go to a person's profile and see all their tweets. For this, we need some kind of list that stores all the tweets related to a user ID.

We're going to accomplish this by creating a new mapping (userTweetIds) that points a user ID to a list of tweetIds.

For some reason, this mapping does not generate a working getter even if we declare it as public (you're welcome to try it yourself). We'll therefore instead add an explicit getter function called getTweetIdsFromUser that fetches the list of tweet IDs based on a user ID.

contracts/users/TweetStorage.sol

Again, we need to make sure that this works as expected, so in our integration test file, we add a new test at the very end:

test/integration/tweets.js


All good!

Automating some processes

Since we've updated one of our (normally immutable) storage contracts, we again need to redeploy all of our contracts. This time though, instead of stopping our running testrpc and spawning a brand new blockchain, we will simply use the truffle migrate command with the special --reset option. That way, even if the contracts have already been deployed before, Truffle will run all the migrations from scratch and upload the contracts to brand new addresses.

We'll also need to copy the new artifact files to the client folder. Since it's getting a bit tedious to perform these steps whenever we want to test out some new contract features, let's see how we can automate the process!

In our package.json file, we'll tweak the dev script so that it always runs our migrations from scratch, copies the artifacts, and finally starts the frontend server.

package.json

Now, you can just re-run npm run dev whenever you want to test the latest contract changes in the web app, without worrying about what's already been deployed.

It can also be handy to have a script that automatically refills your MetaMask wallet with some ether, in case your ganache-cli instance gets restarted.

For this we'll add a new script called fund-metamask, which simply runs the same one-liner we used previously inside the Truffle console to fund our MetaMask wallet.

package.json

The JavaScript file (fund-metamask.js) needs to have a single module.exports function according to Truffle's documentation. The rest is pretty much the same as we did before manually, except that we call a callback function at the end to indicate that the transaction is done.

fund-metamask.js

There we go. Although you might not need it now, it's nice to know that you can run npm run fund-metamask to quickly fill up your MetaMask account in the future.

Alright, let's try running npm run dev in another window. Hopefully, you'll see all your scripts being executed on after the other, with this showing at the end:

That certainly saves us some time!

Creating a tweet from the DApp

Let's go back to the frontend. The first step is to display a "Compose"-button in the navigation bar, which should bring up our tweeting modal.

For this, we simply import the SVG icon we want and wrap it in a button next to our name:

client/components/Nav.js

The toggleComposeModal method simply toggles a state variable, which determines if our modal is rendered or not. We'll use the same Modal component that we're using for the registration form.

client/components/Header.js

That's a good start! Now we just need to fill the modal with its actual elements. For that, we'll create a new component called TweetComposer:

client/components/TweetComposer.js

This component uses typical React behaviour to let the user type in a textarea using state variables. As you can see, we use the createTweet action from our web3/tweets.js-file to post the tweet. This function needs to be slightly modified though, since the ABI for the TweetController contract has been modified since we wrote it. It now takes a single parameter (the tweet text) instead of two:

client/web3/tweets.js

And finally, we render the TweetComposer inside of our Modal:

client/components/Header.js

That's it! Now click the tweet button, write something nice and pat yourself on the back!

Comments

Phaedrus Raznikov

Also gotta import React in the TweetComposer file :P

Phaedrus Raznikov

When we test the getTweetsFromUserId function in TweetStorage, there's a compilation error unless the return signature is rewritten from uint[] to uint[] memory