new readme

This commit is contained in:
samantha42
2026-03-24 12:49:55 +01:00
parent 1a9fe3adc7
commit a4c5c4cb1d

252
README.md
View File

@@ -1,36 +1,45 @@
# Portfolio Engine # Portfolio Engine
A lightweight portfolio tracking backend written in Go. Replaces spreadsheets with a proper database, REST API, and an interactive shell — all in a single binary. A lightweight portfolio and financial data backend written in Go. Tracks companies, currencies, and revenue reports across configurable time periods — replacing spreadsheets with a proper database, REST API, and interactive shell.
## Features ## Features
- SQLite database with foreign key enforcement - SQLite database with foreign key enforcement
- REST API on port `8080` - REST API on port `8080`
- Interactive shell for managing data without an HTTP client - Interactive shell for managing data without an HTTP client
- Tracks companies with share count, price, and currency - Revenue tracking by period (quarterly, half-year, full year)
- Revenue broken down by custom categories (product, location, segment...)
- Health endpoint with DB latency, memory, and uptime stats
## Project Structure ## Project Structure
``` ```
. .
├── main.go # Entry point, HTTP routes, shell loop ├── main.go # Entry point HTTP routes + shell loop
├── app.db # SQLite database (auto-created on first run) ├── app.db # SQLite database (auto-created on first run)
├── build.sh # Build script ├── build.sh # Build script
├── go.mod / go.sum ├── go.mod / go.sum
└── internal/ └── internal/
├── database/ ├── database/
│ └── main.go # Schema init (InitDB) │ └── main.go # Schema init (InitDB) — all CREATE TABLE statements
├── handlers/ ├── handlers/
── main.go # HTTP handlers (HealthHandler, AddCompanyHandler) ── main.go # HealthHandler
│ ├── currency.go # AddCurrencyHandler, GetCurrenciesHandler
│ └── revenue.go # AddRevenueEntryHandler, GetRevenueReportHandler, GetRevenueSumHandler
├── model/ ├── model/
── company.go # Structs + SQL queries (Company, Currency, CompanyInput) ── company.go # Company struct + SQL (InsertCompany, GetAllCompanies)
│ ├── currency.go # Currency struct + SQL (InsertCurrency, GetAllCurrencies)
│ ├── periode.go # Period struct + helpers (QuarterPeriod, HalfYearPeriod, FullYearPeriod)
│ └── revenue.go # Revenue, RevenueReport, RevSum structs + SQL
├── service/ ├── service/
│ ├── main.go # Service wiring
│ ├── company.go # Company business logic │ ├── company.go # Company business logic
│ ├── currency.go # Currency business logic │ ├── currency.go # Currency business logic
│ └── main.go # Service setup │ └── revenue.go # Revenue aggregation logic
└── shell/ └── shell/
├── company.go # Shell commands for companies ├── company.go # add-company, list-companies
── currency.go # Shell commands for currencies ── currency.go # add-currency, list-currency
└── revenue.go # add-revenue, list-revenue, sum-revenue
``` ```
## Getting Started ## Getting Started
@@ -51,7 +60,7 @@ go mod download
go run main.go go run main.go
``` ```
Or use the build script: Or build first:
```bash ```bash
chmod +x build.sh chmod +x build.sh
@@ -59,23 +68,26 @@ chmod +x build.sh
./Portifolio ./Portifolio
``` ```
On startup you'll see: On startup:
``` ```
Connected to SQLite database Connected to SQLite database
Tables ready
Server running on :8080 Server running on :8080
Shell ready. Commands: add-company, help, exit Shell ready. Type 'help' for commands.
> >
``` ```
The HTTP server and the shell run concurrently — the API is live while you type shell commands. The HTTP server and shell run concurrently — the API is live while you type shell commands.
---
## REST API ## REST API
### `GET /health` ### `GET /health`
Returns the status of the server and database connection. Returns server status, DB connection info, memory usage, and uptime.
```bash ```bash
curl http://localhost:8080/health curl http://localhost:8080/health
@@ -84,44 +96,126 @@ curl http://localhost:8080/health
```json ```json
{ {
"status": "ok", "status": "ok",
"database": "ok" "uptime": "4m32s",
"database": {
"status": "ok",
"latency": "121µs",
"open_connections": 1,
"in_use": 0,
"idle": 1
},
"memory": {
"alloc_mb": 2.31,
"sys_mb": 9.44,
"num_gc": 3
},
"go_version": "go1.22.0",
"goroutines": 4
} }
``` ```
Returns `503` if the database is unreachable. Returns `503` with `"status": "degraded"` if the database is unreachable.
--- ---
### `POST /add/company` ### `POST /add/company`
Add a new company. The `currency_id` must reference an existing currency in the database.
```bash ```bash
curl -X POST http://localhost:8080/add/company \ curl -X POST http://localhost:8080/add/company \
-H "Content-Type: application/json" \
-d '{"name":"Novo Nordisk","shares_outstanding":4442064180,"price":251.00,"currency_id":1}'
```
---
### `GET /companies`
```bash
curl http://localhost:8080/companies
```
---
### `POST /add/currency`
```bash
curl -X POST http://localhost:8080/add/currency \
-H "Content-Type: application/json" \
-d '{"code":"DKK","name":"Danish Krone"}'
```
---
### `GET /currencies`
```bash
curl http://localhost:8080/currencies
```
---
### `POST /add/revenue/entry`
Add a single revenue line to a report. The period is created automatically if it doesn't exist.
```bash
curl -X POST http://localhost:8080/add/revenue/entry \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"name": "Novo Nordisk", "company_id": 1,
"shares_outstanding": 4442064180, "currency_id": 1,
"price": 251.00, "period_type": "Q",
"currency_id": 1 "year": 2025,
"index": 1,
"category": "product",
"label": "iPhone",
"value": 69143
}' }'
``` ```
```json `period_type` is one of:
{ "status": "created" }
| Value | Meaning | `index` range |
|-------|-----------|---------------|
| `Q` | Quarter | 14 |
| `H` | Half-year | 12 |
| `Y` | Full year | 1 |
---
### `GET /revenue/report`
Get all revenue entries for a company and period.
```bash
curl "http://localhost:8080/revenue/report?company_id=1&period_type=Q&year=2025&index=1"
``` ```
---
### `GET /revenue/sum`
Sum all revenue entries for a company across all periods of a given type in a year.
```bash
curl "http://localhost:8080/revenue/sum?company_id=1&period_type=Q&year=2025"
```
---
## Shell Commands ## Shell Commands
The shell starts automatically after the server and accepts commands interactively.
| Command | Description | | Command | Description |
|------------------|--------------------------------------| |------------------|-----------------------------------------------|
| `add-company` | Add a new company interactively |
| `list-companies` | List all companies with price and share count |
| `add-currency` | Add a new currency interactively | | `add-currency` | Add a new currency interactively |
| `list-currency` | List all currencies with their IDs | | `list-currency` | List all currencies with their IDs |
| `add-company` | Add a new company interactively | | `add-revenue` | Add a revenue entry interactively |
| `help` | Show available commands | | `list-revenue` | List revenue entries for a company/period |
| `exit` | Quit the application | | `sum-revenue` | Sum revenue across all periods in a year |
| `help` | Show all available commands |
| `exit` | Quit |
### Example session ### Example session
@@ -131,19 +225,46 @@ The shell starts automatically after the server and accepts commands interactive
Name (e.g. Danish Krone): Danish Krone Name (e.g. Danish Krone): Danish Krone
✓ Currency 'Danish Krone' (DKK) added with ID 1 ✓ Currency 'Danish Krone' (DKK) added with ID 1
> list-currency
ID CODE NAME
------------------------------
1 DKK Danish Krone
> add-company > add-company
Name: Maersk Name: Novo Nordisk
Shares outstanding: 15224309 Shares outstanding: 4442064180
Price: 15270.00 Price: 251.00
Currency ID: 1 Currency ID: 1
✓ Company 'Maersk' added. ✓ Company 'Novo Nordisk' added.
> add-revenue
Company ID: 1
Currency ID: 1
Period type (Q/H/Y): Q
Year: 2025
Index (Q: 1-4 | H: 1-2 | Y: 1): 1
Category (product/location/total): product
Label (e.g. iPhone, Americas): Diabetes Care
Value: 54200
✓ Revenue entry added: product / Diabetes Care = 54200.00 (Q1 2025)
> sum-revenue
Company ID: 1
Period type to sum (Q/H/Y): Q
Year: 2025
Revenue Sum — FY2025
Total: 54200.00
By Category:
product 54200.00
By Label:
Diabetes Care 54200.00
> list-companies
ID NAME CURRENCY PRICE SHARES
------------------------------------------------------------
1 Novo Nordisk DKK 251.00 4442064180
``` ```
---
## Database Schema ## Database Schema
```sql ```sql
@@ -161,13 +282,60 @@ CREATE TABLE companies (
currency_id INTEGER NOT NULL, currency_id INTEGER NOT NULL,
FOREIGN KEY (currency_id) REFERENCES currencies(id) FOREIGN KEY (currency_id) REFERENCES currencies(id)
); );
CREATE TABLE periods (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('Q', 'H', 'Y')),
year INTEGER NOT NULL,
idx INTEGER NOT NULL,
start_date TEXT NOT NULL,
end_date TEXT NOT NULL,
UNIQUE(type, year, idx)
);
CREATE TABLE revenue_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL,
period_id INTEGER NOT NULL,
FOREIGN KEY (company_id) REFERENCES companies(id),
FOREIGN KEY (period_id) REFERENCES periods(id),
UNIQUE(company_id, period_id)
);
CREATE TABLE revenue_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_id INTEGER NOT NULL,
currency_id INTEGER NOT NULL,
category TEXT NOT NULL,
label TEXT NOT NULL,
value REAL NOT NULL,
FOREIGN KEY (report_id) REFERENCES revenue_reports(id),
FOREIGN KEY (currency_id) REFERENCES currencies(id)
);
CREATE TABLE category_defs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (company_id) REFERENCES companies(id),
UNIQUE(company_id, name)
);
CREATE TABLE category_labels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_def_id INTEGER NOT NULL,
label TEXT NOT NULL,
FOREIGN KEY (category_def_id) REFERENCES category_defs(id),
UNIQUE(category_def_id, label)
);
``` ```
---
## Roadmap ## Roadmap
- `GET /companies` — list all companies - `GET /company/{id}/revenue` — full revenue history for a company
- `GET /currencies` — list all currencies
- Price update endpoint - Price update endpoint
- Market cap calculation (price × shares) - Market cap calculation (price × shares outstanding)
- Multi-currency conversion - Multi-currency conversion
- Frontend dashboard - Frontend dashboard