diff --git a/Portifolio b/Portifolio index 21c048b..88c07f3 100755 Binary files a/Portifolio and b/Portifolio differ diff --git a/app.db b/app.db index 42cdb24..d5dd44d 100644 Binary files a/app.db and b/app.db differ diff --git a/internal/database/main.go b/internal/database/main.go index dce51b9..4942c8a 100644 --- a/internal/database/main.go +++ b/internal/database/main.go @@ -53,7 +53,28 @@ func InitDB(db *sql.DB) { value REAL NOT NULL, FOREIGN KEY (report_id) REFERENCES revenue_reports(id), FOREIGN KEY (currency_id) REFERENCES currencies(id) - );` + ); + + -- one row per category type per company + -- e.g. Apple has "product" and "location" + CREATE TABLE IF NOT EXISTS 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) + ); + + -- the actual labels belonging to each category + -- e.g. category_def "product" → "iPhone", "Mac", "Services" + CREATE TABLE IF NOT EXISTS 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) + ); + ` if _, err := db.Exec(schema); err != nil { log.Fatal("Failed to create tables:", err) diff --git a/internal/handlers/currency.go b/internal/handlers/currency.go new file mode 100644 index 0000000..9a179ce --- /dev/null +++ b/internal/handlers/currency.go @@ -0,0 +1,44 @@ +package handlers + +import ( + "Portifolio/internal/model" + "Portifolio/internal/service" + "database/sql" + "encoding/json" + "net/http" + + _ "github.com/mattn/go-sqlite3" +) + +func AddCurrencyHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var input model.CurrencyInput + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + http.Error(w, "invalid json", http.StatusBadRequest) + return + } + + id, err := service.InsertCurrency(db, input) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]any{"status": "created", "id": id}) + } +} + +func GetCurrenciesHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + currencies, err := service.GetAllCurrencies(db) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(currencies) + } +} diff --git a/internal/handlers/main.go b/internal/handlers/main.go index c8ae945..4162c3e 100644 --- a/internal/handlers/main.go +++ b/internal/handlers/main.go @@ -6,47 +6,104 @@ import ( "database/sql" "encoding/json" "net/http" + "runtime" + "time" _ "github.com/mattn/go-sqlite3" ) +var startTime = time.Now() + func HealthHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - dbStatus := "ok" - if err := db.Ping(); err != nil { - dbStatus = "error: " + err.Error() + health := checkHealth(db) + + if health["status"] != "ok" { w.WriteHeader(http.StatusServiceUnavailable) } - json.NewEncoder(w).Encode(map[string]string{ - "status": "ok", - "database": dbStatus, - }) + json.NewEncoder(w).Encode(health) } } +func checkHealth(db *sql.DB) map[string]any { + // database + dbStatus := "ok" + dbLatency := "" + t := time.Now() + if err := db.Ping(); err != nil { + dbStatus = "error: " + err.Error() + } else { + dbLatency = time.Since(t).String() + } + + // db pool stats + stats := db.Stats() + + // overall status + status := "ok" + if dbStatus != "ok" { + status = "degraded" + } + + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + + return map[string]any{ + "status": status, + "uptime": time.Since(startTime).String(), + "database": map[string]any{ + "status": dbStatus, + "latency": dbLatency, + "open_connections": stats.OpenConnections, + "in_use": stats.InUse, + "idle": stats.Idle, + }, + "memory": map[string]any{ + "alloc_mb": bToMb(mem.Alloc), + "sys_mb": bToMb(mem.Sys), + "num_gc": mem.NumGC, + }, + "go_version": runtime.Version(), + "goroutines": runtime.NumGoroutine(), + } +} + +func bToMb(b uint64) float64 { + return float64(b) / 1024 / 1024 +} + func AddCompanyHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - var input model.CompanyInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } - if err := service.AddCompany(input, db); err != nil { + id, err := service.InsertCompany(db, input) + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(map[string]string{"status": "created"}) + json.NewEncoder(w).Encode(map[string]any{"status": "created", "id": id}) + } +} + +func GetCompaniesHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + companies, err := service.GetAllCompanies(db) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(companies) } } diff --git a/internal/handlers/revenue.go b/internal/handlers/revenue.go new file mode 100644 index 0000000..b68a816 --- /dev/null +++ b/internal/handlers/revenue.go @@ -0,0 +1,130 @@ +package handlers + +import ( + "Portifolio/internal/model" + "Portifolio/internal/service" + "database/sql" + "encoding/json" + "net/http" + "strconv" + + _ "github.com/mattn/go-sqlite3" +) + +func AddRevenueReportHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var input struct { + CompanyID int `json:"company_id"` + PeriodType string `json:"period_type"` + Year int `json:"year"` + Index int `json:"index"` + } + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + http.Error(w, "invalid json", http.StatusBadRequest) + return + } + + var period model.Period + switch input.PeriodType { + case "Q": + period = model.QuarterPeriod(input.Year, input.Index) + case "H": + period = model.HalfYearPeriod(input.Year, input.Index) + case "Y": + period = model.FullYearPeriod(input.Year) + default: + http.Error(w, "invalid period_type (Q/H/Y)", http.StatusBadRequest) + return + } + + id, err := service.InsertPeriod(db, period) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]any{ + "status": "created", + "period_id": id, + "period": period.String(), + }) + } +} + +func AddRevenueEntryHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var input struct { + CompanyID int `json:"company_id"` + CurrencyID int `json:"currency_id"` + PeriodType string `json:"period_type"` + Year int `json:"year"` + Index int `json:"index"` + Category string `json:"category"` + Label string `json:"label"` + Value float64 `json:"value"` + } + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + http.Error(w, "invalid json", http.StatusBadRequest) + return + } + + var period model.Period + switch input.PeriodType { + case "Q": + period = model.QuarterPeriod(input.Year, input.Index) + case "H": + period = model.HalfYearPeriod(input.Year, input.Index) + case "Y": + period = model.FullYearPeriod(input.Year) + default: + http.Error(w, "invalid period_type", http.StatusBadRequest) + return + } + + if err := service.InsertRevenue(db, input.CompanyID, input.CurrencyID, input.Category, input.Label, input.Value, period); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]string{"status": "created"}) + } +} + +func GetRevenueReportHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + companyID, _ := strconv.Atoi(r.URL.Query().Get("company_id")) + periodType := r.URL.Query().Get("period_type") + year, _ := strconv.Atoi(r.URL.Query().Get("year")) + idx, _ := strconv.Atoi(r.URL.Query().Get("index")) + + entries, err := service.GetRevenue(db, companyID, model.PeriodType(periodType), year, idx) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(entries) + } +} + +func GetRevenueSumHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + companyID, _ := strconv.Atoi(r.URL.Query().Get("company_id")) + periodType := r.URL.Query().Get("period_type") + year, _ := strconv.Atoi(r.URL.Query().Get("year")) + + rs, err := service.SumRevenue(db, companyID, model.PeriodType(periodType), year) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(rs) + } +} diff --git a/internal/model/revenue.go b/internal/model/revenue.go index b48b25f..f9f83cd 100644 --- a/internal/model/revenue.go +++ b/internal/model/revenue.go @@ -21,14 +21,23 @@ type Period struct { } // RevenueCategory is what the revenue is broken down by -type RevenueCategory string +// RevenueCategory is now just a string — categories are dynamic per company +type RevenueCategory = string +// Built-in reserved categories const ( - CategoryProduct RevenueCategory = "product" - CategoryLocation RevenueCategory = "location" - CategoryTotal RevenueCategory = "total" + CategoryTotal RevenueCategory = "total" // always required, never custom ) +// CategoryDef defines a custom category for a specific company +// e.g. Company: Apple, Name: "product", Labels: ["iPhone", "Mac", "Services"] +type CategoryDef struct { + ID int + Company *Company + Name string // e.g. "product", "location", "segment" + Labels []string // populated from category_labels table +} + // Revenue is a single line in a financial report type Revenue struct { ID int @@ -59,11 +68,57 @@ func (r *RevenueReport) Total(category RevenueCategory) float64 { return sum } -// Validate checks that product + location sums match the total entry -func (r *RevenueReport) Validate() (bool, float64, float64) { - total := r.Total(CategoryTotal) - products := r.Total(CategoryProduct) - locations := r.Total(CategoryLocation) - // both breakdowns should equal the total - return products == total && locations == total, products, locations +// SumByCategory returns a map of category → sum for all entries +func (r *RevenueReport) SumByCategory() map[RevenueCategory]float64 { + sums := make(map[RevenueCategory]float64) + for _, e := range r.Entries { + sums[e.Category] += e.Value + } + return sums +} + +// Validate checks every category independently sums to total +func (r *RevenueReport) Validate() (bool, map[RevenueCategory]float64) { + total := r.Total(CategoryTotal) + sums := r.SumByCategory() + delete(sums, CategoryTotal) // don't compare total to itself + + allMatch := true + for cat, sum := range sums { + if sum != total { + allMatch = false + _ = cat // caller can inspect the map to see which failed + } + } + return allMatch, sums +} + +// RevSum aggregates revenue across multiple reports or periods +// Use this when you want e.g. FY = Q1+Q2+Q3+Q4 +type RevSum struct { + Company *Company + Categories map[RevenueCategory]float64 // category → total across all periods + Labels map[string]float64 // label → total (e.g. "iPhone" across quarters) + Total float64 + Periods []Period // which periods were summed +} + +func NewRevSum(company *Company, reports []RevenueReport) *RevSum { + rs := &RevSum{ + Company: company, + Categories: make(map[RevenueCategory]float64), + Labels: make(map[string]float64), + } + for _, report := range reports { + rs.Periods = append(rs.Periods, report.Period) + for _, e := range report.Entries { + if e.Category == CategoryTotal { + rs.Total += e.Value + continue + } + rs.Categories[e.Category] += e.Value + rs.Labels[e.Label] += e.Value + } + } + return rs } diff --git a/internal/service/company.go b/internal/service/company.go index 9d5c20b..e3c50a1 100644 --- a/internal/service/company.go +++ b/internal/service/company.go @@ -3,6 +3,8 @@ package service import ( "Portifolio/internal/model" "database/sql" + + _ "github.com/mattn/go-sqlite3" ) func InsertCompany(db *sql.DB, input model.CompanyInput) (int, error) { diff --git a/internal/service/currency.go b/internal/service/currency.go index 34b0456..23818fb 100644 --- a/internal/service/currency.go +++ b/internal/service/currency.go @@ -3,6 +3,8 @@ package service import ( "Portifolio/internal/model" "database/sql" + + _ "github.com/mattn/go-sqlite3" ) func InsertCurrency(db *sql.DB, input model.CurrencyInput) (int, error) { diff --git a/internal/service/revenue.go b/internal/service/revenue.go new file mode 100644 index 0000000..d3ef5fd --- /dev/null +++ b/internal/service/revenue.go @@ -0,0 +1,135 @@ +package service + +import ( + "Portifolio/internal/model" + "database/sql" + "fmt" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +func InsertRevenue(db *sql.DB, companyID, currencyID int, category, label string, value float64, period model.Period) error { + periodID, err := InsertPeriod(db, period) + if err != nil { + return err + } + + reportID, err := getOrCreateReport(db, companyID, periodID) + if err != nil { + return err + } + + _, err = db.Exec( + `INSERT INTO revenue_entries (report_id, currency_id, category, label, value) VALUES (?, ?, ?, ?, ?)`, + reportID, currencyID, category, label, value, + ) + return err +} + +func InsertPeriod(db *sql.DB, p model.Period) (int, error) { + res, err := db.Exec( + `INSERT INTO periods (type, year, idx, start_date, end_date) VALUES (?, ?, ?, ?, ?) + ON CONFLICT(type, year, idx) DO UPDATE SET start_date=excluded.start_date`, + string(p.Type), p.Year, p.Index, p.Start.Format("2006-01-02"), p.End.Format("2006-01-02"), + ) + if err != nil { + return 0, err + } + id, err := res.LastInsertId() + return int(id), err +} + +func getOrCreateReport(db *sql.DB, companyID, periodID int) (int, error) { + var id int + err := db.QueryRow( + `SELECT id FROM revenue_reports WHERE company_id = ? AND period_id = ?`, + companyID, periodID, + ).Scan(&id) + if err == sql.ErrNoRows { + res, err := db.Exec( + `INSERT INTO revenue_reports (company_id, period_id) VALUES (?, ?)`, + companyID, periodID, + ) + if err != nil { + return 0, err + } + lid, _ := res.LastInsertId() + return int(lid), nil + } + return id, err +} + +func GetRevenue(db *sql.DB, companyID int, periodType model.PeriodType, year, idx int) ([]model.Revenue, error) { + rows, err := db.Query(` + SELECT e.id, e.category, e.label, e.value, + p.type, p.year, p.idx, p.start_date, p.end_date + FROM revenue_entries e + JOIN revenue_reports r ON e.report_id = r.id + JOIN periods p ON r.period_id = p.id + WHERE r.company_id = ? AND p.type = ? AND p.year = ? AND p.idx = ?`, + companyID, string(periodType), year, idx, + ) + if err != nil { + return nil, err + } + defer rows.Close() + + var entries []model.Revenue + for rows.Next() { + var e model.Revenue + var p model.Period + var start, end string + if err := rows.Scan(&e.ID, &e.Category, &e.Label, &e.Value, + &p.Type, &p.Year, &p.Index, &start, &end); err != nil { + return nil, err + } + p.Start, _ = time.Parse("2006-01-02", start) + p.End, _ = time.Parse("2006-01-02", end) + e.Period = p + entries = append(entries, e) + } + return entries, rows.Err() +} + +func SumRevenue(db *sql.DB, companyID int, periodType model.PeriodType, year int) (*model.RevSum, error) { + rows, err := db.Query(` + SELECT e.category, e.label, e.value, p.type, p.year, p.idx + FROM revenue_entries e + JOIN revenue_reports r ON e.report_id = r.id + JOIN periods p ON r.period_id = p.id + WHERE r.company_id = ? AND p.type = ? AND p.year = ?`, + companyID, string(periodType), year, + ) + if err != nil { + return nil, err + } + defer rows.Close() + + rs := &model.RevSum{ + Categories: make(map[model.RevenueCategory]float64), + Labels: make(map[string]float64), + } + seen := map[string]bool{} + for rows.Next() { + var category, label string + var value float64 + var pType model.PeriodType + var pYear, pIdx int + if err := rows.Scan(&category, &label, &value, &pType, &pYear, &pIdx); err != nil { + return nil, err + } + key := fmt.Sprintf("%s-%d-%d", pType, pYear, pIdx) + if !seen[key] { + rs.Periods = append(rs.Periods, model.Period{Type: pType, Year: pYear, Index: pIdx}) + seen[key] = true + } + if category == model.CategoryTotal { + rs.Total += value + } else { + rs.Categories[category] += value + rs.Labels[label] += value + } + } + return rs, rows.Err() +} diff --git a/internal/shell/company.go b/internal/shell/company.go index 6ebb4a1..9a5d429 100644 --- a/internal/shell/company.go +++ b/internal/shell/company.go @@ -52,3 +52,27 @@ func AddCompany(scanner *bufio.Scanner, db *sql.DB) { } fmt.Printf(" ✓ Company '%s' added.\n", input.Name) } + +func ListCompanies(db *sql.DB) { + companies, err := service.GetAllCompanies(db) + if err != nil { + fmt.Println(" ✗ Error:", err) + return + } + + if len(companies) == 0 { + fmt.Println(" No companies found.") + return + } + + fmt.Printf("\n %-5s %-20s %-10s %-15s %s\n", "ID", "NAME", "CURRENCY", "PRICE", "SHARES") + fmt.Println(" " + strings.Repeat("-", 60)) + for _, c := range companies { + currency := "N/A" + if c.Currency != nil { + currency = c.Currency.Code + } + fmt.Printf(" %-5d %-20s %-10s %-15.2f %d\n", + c.ID, c.Name, currency, c.Price, c.SharesOutstanding) + } +} diff --git a/internal/shell/revenue.go b/internal/shell/revenue.go new file mode 100644 index 0000000..b37065c --- /dev/null +++ b/internal/shell/revenue.go @@ -0,0 +1,153 @@ +package shell + +import ( + "Portifolio/internal/model" + "Portifolio/internal/service" + "bufio" + "database/sql" + "fmt" + "strconv" + "strings" +) + +func AddRevenue(scanner *bufio.Scanner, db *sql.DB) { + fmt.Print(" Company ID: ") + scanner.Scan() + companyID, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) + if err != nil { + fmt.Println(" ✗ Invalid company ID") + return + } + + fmt.Print(" Currency ID: ") + scanner.Scan() + currencyID, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) + if err != nil { + fmt.Println(" ✗ Invalid currency ID") + return + } + + fmt.Print(" Period type (Q/H/Y): ") + scanner.Scan() + periodType := strings.ToUpper(strings.TrimSpace(scanner.Text())) + + fmt.Print(" Year: ") + scanner.Scan() + year, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) + if err != nil { + fmt.Println(" ✗ Invalid year") + return + } + + fmt.Print(" Index (Q: 1-4 | H: 1-2 | Y: 1): ") + scanner.Scan() + idx, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) + if err != nil { + fmt.Println(" ✗ Invalid index") + return + } + + fmt.Print(" Category (product/location/total): ") + scanner.Scan() + category := strings.TrimSpace(scanner.Text()) + + fmt.Print(" Label (e.g. iPhone, Americas): ") + scanner.Scan() + label := strings.TrimSpace(scanner.Text()) + + fmt.Print(" Value: ") + scanner.Scan() + value, err := strconv.ParseFloat(strings.TrimSpace(scanner.Text()), 64) + if err != nil { + fmt.Println(" ✗ Invalid value") + return + } + + var period model.Period + switch periodType { + case "Q": + period = model.QuarterPeriod(year, idx) + case "H": + period = model.HalfYearPeriod(year, idx) + case "Y": + period = model.FullYearPeriod(year) + default: + fmt.Println(" ✗ Invalid period type") + return + } + + if err := service.InsertRevenue(db, companyID, currencyID, category, label, value, period); err != nil { + fmt.Println(" ✗ Error:", err) + return + } + fmt.Printf(" ✓ Revenue entry added: %s / %s = %.2f (%s)\n", category, label, value, period.String()) +} + +func ListRevenue(scanner *bufio.Scanner, db *sql.DB) { + fmt.Print(" Company ID: ") + scanner.Scan() + companyID, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) + if err != nil { + fmt.Println(" ✗ Invalid company ID") + return + } + + fmt.Print(" Period type (Q/H/Y): ") + scanner.Scan() + periodType := strings.ToUpper(strings.TrimSpace(scanner.Text())) + + fmt.Print(" Year: ") + scanner.Scan() + year, _ := strconv.Atoi(strings.TrimSpace(scanner.Text())) + + fmt.Print(" Index: ") + scanner.Scan() + idx, _ := strconv.Atoi(strings.TrimSpace(scanner.Text())) + + entries, err := service.GetRevenue(db, companyID, model.PeriodType(periodType), year, idx) + if err != nil { + fmt.Println(" ✗ Error:", err) + return + } + + fmt.Printf("\n %-12s %-20s %12s\n", "CATEGORY", "LABEL", "VALUE") + fmt.Println(" " + strings.Repeat("-", 46)) + for _, e := range entries { + fmt.Printf(" %-12s %-20s %12.2f\n", e.Category, e.Label, e.Value) + } +} + +func SumRevenue(scanner *bufio.Scanner, db *sql.DB) { + fmt.Print(" Company ID: ") + scanner.Scan() + companyID, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) + if err != nil { + fmt.Println(" ✗ Invalid company ID") + return + } + + fmt.Print(" Period type to sum (Q/H/Y): ") + scanner.Scan() + periodType := strings.ToUpper(strings.TrimSpace(scanner.Text())) + + fmt.Print(" Year: ") + scanner.Scan() + year, _ := strconv.Atoi(strings.TrimSpace(scanner.Text())) + + rs, err := service.SumRevenue(db, companyID, model.PeriodType(periodType), year) + if err != nil { + fmt.Println(" ✗ Error:", err) + return + } + + fmt.Printf("\n Revenue Sum — FY%d\n", year) + fmt.Printf(" Total: %.2f\n\n", rs.Total) + fmt.Println(" By Category:") + for cat, sum := range rs.Categories { + fmt.Printf(" %-15s %.2f\n", cat, sum) + } + fmt.Println("\n By Label:") + for label, sum := range rs.Labels { + fmt.Printf(" %-20s %.2f\n", label, sum) + } +} diff --git a/main.go b/main.go index b61b335..f4fd955 100644 --- a/main.go +++ b/main.go @@ -33,11 +33,19 @@ func main() { fmt.Println("Connected to SQLite database") http.HandleFunc("/health", handlers.HealthHandler(db)) + + // Company http.HandleFunc("POST /add/company", handlers.AddCompanyHandler(db)) - /* - http.HandleFunc("GET /companies", handlers.AddCompanyHandler(db)) - http.HandleFunc("GET /currencies", handlers.AddCompanyHandler(db)) - */ + http.HandleFunc("GET /companies", handlers.GetCompaniesHandler(db)) + + // Currency + http.HandleFunc("GET /currencies", handlers.GetCurrenciesHandler(db)) + + // Revenue + http.HandleFunc("POST /add/revenue/report", handlers.AddRevenueReportHandler(db)) + http.HandleFunc("POST /add/revenue/entry", handlers.AddRevenueEntryHandler(db)) + http.HandleFunc("GET /revenue/report", handlers.GetRevenueReportHandler(db)) + http.HandleFunc("GET /revenue/sum", handlers.GetRevenueSumHandler(db)) fmt.Println("Server running on :8080") go func() { @@ -49,7 +57,7 @@ func main() { func runShell(db *sql.DB) { scanner := bufio.NewScanner(os.Stdin) - fmt.Println("\nShell ready. Commands: add-company, help, exit") + fmt.Println("\nShell ready. Type 'help' for commands.") for { fmt.Print("> ") @@ -63,20 +71,40 @@ func runShell(db *sql.DB) { } switch parts[0] { + // Company case "add-company": shell.AddCompany(scanner, db) + case "list-companies": + shell.ListCompanies(db) + + // Currency case "add-currency": shell.AddCurrency(scanner, db) case "list-currency": shell.ListCurrencies(db) - case "help": - fmt.Println("Commands:") - fmt.Println(" add-company - add a new company interactively") - fmt.Println(" add-currency - add a new currency interactively") - fmt.Println(" list-currency - lists all currencies") + // Revenue + case "add-revenue": + shell.AddRevenue(scanner, db) + case "list-revenue": + shell.ListRevenue(scanner, db) + case "sum-revenue": + shell.SumRevenue(scanner, db) - fmt.Println(" exit - quit") + case "help": + fmt.Println("\nCommands:") + fmt.Println(" --- Company ---") + fmt.Println(" add-company add a new company") + fmt.Println(" list-companies list all companies") + fmt.Println(" --- Currency ---") + fmt.Println(" add-currency add a new currency") + fmt.Println(" list-currency list all currencies") + fmt.Println(" --- Revenue ---") + fmt.Println(" add-revenue add a revenue entry interactively") + fmt.Println(" list-revenue list revenue for a company/period") + fmt.Println(" sum-revenue sum revenue across periods") + fmt.Println(" --- Other ---") + fmt.Println(" exit quit") case "exit": fmt.Println("Bye!")