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!
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.
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.
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.
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:
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.
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
:
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 Modal
:
That's it! Now click the tweet button, write something nice and pat yourself on the back!
Comments
You need a Ludu account in order to ask the instructor a question or post a comment.
Also gotta import React in the TweetComposer file :P
When we test the
getTweetsFromUserId
function in TweetStorage, there's a compilation error unless the return signature is rewritten fromuint[]
touint[] memory