basic internal structure
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user