| | 1 | = Build Instructions = |
| | 2 | |
| | 3 | This page explains how to compile, configure, run and test the !EduBerza prototype. |
| | 4 | It is linked from [wiki:PrototypeImplementation]. |
| | 5 | |
| | 6 | == Development environment description == |
| | 7 | |
| | 8 | ||= Tool =||= Version tested =||= Needed for =|| |
| | 9 | || Go || 1.26.0 (`go.mod` asks for 1.25 or newer) || Building `server/` (the CLI) and `bots/` (the market bot). || |
| | 10 | || PostgreSQL || 16.3 (Docker container) || The database. Either the local Docker container or the faculty server. || |
| | 11 | || Docker + Docker Compose || any recent || Optional. Starts a local PostgreSQL with one command. || |
| | 12 | || `psql` || 16 || Optional. Only for running the SQL scripts by hand. || |
| | 13 | || Java || 21 (8+ works) || Optional. Only to open or edit the ER diagram in TerraER. || |
| | 14 | || DBeaver || any recent || Optional. Only to export `relational_schema.jpg`. || |
| | 15 | |
| | 16 | About the PostgreSQL version: `docker-compose.yml` uses the image `postgres` without a version |
| | 17 | tag. Docker therefore starts whatever version of the official image it has pulled. On the |
| | 18 | machine where the prototype was tested, that was PostgreSQL 16.3. The only extension the schema |
| | 19 | needs is `pgcrypto` (`CREATE EXTENSION IF NOT EXISTS pgcrypto`). It ships with PostgreSQL and is |
| | 20 | included in the official image. |
| | 21 | |
| | 22 | You do not need to install anything else. The only third-party Go library is the PostgreSQL |
| | 23 | driver `github.com/lib/pq`. `go build` downloads it automatically, at the version pinned in |
| | 24 | `go.mod` and `go.sum`. |
| | 25 | |
| | 26 | == Build instructions == |
| | 27 | |
| | 28 | Run all commands from the repository root. |
| | 29 | |
| | 30 | === 1. Configure the database connection === |
| | 31 | |
| | 32 | {{{ |
| | 33 | cp .env.example .env |
| | 34 | }}} |
| | 35 | |
| | 36 | The defaults in `.env.example` (`localhost:5433`, user `bp_project`, database `bp_database`) |
| | 37 | match the bundled Docker setup. To use the faculty database instead, edit `.env`, or pass the |
| | 38 | values as real environment variables. Real environment variables take precedence over the file: |
| | 39 | |
| | 40 | {{{ |
| | 41 | DBHOST=... DBPORT=5432 DBUSER=... DBPASSWORD=... DBNAME=... ./eduberza |
| | 42 | }}} |
| | 43 | |
| | 44 | `.env` is not committed on purpose (see `.gitignore`), because it holds a password. |
| | 45 | |
| | 46 | === 2. Start PostgreSQL === |
| | 47 | |
| | 48 | {{{ |
| | 49 | docker compose up -d |
| | 50 | }}} |
| | 51 | |
| | 52 | Skip this step if you use the faculty database. |
| | 53 | |
| | 54 | === 3. Build === |
| | 55 | |
| | 56 | {{{ |
| | 57 | go build -o eduberza ./server |
| | 58 | }}} |
| | 59 | |
| | 60 | === 4. Create the schema and load the sample data === |
| | 61 | |
| | 62 | {{{ |
| | 63 | ./eduberza -init |
| | 64 | }}} |
| | 65 | |
| | 66 | This runs `server/db/schema_creation.sql` and then `server/db/data_load.sql`. It logs |
| | 67 | `Running schema_creation.sql ...`, `Running data_load.sql ...` and `Database initialised.`, |
| | 68 | then prints: |
| | 69 | |
| | 70 | {{{ |
| | 71 | Schema initialised. Re-run without -init to start the CLI. |
| | 72 | }}} |
| | 73 | |
| | 74 | Both scripts are '''compiled into the binary''' (`go:embed`), so `-init` works from any |
| | 75 | directory. It is destructive and can be run again any number of times: it drops and recreates |
| | 76 | the whole `project` schema, so it also resets everything if a demo goes wrong. To reload only |
| | 77 | the data and keep the schema: |
| | 78 | |
| | 79 | {{{ |
| | 80 | ./eduberza -load-data # prints "Sample data reloaded." |
| | 81 | }}} |
| | 82 | |
| | 83 | If you prefer to watch the statements run, the same can be done with `psql`: |
| | 84 | |
| | 85 | {{{ |
| | 86 | psql "postgresql://$DBUSER:$DBPASSWORD@$DBHOST:$DBPORT/$DBNAME" \ |
| | 87 | -f server/db/schema_creation.sql |
| | 88 | psql "postgresql://$DBUSER:$DBPASSWORD@$DBHOST:$DBPORT/$DBNAME" \ |
| | 89 | -f server/db/data_load.sql |
| | 90 | }}} |
| | 91 | |
| | 92 | === 5. Run the prototype === |
| | 93 | |
| | 94 | {{{ |
| | 95 | ./eduberza |
| | 96 | }}} |
| | 97 | |
| | 98 | === 6. Optional: run the market simulation bot === |
| | 99 | |
| | 100 | In a second terminal, also from the repository root (the bot reads `.env` from the current |
| | 101 | directory): |
| | 102 | |
| | 103 | {{{ |
| | 104 | go run ./bots # add -interval 1s for faster ticks; the default is 3s |
| | 105 | }}} |
| | 106 | |
| | 107 | On every tick the bot moves the price of every active market by a small random step, inserts a |
| | 108 | row into `market_trades` and updates the current 1-minute candle. Prices in the CLI change |
| | 109 | while it runs, because the current price is always read from the most recent trade |
| | 110 | (`v_latest_prices`) and never from a stored column. Leave the bot off if you want the exact |
| | 111 | numbers in the tests below. |
| | 112 | |
| | 113 | === 7. Optional: richer data for the P6 reports === |
| | 114 | |
| | 115 | `data_load.sql` seeds only a few minutes of trade history. That is not enough for the |
| | 116 | top traders and market performance reports of [wiki:AdvancedReports] (menu `[10]` and `[11]`) |
| | 117 | to show more than one period. To see more interesting results, load five quarters of synthetic |
| | 118 | history on top: |
| | 119 | |
| | 120 | {{{ |
| | 121 | psql "postgresql://$DBUSER:$DBPASSWORD@$DBHOST:$DBPORT/$DBNAME" \ |
| | 122 | -f server/db/reports_demo_data.sql |
| | 123 | }}} |
| | 124 | |
| | 125 | This script is not part of `-init` or `-load-data` on purpose. The header of |
| | 126 | `reports_demo_data.sql`, shown below, explains why. Running it never changes the balances that |
| | 127 | the tests below check. |
| | 128 | |
| | 129 | {{{ |
| | 130 | -- reports_demo_data.sql |
| | 131 | -- EduBerza - optional historical data for the P6 reports |
| | 132 | -- Course: Databases 2025/2026 Winter, FINKI UKIM |
| | 133 | -- |
| | 134 | -- data_load.sql only seeds ~10 minutes of trade history, which is enough to |
| | 135 | -- demonstrate UC0001-UC0007 but not enough to show report_top_traders() or |
| | 136 | -- report_market_performance() doing anything interesting: everything falls |
| | 137 | -- into a single quarter, so "number of profitable periods" and "consistency" |
| | 138 | -- are trivial and "market return" has almost no history to work with. |
| | 139 | -- |
| | 140 | -- This script adds five quarters of synthetic transactions, market trades and |
| | 141 | -- executed orders on top of an already-loaded data_load.sql, spanning |
| | 142 | -- 2025-07 to 2026-07, so the two P6 reports have several periods and two |
| | 143 | -- markets with opposite price trends to actually compare. |
| | 144 | -- |
| | 145 | -- Deliberately NOT part of -init / -load-data: it only inserts into |
| | 146 | -- transactions, market_trades and orders, and does not touch |
| | 147 | -- users.available_balance/invested_balance or holdings, so it does not |
| | 148 | -- disturb the balances the other use cases' documented "verified run" |
| | 149 | -- sections depend on. Run it by hand, after data_load.sql, only to exercise |
| | 150 | -- the two reports: |
| | 151 | -- |
| | 152 | -- psql "$DATABASE_URL" -f server/db/schema_creation.sql |
| | 153 | -- psql "$DATABASE_URL" -f server/db/data_load.sql |
| | 154 | -- psql "$DATABASE_URL" -f server/db/reports_demo_data.sql |
| | 155 | -- |
| | 156 | -- Idempotent: deletes its own previously-inserted rows (tagged via |
| | 157 | -- description/source) before re-inserting. |
| | 158 | }}} |
| | 159 | |
| | 160 | == Testing instructions == |
| | 161 | |
| | 162 | === How to launch and log in === |
| | 163 | |
| | 164 | Start the prototype with `./eduberza` after steps 1–4. The sample data creates three test |
| | 165 | users. All of them have the password '''`test123`''': |
| | 166 | |
| | 167 | ||= Username =||= Starting state after `-init` =|| |
| | 168 | || `alice` || 8250.00 USD available (1750.00 invested), holds 0.5 ETH bought at 3500.00. Watchlist "Favorites": BTC, ETH, SOL. Best demo account. || |
| | 169 | || `bob` || 5000.00 USD available, no crypto. Watchlist "Bobs Picks": BTC, DOGE. || |
| | 170 | || `charlie` || 2500.00 USD available, no crypto, no watchlist yet. || |
| | 171 | |
| | 172 | The five sample markets are ADA, BTC, DOGE, ETH and SOL, all quoted in USD. Their starting |
| | 173 | last prices are 0.45375, 67140, 0.122, 3520 and 166.1. |
| | 174 | |
| | 175 | === Mini-guide to the application === |
| | 176 | |
| | 177 | You always answer with the number of a menu option. When you have to choose a market, a |
| | 178 | holding or a crypto, the prototype prints a numbered list and you type the number from that |
| | 179 | list. You never type an id or a symbol. A number that is not in the list is refused with |
| | 180 | `Invalid choice, enter a number from 1 to N.` |
| | 181 | |
| | 182 | '''Menu before login''' |
| | 183 | |
| | 184 | ||= Option =||= What it does and how to use it =|| |
| | 185 | || `[1] Register` || Enter a username, an e-mail (must contain `@`), your full name and a password (at least 6 characters). You get `Account created. You can now log in.`, or `Invalid email.`, `Password must be at least 6 characters.` or `Username or email already taken.` A new account starts with 0 USD. || |
| | 186 | || `[2] Login` || Enter your username and password. You get `Login successful.` and the second menu. A wrong password and an unknown username both give `Invalid credentials.` || |
| | 187 | || `[3] Browse markets` || Prints the numbered list of markets with their last price. || |
| | 188 | || `[0] Exit` || Ends the program. || |
| | 189 | |
| | 190 | '''Menu after login''' (headed `--- Logged in as <username> ---`) |
| | 191 | |
| | 192 | ||= Option =||= What it does and how to use it =|| |
| | 193 | || `[1] View balance` || Shows the available, invested and total USD. || |
| | 194 | || `[2] Deposit virtual funds` || Enter an amount in USD. It must be a positive number, otherwise you get `Invalid amount.` You get `Deposited 500.0000 USD.` || |
| | 195 | || `[3] Browse markets` || Same list as before login. || |
| | 196 | || `[4] Place market BUY order` || Lists all markets, numbered, with their last price. Type the number at `Market #:`. The prototype shows the latest price. Type the quantity. You get `Order executed: buy …` or `Insufficient funds: need …, have …`. || |
| | 197 | || `[5] Place market SELL order` || Lists only the cryptos you hold, numbered, with columns `Held` and `Free to sell`. Type the number at `Holding #:`, then the quantity. You get `Order executed: sell …` or `Insufficient holding: …`. If you hold nothing, you get `you hold no crypto that is free to sell`. || |
| | 198 | || `[6] View portfolio` || One row per crypto you hold: quantity, reserved, available, average buy price, current price, value and unrealised P/L. Then your cash, portfolio value and net worth. || |
| | 199 | || `[7] View transaction history` || Your last 20 ledger entries (deposits, buys, sells), newest first. || |
| | 200 | || `[8] Manage watchlist` || Opens a submenu: `[1] List items` shows your watchlist with last prices. `[2] Add crypto` lists, numbered, the cryptos not on it yet; type a number. `[3] Remove crypto` lists, numbered, the cryptos on it; type a number. `[0] Back` returns. A user without a watchlist gets one named "Favorites" the first time. || |
| | 201 | || `[9] Logout` || Back to the first menu. || |
| | 202 | || `[10] Report: top traders` || P6 report. Enter a start date (inclusive) and an end date (exclusive) as `YYYY-MM-DD`. || |
| | 203 | || `[11] Report: market performance` || P6 report, with the same two dates. || |
| | 204 | || `[0] Exit` || Ends the program. || |
| | 205 | |
| | 206 | === End-to-end smoke test === |
| | 207 | |
| | 208 | These values were checked on 2026-09-24 against freshly loaded sample data (PostgreSQL 16.3), |
| | 209 | with the bot not running. The expected values are exact. |
| | 210 | |
| | 211 | 1. `./eduberza -init` prints `Schema initialised. Re-run without -init to start the CLI.` |
| | 212 | 2. `./eduberza`, then `2` (Login), then `alice` / `test123` gives `Login successful.` |
| | 213 | 3. `6` (View portfolio) shows one row: `ETH`, quantity 0.5000, reserved 0.0000, available |
| | 214 | 0.5000, average buy 3500.000000, current 3520.000000, value 1760.0000, unrealised P/L |
| | 215 | `+10.0000`. Cash available is 8250.0000 and net worth is 10010.0000. |
| | 216 | 4. `4` (BUY). The market list shows `1 ADA`, `2 BTC`, `3 DOGE`, `4 ETH`, `5 SOL`. Type `2` at |
| | 217 | `Market #:`, then `0.01` at `Quantity:`. The result is |
| | 218 | `Order executed: buy 0.0100 BTC @ 67140.000000 (notional 671.4000 USD)`. |
| | 219 | 5. `6` (View portfolio) now shows BTC ''and'' ETH, with total value 2431.4000, cash 7578.6000 |
| | 220 | (= 8250.00 − 671.40), and net worth still 10010.0000. |
| | 221 | 6. `5` (SELL). The holdings list shows `1 BTC` (held 0.0100) and `2 ETH` (held 0.5000). Type |
| | 222 | `2` at `Holding #:`, then `0.5`. The result is |
| | 223 | `Order executed: sell 0.5000 ETH @ 3520.000000 (notional 1760.0000 USD)`. |
| | 224 | 7. `7` (View transaction history) lists, newest first: the `sell` (+1760.0000), the `buy` of |
| | 225 | BTC (−671.4000), then the two rows from the sample data, which have the same timestamp: |
| | 226 | `deposit` 10000.0000 "Initial virtual deposit" and `buy` −1750.0000 "Market buy 0.5 ETH @ |
| | 227 | 3500.00". |
| | 228 | 8. `8` (Manage watchlist), then `1` (List items), shows alice's watchlist with BTC, ETH and SOL |
| | 229 | and their last prices. Then `2` (Add crypto) lists `1 ADA` and `2 DOGE`; type `1` and you get |
| | 230 | `Added ADA.` Then `0` (Back). |
| | 231 | 9. `9` (Logout), then `0` (Exit). |
| | 232 | |
| | 233 | === Testing the failure paths === |
| | 234 | |
| | 235 | These matter more than the happy path, because they prove that the transactions really roll |
| | 236 | back and that invalid choices are refused: |
| | 237 | |
| | 238 | * '''Insufficient funds:''' log in as `charlie` (2500 USD). Choose `4`, market `2` (BTC), |
| | 239 | quantity `1`. Expect `Insufficient funds: need 67140.0000, have 2500.0000` and ''no'' change to |
| | 240 | any table: no order row, no ledger entry, no holding. |
| | 241 | * '''Insufficient holding:''' on fresh data (`./eduberza -load-data`), log in as `alice`. Choose |
| | 242 | `5`; the list shows only `1 ETH` (held 0.5000, free 0.5000). Choose `1`, quantity `5`. Expect |
| | 243 | `Insufficient holding: trying to sell 5.0000, available 0.5000 (of 0.5000 held, 0.0000 reserved)`. |
| | 244 | * '''Nothing to sell:''' as `bob` (no crypto), choose `5`. The holdings list is empty, and you |
| | 245 | get `you hold no crypto that is free to sell` without being asked for a number. |
| | 246 | * '''Invalid choice from a list:''' in any list (for example `8`, then `3` Remove crypto), type a |
| | 247 | number larger than the list. Expect `Invalid choice, enter a number from 1 to N.` |
| | 248 | * '''Invalid deposit:''' choose `2` and enter `-50`. Expect `Invalid amount.` |
| | 249 | * '''Duplicate registration:''' register with username `alice`. Expect |
| | 250 | `Username or email already taken.` |
| | 251 | * '''Invalid e-mail:''' register with an e-mail without `@`. Expect `Invalid email.` |
| | 252 | * '''Wrong password:''' log in as `alice` with any wrong password. Expect |
| | 253 | `Invalid credentials.` An unknown username gives the same message, so the prototype does not |
| | 254 | reveal which accounts exist. |
| | 255 | |
| | 256 | The concurrency guarantee of the sell path (two processes selling the same crypto at the same |
| | 257 | moment) cannot be reproduced by typing into two terminals, because each order commits within |
| | 258 | milliseconds. It is described in [wiki:UseCase0005Implementation]. |
| | 259 | |
| | 260 | === For the public presentation === |
| | 261 | |
| | 262 | Demo with `alice`. She already has a position, so the portfolio screen is not empty. Register a |
| | 263 | brand-new account live to show UC0001. Run the bot in a background terminal so the prices |
| | 264 | visibly move between two portfolio refreshes. |
| | 265 | |
| | 266 | == Editing the ER diagram == |
| | 267 | |
| | 268 | TerraER is a third-party tool and is '''not''' committed to this repository on purpose. Download |
| | 269 | the teacher's build from `https://bazi.finki.ukim.mk/resources/Software/` and run it: |
| | 270 | |
| | 271 | {{{ |
| | 272 | java -jar TerraER3.11.jar # then File → Open → docs/P1-ConceptualModel/ERModel_v03.xml |
| | 273 | }}} |
| | 274 | |
| | 275 | The current version is `ERModel_v03.xml`. Save new versions as `ERModel_v04.xml` and so on, |
| | 276 | and export a matching PNG for each. TerraER does not add the extension itself: type `.xml` |
| | 277 | yourself, or the file will not reopen. |
| | 278 | |
| | 279 | == Up-to-date source code == |
| | 280 | |
| | 281 | The repository is pushed to the FINKI DEVELOP git server. The clone URL and credentials are in |
| | 282 | the Repositories section in EPRMS. |
| | 283 | |
| | 284 | === About the source code === |
| | 285 | |
| | 286 | * All the source needed to run the prototype is in this repository: the CLI (`server/`), the |
| | 287 | market bot (`bots/`), the DDL script and the sample-data script (`server/db/`). |
| | 288 | * Third-party Go libraries are '''not''' vendored. `go build` downloads `github.com/lib/pq` at |
| | 289 | the versions pinned in `go.mod` and `go.sum`. |
| | 290 | * Third-party executables are '''not''' committed. `.gitignore` excludes `*.jar`, and TerraER is |
| | 291 | downloaded from the URL above. |
| | 292 | * No third-party images, styles or frameworks are used. The prototype has no images at all; |
| | 293 | the interface is text. |