| 1 | # Use-case 0007 Implementation - Manage watchlist
|
|---|
| 2 |
|
|---|
| 3 | **Initiating actor:** Trader
|
|---|
| 4 |
|
|---|
| 5 | **Other actors:** —
|
|---|
| 6 |
|
|---|
| 7 | A logged-in Trader keeps a list of crypto assets they want to monitor, with the last
|
|---|
| 8 | price of each. The first time the watchlist is opened the system creates a default
|
|---|
| 9 | watchlist named "Favorites" for the Trader. From a sub-menu the Trader can list the
|
|---|
| 10 | watchlist, add a crypto or remove one. The Trader never types a symbol: for adding, the
|
|---|
| 11 | system lists, numbered, only the cryptos that are not on the watchlist yet, and for
|
|---|
| 12 | removing, only the cryptos that are on it; the Trader picks one by its number. Adding
|
|---|
| 13 | a crypto that is already on the list is a no-op (idempotent), and a number that is not
|
|---|
| 14 | in the list is refused without touching the database.
|
|---|
| 15 |
|
|---|
| 16 | Original use-case description (P3): [UseCase0007](../P3-UseCaseModel/UseCase0007.md).
|
|---|
| 17 | Implementation: [`server/watchlist.go`](../../server/watchlist.go), functions
|
|---|
| 18 | `ManageWatchlist`, `ensureDefaultWatchlist`, `listWatchlist`, `addToWatchlist` and
|
|---|
| 19 | `removeFromWatchlist`, with `pickNumber` from
|
|---|
| 20 | [`server/market.go`](../../server/market.go).
|
|---|
| 21 |
|
|---|
| 22 | All statements run on the `project` schema (the connection sets
|
|---|
| 23 | `search_path=project,public` in `server/db/db.go`). The SQL below is copied from the
|
|---|
| 24 | Go code; only the Go source indentation is removed.
|
|---|
| 25 |
|
|---|
| 26 | The run shown is user `alice` on the seed data, whose watchlist contains BTC, ETH and
|
|---|
| 27 | SOL. She lists it, adds ADA, tries to remove a number that is not in the list, removes
|
|---|
| 28 | SOL and lists the result.
|
|---|
| 29 |
|
|---|
| 30 | ## Scenario
|
|---|
| 31 |
|
|---|
| 32 | 1. **Trader** chooses `[8] Manage watchlist` in the authenticated menu (types `8`).
|
|---|
| 33 | 2. **System** makes sure the Trader has a watchlist and takes the id of the oldest one
|
|---|
| 34 | (`ensureDefaultWatchlist`; `$1` = the logged-in user's id):
|
|---|
| 35 |
|
|---|
| 36 | ```sql
|
|---|
| 37 | SELECT id FROM watchlists WHERE user_id = $1 ORDER BY created_at LIMIT 1
|
|---|
| 38 | ```
|
|---|
| 39 |
|
|---|
| 40 | Only if this returns no row, it creates the default watchlist and uses its id:
|
|---|
| 41 |
|
|---|
| 42 | ```sql
|
|---|
| 43 | INSERT INTO watchlists (user_id, name) VALUES ($1, 'Favorites') RETURNING id
|
|---|
| 44 | ```
|
|---|
| 45 |
|
|---|
| 46 | (alice already has the seed watchlist "Favorites", so only the `SELECT` runs.) The
|
|---|
| 47 | watchlist id is kept in Go and used as `$1` in all statements below.
|
|---|
| 48 | 3. **System** shows the sub-menu `-- Watchlist --` with `[1] List items`,
|
|---|
| 49 | `[2] Add crypto`, `[3] Remove crypto` and `[0] Back`.
|
|---|
| 50 |
|
|---|
| 51 | 
|
|---|
| 52 |
|
|---|
| 53 | ### List items
|
|---|
| 54 |
|
|---|
| 55 | 4. **Trader** chooses `[1] List items`.
|
|---|
| 56 | 5. **System** lists the cryptos on the watchlist with their last price against USD
|
|---|
| 57 | (`listWatchlist`; `$1` = watchlist id):
|
|---|
| 58 |
|
|---|
| 59 | ```sql
|
|---|
| 60 | SELECT c.symbol, c.name, COALESCE(lp.price, 0)
|
|---|
| 61 | FROM watchlist_items wi
|
|---|
| 62 | JOIN crypto c ON c.id = wi.crypto_id
|
|---|
| 63 | LEFT JOIN markets m ON m.crypto_id = c.id AND m.quote_currency = 'USD'
|
|---|
| 64 | LEFT JOIN v_latest_prices lp ON lp.market_id = m.id
|
|---|
| 65 | WHERE wi.watchlist_id = $1
|
|---|
| 66 | ORDER BY c.symbol
|
|---|
| 67 | ```
|
|---|
| 68 |
|
|---|
| 69 | For alice it prints `BTC Bitcoin 67140.000000`, `ETH Ethereum 3520.000000` and
|
|---|
| 70 | `SOL Solana 166.100000` (an empty watchlist prints `(watchlist is empty)`), then
|
|---|
| 71 | shows the sub-menu again.
|
|---|
| 72 |
|
|---|
| 73 | 
|
|---|
| 74 |
|
|---|
| 75 | ### Add a crypto
|
|---|
| 76 |
|
|---|
| 77 | 6. **Trader** chooses `[2] Add crypto`.
|
|---|
| 78 | 7. **System** lists, numbered, the cryptos that are not on the watchlist yet
|
|---|
| 79 | (`addToWatchlist`; `$1` = watchlist id):
|
|---|
| 80 |
|
|---|
| 81 | ```sql
|
|---|
| 82 | SELECT c.id, c.symbol, c.name
|
|---|
| 83 | FROM crypto c
|
|---|
| 84 | WHERE NOT EXISTS (SELECT 1 FROM watchlist_items wi
|
|---|
| 85 | WHERE wi.watchlist_id = $1 AND wi.crypto_id = c.id)
|
|---|
| 86 | ORDER BY c.symbol
|
|---|
| 87 | ```
|
|---|
| 88 |
|
|---|
| 89 | For alice it prints `1 ADA Cardano` and `2 DOGE Dogecoin` and asks
|
|---|
| 90 | `Crypto # to add:`. Go keeps each row's crypto id in memory. (If every crypto is
|
|---|
| 91 | already on the watchlist, it prints `Every crypto is already on your watchlist.`
|
|---|
| 92 | instead.)
|
|---|
| 93 |
|
|---|
| 94 | 
|
|---|
| 95 |
|
|---|
| 96 | 8. **Trader** picks the crypto by its number in the list: `1` (ADA).
|
|---|
| 97 | 9. **System** adds the crypto of row 1 to the watchlist (`$1` = watchlist id,
|
|---|
| 98 | `$2` = the chosen crypto's id); thanks to the unique constraint and
|
|---|
| 99 | `ON CONFLICT ... DO NOTHING`, adding a crypto that is already there changes
|
|---|
| 100 | nothing:
|
|---|
| 101 |
|
|---|
| 102 | ```sql
|
|---|
| 103 | INSERT INTO watchlist_items (watchlist_id, crypto_id)
|
|---|
| 104 | VALUES ($1, $2)
|
|---|
| 105 | ON CONFLICT (watchlist_id, crypto_id) DO NOTHING
|
|---|
| 106 | ```
|
|---|
| 107 |
|
|---|
| 108 | It prints `Added ADA.` and shows the sub-menu again.
|
|---|
| 109 |
|
|---|
| 110 | 
|
|---|
| 111 |
|
|---|
| 112 | ### Remove a crypto
|
|---|
| 113 |
|
|---|
| 114 | 10. **Trader** chooses `[3] Remove crypto`.
|
|---|
| 115 | 11. **System** lists, numbered, the cryptos that are on the watchlist
|
|---|
| 116 | (`removeFromWatchlist`; `$1` = watchlist id):
|
|---|
| 117 |
|
|---|
| 118 | ```sql
|
|---|
| 119 | SELECT c.id, c.symbol, c.name
|
|---|
| 120 | FROM watchlist_items wi
|
|---|
| 121 | JOIN crypto c ON c.id = wi.crypto_id
|
|---|
| 122 | WHERE wi.watchlist_id = $1
|
|---|
| 123 | ORDER BY c.symbol
|
|---|
| 124 | ```
|
|---|
| 125 |
|
|---|
| 126 | For alice it now prints `1 ADA Cardano`, `2 BTC Bitcoin`, `3 ETH Ethereum` and
|
|---|
| 127 | `4 SOL Solana` and asks `Crypto # to remove:`. Go keeps each row's crypto id in
|
|---|
| 128 | memory. (If the watchlist is empty, it prints `Your watchlist is empty.` instead.)
|
|---|
| 129 |
|
|---|
| 130 | 
|
|---|
| 131 |
|
|---|
| 132 | 12. **Trader** picks the crypto by its number in the list: `4` (SOL).
|
|---|
| 133 | 13. **System** removes the crypto of row 4 from the watchlist (`$1` = watchlist id,
|
|---|
| 134 | `$2` = the chosen crypto's id):
|
|---|
| 135 |
|
|---|
| 136 | ```sql
|
|---|
| 137 | DELETE FROM watchlist_items WHERE watchlist_id = $1 AND crypto_id = $2
|
|---|
| 138 | ```
|
|---|
| 139 |
|
|---|
| 140 | It prints `Removed SOL.` and shows the sub-menu again.
|
|---|
| 141 |
|
|---|
| 142 | 
|
|---|
| 143 |
|
|---|
| 144 | #### Alternate flow 12a — number not in the list
|
|---|
| 145 |
|
|---|
| 146 | Before removing SOL, alice first chose `[3] Remove crypto` and, at step 12, entered `5`
|
|---|
| 147 | while only numbers 1–4 were listed. `pickNumber` prints
|
|---|
| 148 | `Invalid choice, enter a number from 1 to 4.`, the `DELETE` is not run and the sub-menu
|
|---|
| 149 | is shown again; she then chose `[3]` once more, which returned the scenario to step 11.
|
|---|
| 150 | The same check applies to the number entered at step 8.
|
|---|
| 151 |
|
|---|
| 152 | 
|
|---|
| 153 |
|
|---|
| 154 | ### Verification — list after the changes
|
|---|
| 155 |
|
|---|
| 156 | Choosing `[1] List items` again runs the query from step 5, which now returns
|
|---|
| 157 | `ADA Cardano 0.453750`, `BTC Bitcoin 67140.000000` and `ETH Ethereum 3520.000000`:
|
|---|
| 158 | ADA was added and SOL removed. `[0] Back` returns to the authenticated menu.
|
|---|
| 159 |
|
|---|
| 160 | 
|
|---|