basic internal structure

This commit is contained in:
samantha42
2026-03-24 11:17:08 +01:00
parent 29b3c2dd6c
commit a9d4f09b62
14 changed files with 407 additions and 3 deletions

BIN
Portifolio Executable file

Binary file not shown.

BIN
app.db Normal file

Binary file not shown.

1
build.sh Normal file
View File

@@ -0,0 +1 @@
CGO_CFLAGS="-w" go build Portifolio

2
go.mod
View File

@@ -1,3 +1,5 @@
module Portifolio
go 1.25.7
require github.com/mattn/go-sqlite3 v1.14.37 // indirect

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=

32
internal/database/main.go Normal file
View File

@@ -0,0 +1,32 @@
package database
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func InitDB(db *sql.DB) {
schema := `
CREATE TABLE IF NOT EXISTS currencies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS companies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
shares_outstanding INTEGER NOT NULL,
price REAL NOT NULL,
currency_id INTEGER NOT NULL,
FOREIGN KEY (currency_id) REFERENCES currencies(id)
);`
if _, err := db.Exec(schema); err != nil {
log.Fatal("Failed to create tables:", err)
}
fmt.Println("Tables ready")
}

52
internal/handlers/main.go Normal file
View File

@@ -0,0 +1,52 @@
package handlers
import (
"Portifolio/internal/model"
"Portifolio/internal/service"
"database/sql"
"encoding/json"
"net/http"
_ "github.com/mattn/go-sqlite3"
)
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()
w.WriteHeader(http.StatusServiceUnavailable)
}
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
"database": dbStatus,
})
}
}
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 {
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"})
}
}

28
internal/model/company.go Normal file
View File

@@ -0,0 +1,28 @@
package model
type Currency struct {
ID int
Code string
Name string
}
type Company struct {
ID int
Name string
SharesOutstanding int
Price float64
CurrencyID int
Currency *Currency // populated on joins
}
type CompanyInput struct {
Name string `json:"name"`
SharesOutstanding int `json:"shares_outstanding"`
Price float64 `json:"price"`
CurrencyID int `json:"currency_id"`
}
type CurrencyInput struct {
Code string `json:"code"`
Name string `json:"name"`
}

View File

@@ -0,0 +1,48 @@
package service
import (
"Portifolio/internal/model"
"database/sql"
)
func InsertCompany(db *sql.DB, input model.CompanyInput) (int, error) {
res, err := db.Exec(
`INSERT INTO companies (name, shares_outstanding, price, currency_id) VALUES (?, ?, ?, ?)`,
input.Name, input.SharesOutstanding, input.Price, input.CurrencyID,
)
if err != nil {
return 0, err
}
id, err := res.LastInsertId()
return int(id), err
}
func GetAllCompanies(db *sql.DB) ([]model.Company, error) {
rows, err := db.Query(`
SELECT c.id, c.name, c.shares_outstanding, c.price,
cu.id, cu.code, cu.name
FROM companies c
JOIN currencies cu ON c.currency_id = cu.id
ORDER BY c.name
`)
if err != nil {
return nil, err
}
defer rows.Close()
var companies []model.Company
for rows.Next() {
var c model.Company
var cu model.Currency
if err := rows.Scan(
&c.ID, &c.Name, &c.SharesOutstanding, &c.Price,
&cu.ID, &cu.Code, &cu.Name,
); err != nil {
return nil, err
}
c.CurrencyID = cu.ID
c.Currency = &cu
companies = append(companies, c)
}
return companies, rows.Err()
}

View File

@@ -0,0 +1,47 @@
package service
import (
"Portifolio/internal/model"
"database/sql"
)
func InsertCurrency(db *sql.DB, input model.CurrencyInput) (int, error) {
res, err := db.Exec(
`INSERT INTO currencies (code, name) VALUES (?, ?)`,
input.Code, input.Name,
)
if err != nil {
return 0, err
}
id, err := res.LastInsertId()
return int(id), err
}
func GetCurrencyByCode(db *sql.DB, code string) (*model.Currency, error) {
c := &model.Currency{}
err := db.QueryRow(
`SELECT id, code, name FROM currencies WHERE code = ?`, code,
).Scan(&c.ID, &c.Code, &c.Name)
if err == sql.ErrNoRows {
return nil, nil
}
return c, err
}
func GetAllCurrencies(db *sql.DB) ([]model.Currency, error) {
rows, err := db.Query(`SELECT id, code, name FROM currencies ORDER BY code`)
if err != nil {
return nil, err
}
defer rows.Close()
var currencies []model.Currency
for rows.Next() {
var c model.Currency
if err := rows.Scan(&c.ID, &c.Code, &c.Name); err != nil {
return nil, err
}
currencies = append(currencies, c)
}
return currencies, rows.Err()
}

