diff --git a/db/entities.go b/db/entities.go index e2c2798..2f71a02 100644 --- a/db/entities.go +++ b/db/entities.go @@ -20,28 +20,46 @@ const ( Other ) -// entities -type Item struct { - ID int - Name string - Notes *string - Description *string - Stage PackingStage - Category Category +var CategoryMap = map[Category]string{ + Bedroom: "Bedroom", + Bathroom: "Bathroom", + Kitchen: "Kitchen", + Office: "Office", + LivingRoom: "Living Room", + Other: "Other", } -type Box struct { - ID int - Name string - Notes *string - Description *string - Stage PackingStage - Category Category +var PackingStageMap = map[PackingStage]string{ + Essentials: "Essentials", + StageOne: "Stage One", + StageTwo: "Stage Two", + StageThree: "Stage Three", } +type EntityLabel string + +const ( + ItemType EntityLabel = "items" + BoxType EntityLabel = "boxes" + BoxItemType EntityLabel = "box_items" +) + +type Entity struct { + ID int + EntityLabel EntityLabel + Name string + Notes *string + Description *string + Stage PackingStage + Category Category +} + +type Item Entity +type Box Entity + // joins type BoxItem struct { - ID int - BoxID int - ItemID int + ID int + BoxID int + ItemID int } diff --git a/db/seed.go b/db/seed.go index 6aeddb6..4a6a94a 100644 --- a/db/seed.go +++ b/db/seed.go @@ -58,12 +58,12 @@ func GetSeedData() (items []Item, boxes []Box, boxitems []BoxItem) { func CreateTables(client *sql.DB) (int64, error) { script, err := os.ReadFile("/home/mikayla/go/go-htmx-tailwind-example/db/seed.sql") if err != nil { - panic(err) + return -1, err } result, err := client.Exec(string(script)) if err != nil { - panic(err) + return -1, err } return result.RowsAffected() @@ -86,6 +86,11 @@ func SeedDB() (int64, error) { for i := range(items) { _, err := PostItem(items[i]) if err != nil { + // ignore unique constraint violations and continue + if err.Error() == "UNIQUE constraint failed: items.Name" { + continue + } + return -1, err } insertCount++ @@ -94,6 +99,11 @@ func SeedDB() (int64, error) { for i := range(boxes) { _, err := PostBox(boxes[i]) if err != nil { + // ignore unique constraint violations and continue + if err.Error() == "UNIQUE constraint failed: boxes.Name" { + continue + } + return -1, err } insertCount++ diff --git a/db/sql.go b/db/sql.go index f2def63..ac3e8ff 100644 --- a/db/sql.go +++ b/db/sql.go @@ -2,13 +2,33 @@ package db import ( "database/sql" + "encoding/json" ) func CreateClient() (db *sql.DB, err error) { return sql.Open("sqlite3", "./example.db") } -func GetAllItems() (result []Item, err error) { +func GetAllItems() (rows *sql.Rows, err error) { + db, err := CreateClient() + if err != nil { + return + } + + defer db.Close() + + rows, err = db.Query("SELECT * FROM items") + if err != nil { + return + } + + // fmt.Println("rows", rows) + + defer rows.Close() + return +} + +func GetAll(table EntityLabel) (rows *sql.Rows, err error) { db, err := CreateClient() if err != nil { return nil, err @@ -16,46 +36,50 @@ func GetAllItems() (result []Item, err error) { defer db.Close() - rows, err := db.Query("SELECT * FROM items") + rows, err = db.Query("SELECT * FROM ?", table) if err != nil { return nil, err } defer rows.Close() - - for rows.Next() { - item := Item{} - - err = rows.Scan(&item.ID, &item.Name, &item.Notes, &item.Description, &item.Stage, &item.Category) - - if err != nil { - return nil, err - } - - result = append(result, item) - } - return } -func GetItemByID(id int) (item Item, err error) { +func GetByID(table EntityLabel, id int) (row *sql.Row, err error) { db, err := CreateClient() if err != nil { - return Item{}, err + return nil, err } defer db.Close() - row := db.QueryRow("SELECT * FROM items WHERE id = ?", id) - err = row.Scan(&item.ID, &item.Name, &item.Notes, &item.Description, &item.Stage, &item.Category) - + row = db.QueryRow("SELECT * FROM ? WHERE id = ?", table, id) return } -func PostItem(item Item) (int64, error) { +func Put[T Entity](table EntityLabel, record Entity) (sql.Result, error) { db, err := CreateClient() if err != nil { - return -1, err + return nil, err + } + + defer db.Close() + + query := `UPDATE ? SET name = ?, notes = ?, description = ?, stage = ?, category = ? WHERE id = ?` + + result, err := db.Exec(query, table, record.Name, record.Notes, record.Description, record.Stage, record.Category, record.ID) + + if err != nil { + return nil, err + } + + return result, nil +} + +func PostItem(record Item) (sql.Result, error) { + db, err := CreateClient() + if err != nil { + return nil, err } defer db.Close() @@ -63,37 +87,16 @@ func PostItem(item Item) (int64, error) { query := `INSERT INTO items (name, notes, description, stage, category) VALUES (?, ?, ?, ?, ?)` - result, err := db.Exec(query, item.Name, item.Notes, item.Description, item.Stage, item.Category) + result, err := db.Exec(query, record.Name, record.Notes, record.Description, record.Stage, record.Category) if err != nil { - return -1, err + return nil, err } - return result.LastInsertId() + return result, nil } - - -func PostBox(box Box) (int64, error) { - db, err := CreateClient() - if err != nil { - return -1, err - } - - defer db.Close() - - query := `INSERT INTO boxes (name, notes, description, stage, category) VALUES (?, ?, ?, ?, ?)` - - result, err := db.Exec(query, box.Name, box.Notes, box.Description, box.Stage, box.Category) - - if err != nil { - return -1, err - } - - return result.LastInsertId() -} - -func GetAllBoxes() (result []Box, err error) { +func PostBox(record Box) (sql.Result, error) { db, err := CreateClient() if err != nil { return nil, err @@ -101,34 +104,50 @@ func GetAllBoxes() (result []Box, err error) { defer db.Close() - rows, err := db.Query("SELECT * FROM boxes") + query := `INSERT INTO boxes (name, notes, description, stage, category) + VALUES (?, ?, ?, ?, ?)` + + result, err := db.Exec(query, record.Name, record.Notes, record.Description, record.Stage, record.Category) + if err != nil { return nil, err } - defer rows.Close() + return result, nil +} - for rows.Next() { - box := Box{} - - err = rows.Scan(&box.ID, &box.Name, &box.Notes, &box.Description, &box.Stage, &box.Category) - - if err != nil { - return nil, err - } - - result = append(result, box) +func Delete(table EntityLabel, id int) (sql.Result, error) { + db, err := CreateClient() + if err != nil { + return nil, err } + defer db.Close() + + query := `DELETE FROM ? WHERE id = ?` + + return db.Exec(query, table, id) +} + +func ParseItem(item *Item, scan func(dest ...any) error) (err error) { + return scan(&item.ID, &item.Name, &item.Notes, &item.Description, &item.Stage, &item.Category) +} + +func ParseBox(box *Box, scan func(dest ...any) error) error { + return scan(&box.ID, &box.Name, &box.Notes, &box.Description, &box.Stage, &box.Category) +} + +func ParseEntityFromBytes(b []byte) (entity Entity, err error) { + err = json.Unmarshal(b, &entity) return } -// func PostBoxItem(itemid int, boxid int) (int64, error) { -// db, err := CreateClient() -// if err != nil { -// return -1, err -// } +func ParseItemFromBytes(b []byte) (item Item, err error) { + err = json.Unmarshal(b, &item) + return +} -// defer db.Close() -// // query := -// } +func ParseBoxFromBytes(b []byte) (box Box, err error) { + err = json.Unmarshal(b, &box) + return +} diff --git a/main.go b/main.go index bf8e72d..747cedb 100644 --- a/main.go +++ b/main.go @@ -34,10 +34,14 @@ func main() { //exit process immediately upon sigterm handleSigTerms() - db.SeedDB() + i, err := db.SeedDB() + if err != nil { + panic(err) + } + + fmt.Printf("seeded db with %d records\n", i) //parse templates - var err error html, err = web.TemplateParseFSRecursive(templateFS, ".html", true, nil) if err != nil { panic(err) @@ -52,7 +56,11 @@ func main() { router.Handle("/", web.Action(routes.HomePage)) router.Handle("/items", web.Action(routes.Items(html).GetAll)) + router.Handle("/items/{id}", web.Action(routes.Items(html).GetByID)) router.Handle("/boxes", web.Action(routes.Boxes(html).GetAll)) + router.Handle("/unrelated", web.Action(func(r *http.Request) *web.Response { + return web.HTML(http.StatusOK, html, "row-edit.html", nil, nil) + })) //logging/tracing nextRequestID := func() string { diff --git a/routes/base.go b/routes/base.go index f304360..af41d40 100644 --- a/routes/base.go +++ b/routes/base.go @@ -3,17 +3,10 @@ package routes import ( "net/http" + "github.com/innocuous-symmetry/moving-mgmt/db" "github.com/jritsema/gotoolbox/web" ) -type Entity int - -const ( - Item Entity = iota - Box - BoxItem -) - type RouterActions struct { GetAll func(r *http.Request) *web.Response GetByID func(r *http.Request) *web.Response @@ -23,8 +16,7 @@ type RouterActions struct { } type Router struct { - Entity Entity - Path string + Entity db.EntityLabel GetAll func(r *http.Request) *web.Response GetByID func(r *http.Request) *web.Response Post func(r *http.Request) *web.Response @@ -32,10 +24,9 @@ type Router struct { Delete func(r *http.Request) *web.Response } -func NewRouter(entity Entity, path string, actions RouterActions) *Router { +func NewRouter(entity db.EntityLabel, actions RouterActions) *Router { return &Router{ Entity: entity, - Path: path, GetAll: actions.GetAll, GetByID: actions.GetByID, Post: actions.Post, diff --git a/routes/boxes.go b/routes/boxes.go index c2a2414..b837ae3 100644 --- a/routes/boxes.go +++ b/routes/boxes.go @@ -12,8 +12,7 @@ func Boxes(_html *template.Template) *Router { html = _html return NewRouter( - Box, - "/boxes", + "boxes", RouterActions{ GetAll: GetAllBoxes, GetByID: nil, @@ -25,11 +24,23 @@ func Boxes(_html *template.Template) *Router { } func GetAllBoxes(_ *http.Request) *web.Response { - result, err := db.GetAllBoxes() + result, err := db.GetAll("boxes") if err != nil { return web.Error(http.StatusBadRequest, err, nil) } + boxes := []db.Box{} + + for result.Next() { + box := db.Box{} + err = db.ParseBox(&box, result.Scan) + if err != nil { + return web.Error(http.StatusInternalServerError, err, nil) + } + + boxes = append(boxes, box) + } + return web.HTML( http.StatusOK, html, diff --git a/routes/index.go b/routes/index.go index b2dc6e6..560cd8b 100644 --- a/routes/index.go +++ b/routes/index.go @@ -1,8 +1,10 @@ package routes import ( + "fmt" "html/template" "net/http" + // "github.com/innocuous-symmetry/moving-mgmt/" db "github.com/innocuous-symmetry/moving-mgmt/db" "github.com/jritsema/gotoolbox/web" @@ -12,15 +14,32 @@ var html *template.Template func HomePage(r *http.Request) *web.Response { result, err := db.GetAllItems() + + fmt.Println(result) + if err != nil { - panic(err) + return web.Error(http.StatusNotFound, err, nil) + } + + items := []db.Item{} + for result.Next() { + item := db.Item{} + err = db.ParseItem(&item, result.Scan) + + fmt.Println("name", item.Name) + + if err != nil { + return web.Error(http.StatusInternalServerError, err, nil) + } + + items = append(items, item) } return web.HTML( http.StatusOK, html, "index.html", - result, + items, nil, ) } diff --git a/routes/items.go b/routes/items.go index 1f4018f..490fa5d 100644 --- a/routes/items.go +++ b/routes/items.go @@ -1,11 +1,13 @@ package routes import ( + "fmt" "html/template" "net/http" "strconv" db "github.com/innocuous-symmetry/moving-mgmt/db" + "github.com/innocuous-symmetry/moving-mgmt/util" "github.com/jritsema/gotoolbox/web" ) @@ -13,11 +15,10 @@ func Items(_html *template.Template) *Router { html = _html return NewRouter( - Item, - "/items", + "items", RouterActions{ GetAll: GetAllItems, - GetByID: nil, + GetByID: GetItemByID, Post: nil, Put: nil, Delete: nil, @@ -26,11 +27,26 @@ func Items(_html *template.Template) *Router { } func GetAllItems(_ *http.Request) *web.Response { - result, err := db.GetAllItems() + result, err := db.GetAll("items") if err != nil { - panic(err) + return web.Error(http.StatusNotFound, err, nil) } + items := []db.Item{} + + for result.Next() { + item := db.Item{} + err = db.ParseItem(&item, result.Scan) + if err != nil { + fmt.Println(err.Error()) + return web.Error(http.StatusInternalServerError, err, nil) + } + + items = append(items, item) + } + + fmt.Println("items", items) + return web.HTML( http.StatusOK, html, @@ -41,14 +57,57 @@ func GetAllItems(_ *http.Request) *web.Response { } func GetItemByID(r *http.Request) *web.Response { - var id int - id, err := strconv.Atoi(r.URL.Query().Get("id")) + id, err := util.GetIDFromPath(r) if err != nil { return web.Error(http.StatusBadRequest, err, nil) } - result, err := db.GetItemByID(id) + editMode, err := strconv.ParseBool(r.URL.Query().Get("edit")) + if err != nil { + return web.Error(http.StatusBadRequest, err, nil) + } + res, err := db.GetByID("items", id) + + item := db.Item{} + err = db.ParseItem(&item, res.Scan) + if err != nil { + return web.Error(http.StatusInternalServerError, err, nil) + } + + var tmpl string + + if editMode { + tmpl = "entity-edit.html" + } else { + tmpl = "entity-row.html" + } + + return web.HTML( + http.StatusOK, + html, + tmpl, + item, + nil, + ) +} + +func PutItem(r *http.Request) *web.Response { + body := r.Body + defer body.Close() + + bodyBytes := make([]byte, r.ContentLength) + _, err := body.Read(bodyBytes) + if err != nil { + return web.Error(http.StatusInternalServerError, err, nil) + } + + item, err := db.ParseEntityFromBytes(bodyBytes) + if err != nil { + return web.Error(http.StatusBadRequest, err, nil) + } + + result, err := db.Put("items", item) if err != nil { return web.Error(http.StatusInternalServerError, err, nil) } @@ -56,7 +115,7 @@ func GetItemByID(r *http.Request) *web.Response { return web.HTML( http.StatusOK, html, - "item-by-id.html", + "entity-row.html", result, nil, ) diff --git a/templates/entity-row.html b/templates/entity-row.html index 9469adb..037b147 100644 --- a/templates/entity-row.html +++ b/templates/entity-row.html @@ -5,4 +5,14 @@ {{.Category}} {{.Description}} {{.Notes}} + + diff --git a/templates/index.html b/templates/index.html index e7d8f42..6b84b50 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ - Go + HTMX + Tailwind + Mikayla's Move Manager
@@ -34,7 +34,9 @@
+ Items +
{{ template "entity-list.html" . }}
diff --git a/templates/row-edit.html b/templates/row-edit.html index cb9e5ee..9e75f7a 100644 --- a/templates/row-edit.html +++ b/templates/row-edit.html @@ -5,8 +5,8 @@ type="text" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" data-include-edit="{{.ID}}" - name="company" - value="{{.Company}}" + name="Name" + value="{{.Name}}" /> @@ -14,8 +14,8 @@ type="text" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" data-include-edit="{{.ID}}" - name="contact" - value="{{.Contact}}" + name="stage" + value="{{.Stage}}" /> @@ -23,13 +23,31 @@ type="text" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" data-include-edit="{{.ID}}" - name="country" - value="{{.Country}}" + name="Category" + value="{{.Category}}" + /> + + + + + +