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