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:
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.
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:
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.
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:
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!
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.
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.
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.
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:
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.
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
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:
And finally, we render the
TweetComposer inside of our
That's it! Now click the tweet button, write something nice and pat yourself on the back!