parent
89e485efd7
commit
32153093cb
@ -0,0 +1,15 @@
|
||||
package quartermaster
|
||||
|
||||
type Category struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type CategoryService interface {
|
||||
Category(int) (*Category, error)
|
||||
AddCategory(*Category) (*Category, error)
|
||||
UpdateCategory(*Category) error
|
||||
DeleteCategory(*Category) error
|
||||
GetItems(*Category) ([]*Item, error)
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster/internal/postgres"
|
||||
"github.com/dustinpianalto/quartermaster/internal/services"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var migrationsVersion uint = 2 // Update when there is a new migration
|
||||
postgres.ConnectDatabase(os.Getenv("DATABASE_URL"), migrationsVersion)
|
||||
services.InitServices()
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
module github.com/dustinpianalto/quartermaster
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/gobuffalo/here v0.6.0 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.15.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.4 // indirect
|
||||
github.com/markbates/pkger v0.17.1 // indirect
|
||||
go.uber.org/atomic v1.6.0 // indirect
|
||||
)
|
||||
@ -0,0 +1,15 @@
|
||||
package quartermaster
|
||||
|
||||
type Group struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type GroupService interface {
|
||||
Group(int) (*Group, error)
|
||||
AddGroup(*Group) (*Group, error)
|
||||
UpdateGroup(*Group) error
|
||||
DeleteGroup(*Group) error
|
||||
GetItems(*Group) ([]*Item, error)
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster"
|
||||
)
|
||||
|
||||
type categoryService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (s categoryService) Category(id int) (*quartermaster.Category, error) {
|
||||
var c quartermaster.Category
|
||||
queryString := "SELECT id, name, description FROM categories WHERE id = $1"
|
||||
row := s.db.QueryRow(queryString, id)
|
||||
err := row.Scan(&c.ID, &c.Name, &c.Description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (s categoryService) AddCategory(c *quartermaster.Category) (*quartermaster.Category, error) {
|
||||
queryString := "INSERT INTO categories (name, description) VALUES ($1, $2) RETURNING id"
|
||||
err := s.db.QueryRow(queryString, c.Name, c.Description).Scan(&c.ID)
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (s categoryService) UpdateCategory(c *quartermaster.Category) error {
|
||||
queryString := "UPDATE categories SET name = $2, description = $3 WHERE id = $1"
|
||||
_, err := s.db.Exec(queryString, c.ID, c.Name, c.Description)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s categoryService) DeleteCategory(c *quartermaster.Category) error {
|
||||
queryString := "DELETE FROM categories WHERE id = $1"
|
||||
_, err := s.db.Exec(queryString, c.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s categoryService) GetItems(c *quartermaster.Category) ([]*quartermaster.Item, error) {
|
||||
var items []*quartermaster.Item
|
||||
queryString := "SELECT item_id FROM x_items_categories WHERE category_id = $1"
|
||||
rows, err := s.db.Query(queryString, c.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var id int
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
item, err := ItemService.Item(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster"
|
||||
migrate "github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/pkger"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/markbates/pkger"
|
||||
)
|
||||
|
||||
var (
|
||||
ItemService quartermaster.ItemService
|
||||
NutritionService quartermaster.NutritionService
|
||||
LocationService quartermaster.LocationService
|
||||
VitaminService quartermaster.VitaminService
|
||||
GroupService quartermaster.GroupService
|
||||
CategoryService quartermaster.CategoryService
|
||||
)
|
||||
|
||||
func ConnectDatabase(dbConnString string, version uint) {
|
||||
db, err := sql.Open("postgres", dbConnString)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Can't connect to the database. %v", err))
|
||||
}
|
||||
db.SetMaxOpenConns(75) // The RDS instance has a max of 75 open connections
|
||||
db.SetMaxIdleConns(5)
|
||||
db.SetConnMaxLifetime(300)
|
||||
log.Println("Database Connected")
|
||||
err = runMigrations(db, version)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Migrations Completed")
|
||||
initServices(db)
|
||||
log.Println("Postgres Database Initialized")
|
||||
}
|
||||
|
||||
func runMigrations(db *sql.DB, version uint) error {
|
||||
_ = pkger.Include("/internal/postgres/migrations")
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := migrate.NewWithDatabaseInstance("pkger:///internal/postgres/migrations", "postgres", driver)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if err := m.Migrate(version); errors.Is(err, migrate.ErrNoChange) {
|
||||
log.Println(err)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initServices(db *sql.DB) {
|
||||
ItemService = itemService{db: db}
|
||||
NutritionService = nutritionService{db: db}
|
||||
LocationService = locationService{db: db}
|
||||
VitaminService = vitaminService{db: db}
|
||||
GroupService = groupService{db: db}
|
||||
CategoryService = categoryService{db: db}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster"
|
||||
)
|
||||
|
||||
type groupService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (s groupService) Group(id int) (*quartermaster.Group, error) {
|
||||
var g quartermaster.Group
|
||||
queryString := "SELECT id, name, description FROM groups WHERE id = $1"
|
||||
row := s.db.QueryRow(queryString, id)
|
||||
err := row.Scan(&g.ID, &g.Name, &g.Description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
func (s groupService) AddGroup(g *quartermaster.Group) (*quartermaster.Group, error) {
|
||||
queryString := "INSERT INTO groups (name, description) VALUES ($1, $2) RETURNING id"
|
||||
err := s.db.QueryRow(queryString, g.Name, g.Description).Scan(&g.ID)
|
||||
return g, err
|
||||
}
|
||||
|
||||
func (s groupService) UpdateGroup(g *quartermaster.Group) error {
|
||||
queryString := "UPDATE groups SET name = $2, description = $3 WHERE id = $1"
|
||||
_, err := s.db.Exec(queryString, g.ID, g.Name, g.Description)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s groupService) DeleteGroup(g *quartermaster.Group) error {
|
||||
queryString := "DELETE FROM groups WHERE id = $1"
|
||||
_, err := s.db.Exec(queryString, g.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s groupService) GetItems(g *quartermaster.Group) ([]*quartermaster.Item, error) {
|
||||
var items []*quartermaster.Item
|
||||
queryString := "SELECT item_id FROM x_items_groups WHERE group_id = $1"
|
||||
rows, err := s.db.Query(queryString, g.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var id int
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
item, err := ItemService.Item(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster"
|
||||
)
|
||||
|
||||
type itemService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (s itemService) Item(id int) (*quartermaster.Item, error) {
|
||||
var i quartermaster.Item
|
||||
queryString := "SELECT id, name, description, size, unit, barcode, nutrition_id FROM items WHERE id = $1"
|
||||
row := s.db.QueryRow(queryString, id)
|
||||
var nutrition_id sql.NullInt32
|
||||
err := row.Scan(&i.ID, &i.Name, &i.Description, &i.Size, &i.Unit, &i.Barcode, &nutrition_id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nutrition_id.Valid {
|
||||
n, err := NutritionService.Nutrition(int(nutrition_id.Int32))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.Nutrition = n
|
||||
} else {
|
||||
i.Nutrition = nil
|
||||
}
|
||||
return &i, nil
|
||||
}
|
||||
|
||||
func (s itemService) AddItem(i *quartermaster.Item, l *quartermaster.Location) (*quartermaster.Item, error) {
|
||||
var err error
|
||||
if i.ID == 0 {
|
||||
if i.Nutrition != nil {
|
||||
i.Nutrition, err = NutritionService.AddNutrition(i.Nutrition)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
queryString := "INSERT INTO items (name, description, size, unit, barcode, nutrition_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id"
|
||||
err := s.db.QueryRow(queryString, i.Name, i.Description, i.Size, i.Unit, i.Barcode, i.Nutrition.ID).Scan(&i.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
queryString := "INSERT INTO x_items_locations (item_id, location_id, count) VALUES ($1, $2, 1) ON DUPLICATE KEY UPDATE count = count + 1"
|
||||
_, err = s.db.Exec(queryString, i.ID, l.ID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
func (s itemService) AddCategory(i *quartermaster.Item, c *quartermaster.Category) error {
|
||||
queryString := "INSERT INTO x_items_categories (item_id, category_id) VALUES ($1, $2) ON DUPLICATE KEY DO NOTHING"
|
||||
_, err := s.db.Exec(queryString, i.ID, c.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s itemService) AddGroup(i *quartermaster.Item, g *quartermaster.Group) error {
|
||||
queryString := "INSERT INTO x_items_groups (item_id, group_id) VALUES ($1, $2) ON DUPLICATE KEY DO NOTHING"
|
||||
_, err := s.db.Exec(queryString, i.ID, g.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s itemService) RemoveCategory(i *quartermaster.Item, c *quartermaster.Category) error {
|
||||
queryString := "DELETE FROM x_items_categories WHERE item_id = $1 AND category_id = $2"
|
||||
_, err := s.db.Exec(queryString, i.ID, c.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s itemService) RemoveGroup(i *quartermaster.Item, g *quartermaster.Group) error {
|
||||
queryString := "DELETE FROM x_items_groups WHERE item_id = $1 AND group_id = $2"
|
||||
_, err := s.db.Exec(queryString, i.ID, g.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s itemService) DeleteItem(i *quartermaster.Item) error {
|
||||
queryString := "DELETE FROM items WHERE id = $1"
|
||||
_, err := s.db.Exec(queryString, i.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s itemService) GetItemByBarcode(b string) (*quartermaster.Item, error) {
|
||||
queryString := "SELECT id FROM items WHERE barcode = $1"
|
||||
row := s.db.QueryRow(queryString, b)
|
||||
var id int
|
||||
err := row.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.Item(id)
|
||||
}
|
||||
|
||||
func (s itemService) RemoveItem(i *quartermaster.Item, l *quartermaster.Location) error {
|
||||
queryString := "UPDATE x_items_locations SET count = count - 1 WHERE item_id = $1 AND location_id = $2 RETURNING count"
|
||||
var count int
|
||||
err := s.db.QueryRow(queryString, i.ID, l.ID).Scan(&count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count <= 0 {
|
||||
queryString = "DELETE FROM x_items_locations WHERE item_id = $1 AND location_id = $2"
|
||||
_, err := s.db.Exec(queryString, i.ID, l.ID)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s itemService) MoveItem(i *quartermaster.Item, old, new *quartermaster.Location) error {
|
||||
err := s.RemoveItem(i, old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.AddItem(i, new)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s itemService) UpdateItem(i *quartermaster.Item) error {
|
||||
var err error
|
||||
if i.Nutrition != nil {
|
||||
if i.Nutrition.ID == 0 {
|
||||
i.Nutrition, err = NutritionService.AddNutrition(i.Nutrition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = NutritionService.UpdateNutrition(i.Nutrition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
queryString := "UPDATE items SET name = $2, description = $3, size = $4, unit = $5, barcode = $6, nutrition_id = $7, WHERE id = $1"
|
||||
_, err = s.db.Exec(queryString, i.ID, i.Name, i.Description, i.Size, i.Unit, i.Barcode, i.Nutrition.ID)
|
||||
return err
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster"
|
||||
)
|
||||
|
||||
type locationService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (s locationService) Location(id int) (*quartermaster.Location, error) {
|
||||
var l quartermaster.Location
|
||||
var parent_id sql.NullInt32
|
||||
queryString := "SELECT id, name, description, parent_id FROM locations WHERE id = $1"
|
||||
row := s.db.QueryRow(queryString, id)
|
||||
err := row.Scan(&l.ID, &l.Name, &l.Description, &parent_id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parent_id.Valid {
|
||||
p, err := s.Location(int(parent_id.Int32))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.Parent = p
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
func (s locationService) AddLocation(l *quartermaster.Location) (*quartermaster.Location, error) {
|
||||
queryString := "INSERT INTO locations (name, description, parent_id VALUES ($1, $2, $3) RETURNING id"
|
||||
var err error
|
||||
if l.Parent != nil {
|
||||
err = s.db.QueryRow(queryString, l.Name, l.Description, l.Parent.ID).Scan(&l.ID)
|
||||
} else {
|
||||
err = s.db.QueryRow(queryString, l.Name, l.Description, nil).Scan(&l.ID)
|
||||
}
|
||||
return l, err
|
||||
}
|
||||
|
||||
func (s locationService) UpdateLocation(l *quartermaster.Location) error {
|
||||
queryString := "UPDATE locations SET name = $2, description = $3, parent_id = $4 WHERE id = $1"
|
||||
var err error
|
||||
if l.Parent != nil {
|
||||
_, err = s.db.Exec(queryString, l.ID, l.Name, l.Description, l.Parent.ID)
|
||||
} else {
|
||||
_, err = s.db.Exec(queryString, l.ID, l.Name, l.Description, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s locationService) DeleteLocation(l *quartermaster.Location) error {
|
||||
queryString := "DELETE FROM locations WHERE id = $1"
|
||||
_, err := s.db.Exec(queryString, l.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s locationService) GetChildren(l *quartermaster.Location) ([]*quartermaster.Location, error) {
|
||||
var locations []*quartermaster.Location
|
||||
queryString := "SELECT id FROM locations WHERE parent_id = $1"
|
||||
rows, err := s.db.Query(queryString, l.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var id int
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
location, err := s.Location(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
locations = append(locations, location)
|
||||
}
|
||||
return locations, nil
|
||||
}
|
||||
|
||||
func (s locationService) GetItems(l *quartermaster.Location) (map[*quartermaster.Item]int, error) {
|
||||
items := make(map[*quartermaster.Item]int)
|
||||
queryString := "SELECT item_id, count FROM x_items_locations WHERE location_id = $1"
|
||||
rows, err := s.db.Query(queryString, l.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var id, count int
|
||||
err = rows.Scan(&id, &count)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
item, err := ItemService.Item(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
items[item] = count
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s locationService) GetTopLocations() ([]*quartermaster.Location, error) {
|
||||
var locations []*quartermaster.Location
|
||||
queryString := "SELECT id FROM locations WHERE parent_id IS NULL"
|
||||
rows, err := s.db.Query(queryString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var id int
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
l, err := s.Location(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
locations = append(locations, l)
|
||||
}
|
||||
return locations, nil
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS x_items_groups;
|
||||
DROP TABLE IF EXISTS x_items_categories;
|
||||
DROP TABLE IF EXISTS x_items_locations;
|
||||
DROP TABLE IF EXISTS groups;
|
||||
DROP TABLE IF EXISTS categories;
|
||||
DROP TABLE IF EXISTS vitamins;
|
||||
DROP TABLE IF EXISTS items;
|
||||
DROP TABLE IF EXISTS locations;
|
||||
DROP TABLE IF EXISTS nutrition;
|
||||
DROP TYPE unit;
|
||||
DROP TYPE vitamin;
|
||||
|
||||
COMMIT;
|
||||
@ -0,0 +1,125 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS locations (
|
||||
id SERIAL PRIMARY KEY ,
|
||||
name VARCHAR(30) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
parent_id INTEGER,
|
||||
CONSTRAINT fk_parent
|
||||
FOREIGN KEY (parent_id)
|
||||
REFERENCES locations (id)
|
||||
ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id SERIAL PRIMARY KEY ,
|
||||
name VARCHAR(30) NOT NULL,
|
||||
description TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id SERIAL PRIMARY KEY ,
|
||||
name VARCHAR(30) NOT NULL,
|
||||
description TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TYPE unit AS ENUM (
|
||||
'Teaspoon',
|
||||
'Tablespoon',
|
||||
'Cup',
|
||||
'Ounce',
|
||||
'Gram',
|
||||
'Pound'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nutrition (
|
||||
id SERIAL PRIMARY KEY ,
|
||||
unit unit NOT NULL,
|
||||
calories FLOAT NOT NULL,
|
||||
fat FLOAT NOT NULL,
|
||||
sodium FLOAT NOT NULL,
|
||||
protein FLOAT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TYPE vitamin AS ENUM (
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'Iron'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vitamins (
|
||||
nutrition_id INTEGER NOT NULL,
|
||||
vitamin vitamin NOT NULL,
|
||||
amount FLOAT NOT NULL,
|
||||
CONSTRAINT fk_nutrition
|
||||
FOREIGN KEY (nutrition_id)
|
||||
REFERENCES nutrition(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT uniq_nutrition_vitamin
|
||||
UNIQUE (nutrition_id, vitamin)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id SERIAL PRIMARY KEY ,
|
||||
name VARCHAR(30) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
size FLOAT NOT NULL,
|
||||
unit unit NOT NULL,
|
||||
barcode VARCHAR(100),
|
||||
nutrition_id INTEGER,
|
||||
CONSTRAINT fk_item_nutrition
|
||||
FOREIGN KEY (nutrition_id)
|
||||
REFERENCES nutrition (id)
|
||||
ON DELETE SET NULL,
|
||||
CONSTRAINT uniq_barcode
|
||||
UNIQUE (barcode)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS x_items_locations (
|
||||
item_id INTEGER NOT NULL,
|
||||
location_id INTEGER NOT NULL,
|
||||
count INTEGER NOT NULL,
|
||||
CONSTRAINT fk_item
|
||||
FOREIGN KEY (item_id)
|
||||
REFERENCES items (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_location
|
||||
FOREIGN KEY (location_id)
|
||||
REFERENCES locations (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT uniq_item_location
|
||||
UNIQUE (item_id, location_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS x_items_categories (
|
||||
item_id INTEGER NOT NULL,
|
||||
category_id INTEGER NOT NULL,
|
||||
CONSTRAINT fk_item
|
||||
FOREIGN KEY (item_id)
|
||||
REFERENCES items (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_category
|
||||
FOREIGN KEY (category_id)
|
||||
REFERENCES categories (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT uniq_item_category
|
||||
UNIQUE (item_id, category_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS x_items_groups (
|
||||
item_id INTEGER NOT NULL,
|
||||
group_id INTEGER NOT NULL,
|
||||
CONSTRAINT fk_item
|
||||
FOREIGN KEY (item_id)
|
||||
REFERENCES items (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_group
|
||||
FOREIGN KEY (group_id)
|
||||
REFERENCES groups (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT uniq_item_group
|
||||
UNIQUE (item_id, group_id)
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@ -0,0 +1 @@
|
||||
ALTER TYPE unit ADD VALUE 'Individual';
|
||||
@ -0,0 +1,116 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster"
|
||||
)
|
||||
|
||||
type nutritionService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (s nutritionService) Nutrition(id int) (*quartermaster.Nutrition, error) {
|
||||
var n quartermaster.Nutrition
|
||||
queryString := "SELECT id, unit, calories, fat, sodium, protein FROM nutrition WHERE id = $1"
|
||||
row := s.db.QueryRow(queryString, id)
|
||||
err := row.Scan(&n.ID, &n.Unit, &n.Calories, &n.Fat, &n.Sodium, &n.Protein)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
func (s nutritionService) AddNutrition(n *quartermaster.Nutrition) (*quartermaster.Nutrition, error) {
|
||||
queryString := "INSERT INTO nutrition (unit, calories, fat, sodium, protein) VALUES ($1, $2, $3, $4, $5) RETURNING id"
|
||||
err := s.db.QueryRow(queryString, n.Unit, n.Calories, n.Fat, n.Sodium, n.Protein).Scan(&n.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range n.Vitamins {
|
||||
err := VitaminService.AddVitamin(v, n)
|
||||
if err != nil {
|
||||
s.DeleteNutrition(n)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s nutritionService) UpdateNutrition(n *quartermaster.Nutrition) error {
|
||||
queryString := "UPDATE nutrition SET unit = $2, calories = $3, fat = $4, sodium = $5, protein = $6 WHERE id = $1"
|
||||
_, err := s.db.Exec(queryString, n.ID, n.Unit, n.Calories, n.Fat, n.Sodium, n.Protein)
|
||||
currVitamins, _ := VitaminService.GetNutritionVitamins(n)
|
||||
for _, v := range n.Vitamins {
|
||||
VitaminService.UpdateVitamin(v, n)
|
||||
}
|
||||
outloop:
|
||||
for _, v := range currVitamins {
|
||||
for _, w := range n.Vitamins {
|
||||
if v == w {
|
||||
continue outloop
|
||||
}
|
||||
}
|
||||
VitaminService.DeleteVitamin(v, n)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s nutritionService) DeleteNutrition(n *quartermaster.Nutrition) error {
|
||||
queryString := "DELETE FROM nutrition WHERE nutrition_id = $1"
|
||||
_, err := s.db.Exec(queryString, n.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
type vitaminService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (s vitaminService) Vitamin(vitamin quartermaster.VitaminType, n *quartermaster.Nutrition) (*quartermaster.Vitamin, error) {
|
||||
var v quartermaster.Vitamin
|
||||
queryString := "SELECT vitamin, amount FROM vitamins WHERE vitamin = $1 AND nutrition_id = $2"
|
||||
row := s.db.QueryRow(queryString, vitamin, n.ID)
|
||||
err := row.Scan(&v.Vitamin, &v.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func (s vitaminService) AddVitamin(v *quartermaster.Vitamin, n *quartermaster.Nutrition) error {
|
||||
queryString := "INSERT INTO vitamins (nutrition_id, vitamin, amount) VALUES ($1, $2, $3)"
|
||||
_, err := s.db.Exec(queryString, n.ID, v.Vitamin, v.Amount)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s vitaminService) UpdateVitamin(v *quartermaster.Vitamin, n *quartermaster.Nutrition) error {
|
||||
queryString := "INSERT INTO vitamins (nutrition_id, vitamin, amount) VALUES ($1, $2, $3) ON DUPLICATE KEY UPDATE amount = $3"
|
||||
_, err := s.db.Exec(queryString, n.ID, v.Vitamin, v.Amount)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s vitaminService) DeleteVitamin(v *quartermaster.Vitamin, n *quartermaster.Nutrition) error {
|
||||
queryString := "DELETE FROM vitamins WHERE vitamin = $1 AND nutrition_id = $2"
|
||||
_, err := s.db.Exec(queryString, v.Vitamin, n.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s vitaminService) GetNutritionVitamins(n *quartermaster.Nutrition) ([]*quartermaster.Vitamin, error) {
|
||||
var vitamins []*quartermaster.Vitamin
|
||||
queryString := "SELECT vitamin, amount FROM vitamins WHERE nutrition_id = $1"
|
||||
rows, err := s.db.Query(queryString, n.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var vitamin quartermaster.Vitamin
|
||||
err := rows.Scan(&vitamin.Vitamin, &vitamin.Amount)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
vitamins = append(vitamins, &vitamin)
|
||||
}
|
||||
return vitamins, nil
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/dustinpianalto/quartermaster"
|
||||
"github.com/dustinpianalto/quartermaster/internal/postgres"
|
||||
)
|
||||
|
||||
var (
|
||||
ItemService quartermaster.ItemService
|
||||
NutritionService quartermaster.NutritionService
|
||||
LocationService quartermaster.LocationService
|
||||
VitaminService quartermaster.VitaminService
|
||||
GroupService quartermaster.GroupService
|
||||
CategoryService quartermaster.CategoryService
|
||||
)
|
||||
|
||||
func InitServices() {
|
||||
ItemService = postgres.ItemService
|
||||
NutritionService = postgres.NutritionService
|
||||
LocationService = postgres.LocationService
|
||||
VitaminService = postgres.VitaminService
|
||||
GroupService = postgres.GroupService
|
||||
CategoryService = postgres.CategoryService
|
||||
log.Println("Services Initialized")
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package quartermaster
|
||||
|
||||
import "database/sql"
|
||||
|
||||
type Item struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Size float32 `json:"size"`
|
||||
Unit Unit `json:"unit"`
|
||||
Barcode sql.NullString `json:"barcode"`
|
||||
Nutrition *Nutrition `json:"nutrition,omitempty"`
|
||||
}
|
||||
|
||||
type ItemService interface {
|
||||
Item(int) (*Item, error)
|
||||
AddItem(*Item, *Location) (*Item, error)
|
||||
UpdateItem(*Item) error
|
||||
MoveItem(item *Item, old *Location, new *Location) error
|
||||
RemoveItem(*Item, *Location) error
|
||||
DeleteItem(*Item) error
|
||||
GetItemByBarcode(barcode string) (*Item, error)
|
||||
AddGroup(*Item, *Group) error
|
||||
AddCategory(*Item, *Category) error
|
||||
RemoveGroup(*Item, *Group) error
|
||||
RemoveCategory(*Item, *Category) error
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package quartermaster
|
||||
|
||||
type Location struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Parent *Location `json:"parent,omitempty"`
|
||||
}
|
||||
|
||||
type LocationService interface {
|
||||
Location(int) (*Location, error)
|
||||
AddLocation(*Location) (*Location, error)
|
||||
UpdateLocation(*Location) error
|
||||
DeleteLocation(*Location) error
|
||||
GetChildren(*Location) ([]*Location, error)
|
||||
GetItems(*Location) (map[*Item]int, error)
|
||||
GetTopLocations() ([]*Location, error)
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package quartermaster
|
||||
|
||||
type Nutrition struct {
|
||||
ID int `json:"id"`
|
||||
Unit Unit `json:"unit"`
|
||||
Calories float32 `json:"calories"`
|
||||
Fat float32 `json:"fat"`
|
||||
Sodium float32 `json:"sodium"`
|
||||
Protein float32 `json:"protein"`
|
||||
Vitamins []*Vitamin `json:"vitamins"`
|
||||
}
|
||||
|
||||
type Vitamin struct {
|
||||
Vitamin VitaminType `json:"vitamin"`
|
||||
Amount float32 `json:"amount"`
|
||||
}
|
||||
|
||||
type NutritionService interface {
|
||||
Nutrition(int) (*Nutrition, error)
|
||||
AddNutrition(*Nutrition) (*Nutrition, error)
|
||||
UpdateNutrition(*Nutrition) error
|
||||
DeleteNutrition(*Nutrition) error
|
||||
}
|
||||
|
||||
type VitaminService interface {
|
||||
Vitamin(VitaminType, *Nutrition) (*Vitamin, error)
|
||||
AddVitamin(*Vitamin, *Nutrition) error
|
||||
UpdateVitamin(*Vitamin, *Nutrition) error
|
||||
DeleteVitamin(*Vitamin, *Nutrition) error
|
||||
GetNutritionVitamins(*Nutrition) ([]*Vitamin, error)
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package quartermaster
|
||||
|
||||
type Unit int
|
||||
|
||||
const (
|
||||
Teaspoon Unit = iota
|
||||
Tablespoon
|
||||
Cup
|
||||
Ounce
|
||||
Gram
|
||||
Pound
|
||||
Individual
|
||||
)
|
||||
|
||||
func (u Unit) String() string {
|
||||
switch u {
|
||||
case Teaspoon:
|
||||
return "Teaspoon"
|
||||
case Tablespoon:
|
||||
return "Tablespoon"
|
||||
case Cup:
|
||||
return "Cup"
|
||||
case Ounce:
|
||||
return "Ounce"
|
||||
case Gram:
|
||||
return "Gram"
|
||||
case Pound:
|
||||
return "Pound"
|
||||
case Individual:
|
||||
return "Individual"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
type VitaminType int
|
||||
|
||||
const (
|
||||
A VitaminType = iota
|
||||
B
|
||||
C
|
||||
Iron
|
||||
)
|
||||
|
||||
func (v VitaminType) String() string {
|
||||
switch v {
|
||||
case A:
|
||||
return "A"
|
||||
case B:
|
||||
return "B"
|
||||
case C:
|
||||
return "C"
|
||||
case Iron:
|
||||
return "Iron"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
Loading…
Reference in new issue