| 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_diagram_v4.png`. ||
|
|---|
| 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_v05.xml
|
|---|
| 273 | }}}
|
|---|
| 274 |
|
|---|
| 275 | The current version is `ERModel_v05.xml`. Save new versions as `ERModel_v06.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.
|
|---|