# Portfolio Engine 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 - SQLite database with foreign key enforcement - REST API on port `8080` - Interactive shell for managing data without an HTTP client - 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 ``` . ├── main.go # Entry point — HTTP routes + shell loop ├── app.db # SQLite database (auto-created on first run) ├── build.sh # Build script ├── go.mod / go.sum └── internal/ ├── database/ │ └── main.go # Schema init (InitDB) — all CREATE TABLE statements ├── handlers/ │ ├── main.go # HealthHandler │ ├── currency.go # AddCurrencyHandler, GetCurrenciesHandler │ └── revenue.go # AddRevenueEntryHandler, GetRevenueReportHandler, GetRevenueSumHandler ├── model/ │ ├── 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/ │ ├── main.go # Service wiring │ ├── company.go # Company business logic │ ├── currency.go # Currency business logic │ └── revenue.go # Revenue aggregation logic └── shell/ ├── company.go # add-company, list-companies ├── currency.go # add-currency, list-currency └── revenue.go # add-revenue, list-revenue, sum-revenue ``` ## Getting Started ### Prerequisites - Go 1.21+ - GCC (required for SQLite CGO bindings) - Ubuntu/Debian: `sudo apt install gcc` - macOS: comes with Xcode Command Line Tools ### Install & Run ```bash git clone git@git.samantha42.xyz:samantha/Portifolio-Engine.git cd Portifolio-Engine go mod download go run main.go ``` Or build first: ```bash chmod +x build.sh ./build.sh ./Portifolio ``` On startup: ``` Connected to SQLite database Tables ready Server running on :8080 Shell ready. Type 'help' for commands. > ``` The HTTP server and shell run concurrently — the API is live while you type shell commands. --- ## REST API ### `GET /health` Returns server status, DB connection info, memory usage, and uptime. ```bash curl http://localhost:8080/health ``` ```json { "status": "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` with `"status": "degraded"` if the database is unreachable. --- ### `POST /add/company` ```bash 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" \ -d '{ "company_id": 1, "currency_id": 1, "period_type": "Q", "year": 2025, "index": 1, "category": "product", "label": "iPhone", "value": 69143 }' ``` `period_type` is one of: | Value | Meaning | `index` range | |-------|-----------|---------------| | `Q` | Quarter | 1–4 | | `H` | Half-year | 1–2 | | `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 | 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 | | `list-currency` | List all currencies with their IDs | | `add-revenue` | Add a revenue entry interactively | | `list-revenue` | List revenue entries for a company/period | | `sum-revenue` | Sum revenue across all periods in a year | | `help` | Show all available commands | | `exit` | Quit | ### Example session ``` > add-currency Code (e.g. DKK): DKK Name (e.g. Danish Krone): Danish Krone ✓ Currency 'Danish Krone' (DKK) added with ID 1 > add-company Name: Novo Nordisk Shares outstanding: 4442064180 Price: 251.00 Currency ID: 1 ✓ 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 ```sql CREATE TABLE currencies ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL UNIQUE, name TEXT NOT NULL ); CREATE TABLE companies ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, shares_outstanding INTEGER NOT NULL, price REAL NOT NULL, currency_id INTEGER NOT NULL, 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 - `GET /company/{id}/revenue` — full revenue history for a company - Price update endpoint - Market cap calculation (price × shares outstanding) - Multi-currency conversion - Frontend dashboard