basic internal structure
This commit is contained in:
BIN
Portifolio
Executable file
BIN
Portifolio
Executable file
Binary file not shown.
2
go.mod
2
go.mod
@@ -1,3 +1,5 @@
|
|||||||
module Portifolio
|
module Portifolio
|
||||||
|
|
||||||
go 1.25.7
|
go 1.25.7
|
||||||
|
|
||||||
|
require github.com/mattn/go-sqlite3 v1.14.37 // indirect
|
||||||
|
|||||||
2
go.sum
Normal file
2
go.sum
Normal 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
32
internal/database/main.go
Normal 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
52
internal/handlers/main.go
Normal 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
28
internal/model/company.go
Normal 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"`
|
||||||
|
}
|
||||||
48
internal/service/company.go
Normal file
48
internal/service/company.go
Normal 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()
|
||||||
|
}
|
||||||
47
internal/service/currency.go
Normal file
47
internal/service/currency.go
Normal 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
16
internal/service/main.go
Normal 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
54
internal/shell/company.go
Normal 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)
|
||||||
|
}
|
||||||
44
internal/shell/currency.go
Normal file
44
internal/shell/currency.go
Normal 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
84
main.go
@@ -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() {
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user