16
internal/service/main.go Normal file
View File

@@ -0,0 +1,16 @@
package service
import (
"Portifolio/internal/model"
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
func AddCompany(input model.CompanyInput, db *sql.DB) error {
_, err := db.Exec(
`INSERT INTO companies (name, shares_outstanding, price, currency_id) VALUES (?, ?, ?, ?)`,
input.Name, input.SharesOutstanding, input.Price, input.CurrencyID,
)
return err
}

54
internal/shell/company.go Normal file
View File

@@ -0,0 +1,54 @@
package shell
import (
"Portifolio/internal/model"
"Portifolio/internal/service"
"bufio"
"database/sql"
"fmt"
"strconv"
"strings"
_ "github.com/mattn/go-sqlite3"
)
func AddCompany(scanner *bufio.Scanner, db *sql.DB) {
input := model.CompanyInput{}
fmt.Print(" Name: ")
scanner.Scan()
input.Name = strings.TrimSpace(scanner.Text())
fmt.Print(" Shares outstanding: ")
scanner.Scan()
shares, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
fmt.Println(" Invalid number for shares.")
return
}
input.SharesOutstanding = shares
fmt.Print(" Price: ")
scanner.Scan()
price, err := strconv.ParseFloat(strings.TrimSpace(scanner.Text()), 64)
if err != nil {
fmt.Println(" Invalid number for price.")
return
}
input.Price = price
fmt.Print(" Currency ID: ")
scanner.Scan()
cid, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
fmt.Println(" Invalid currency ID.")
return
}
input.CurrencyID = cid
if err := service.AddCompany(input, db); err != nil {
fmt.Println(" Error:", err)
return
}
fmt.Printf(" ✓ Company '%s' added.\n", input.Name)
}

View File

@@ -0,0 +1,44 @@
package shell
import (
"Portifolio/internal/model"
"Portifolio/internal/service"
"bufio"
"database/sql"
"fmt"
"strings"
_ "github.com/mattn/go-sqlite3"
)
func AddCurrency(scanner *bufio.Scanner, db *sql.DB) {
input := model.CurrencyInput{}
fmt.Print(" Code (e.g. DKK): ")
scanner.Scan()
input.Code = strings.ToUpper(strings.TrimSpace(scanner.Text()))
fmt.Print(" Name (e.g. Danish Krone): ")
scanner.Scan()
input.Name = strings.TrimSpace(scanner.Text())
id, err := service.InsertCurrency(db, input)
if err != nil {
fmt.Println(" ✗ Error:", err)
return
}
fmt.Printf(" ✓ Currency '%s' (%s) added with ID %d\n", input.Name, input.Code, id)
}
func ListCurrencies(db *sql.DB) {
currencies, err := service.GetAllCurrencies(db)
if err != nil {
fmt.Println(" ✗ Error:", err)
return
}
fmt.Printf(" %-5s %-6s %s\n", "ID", "CODE", "NAME")
fmt.Println(" " + strings.Repeat("-", 30))
for _, c := range currencies {
fmt.Printf(" %-5d %-6s %s\n", c.ID, c.Code, c.Name)
}
}

84
main.go
View File

@@ -1,7 +1,85 @@
package portifolio
package main
import "fmt"
import (
"Portifolio/internal/database"
"Portifolio/internal/handlers"
"Portifolio/internal/shell"
"bufio"
"database/sql"
"fmt"
"log"
"net/http"
"os"
"strings"
_ "github.com/mattn/go-sqlite3"
)
var db *sql.DB
func main() {
fmt.Println("hello world")
var err error
db, err = sql.Open("sqlite3", "./app.db?_foreign_keys=on")
if err != nil {
log.Fatal("Failed to open database:", err)
}
defer db.Close()
if err = db.Ping(); err != nil {
log.Fatal("Failed to connect to database:", err)
}
database.InitDB(db)
fmt.Println("Connected to SQLite database")
http.HandleFunc("/health", handlers.HealthHandler(db))
http.HandleFunc("/add/company", handlers.AddCompanyHandler(db))
fmt.Println("Server running on :8080")
go func() {
log.Fatal(http.ListenAndServe(":8080", nil))
}()
runShell(db)
}
func runShell(db *sql.DB) {
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("\nShell ready. Commands: add-company, help, exit")
for {
fmt.Print("> ")
if !scanner.Scan() {
break
}
parts := strings.Fields(scanner.Text())
if len(parts) == 0 {
continue
}
switch parts[0] {
case "add-company":
shell.AddCompany(scanner, db)
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")
fmt.Println(" exit - quit")
case "exit":
fmt.Println("Bye!")
os.Exit(0)
default:
fmt.Printf("Unknown command: %s. Type 'help' for commands.\n", parts[0])
}
}
}