Category data now includes their subcategories. Categories can now be requested by ID in a prettier way.

This commit is contained in:
2021-09-09 11:34:06 -05:00
parent 20a6365159
commit 871484a8e5
4 changed files with 147 additions and 57 deletions
+110 -10
View File
@@ -1,17 +1,18 @@
package main package main
type Category struct { type Category struct {
Id int32 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Subcategories []Subcategory `json:"subcategories"`
} }
type Subcategory struct { type Subcategory struct {
Id int32 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
CategoryId int32 `json:"categoryId"` CategoryId int64 `json:"categoryId"`
} }
func getAllCategories() []Category { func getAllCategories() (categories []Category, ok bool) {
db := getSQLConnection() db := getSQLConnection()
defer db.Close() defer db.Close()
@@ -19,22 +20,121 @@ func getAllCategories() []Category {
rows, err := db.Query("SELECT * FROM category;") rows, err := db.Query("SELECT * FROM category;")
if err != nil { if err != nil {
panic(err) //TODO: log this error
return nil, false
//panic(err)
} }
defer rows.Close() defer rows.Close()
var categories []Category
for rows.Next() { for rows.Next() {
var category Category var category Category
if err = rows.Scan(&category.Id, &category.Name); err != nil { if err = rows.Scan(&category.ID, &category.Name); err != nil {
println(err.Error()) println(err.Error())
} }
subcategories, ok := getSubcategories(category.ID)
if ok {
category.Subcategories = subcategories
}
categories = append(categories, category) categories = append(categories, category)
} }
return categories return categories, true
}
func insertNewCategory(name string) (recordID int32, ok bool) {
db := getSQLConnection()
defer db.Close()
err := db.QueryRow("INSERT INTO category (category_name) VALUES ($1) RETURNING category_id;", name).Scan(&recordID)
if err != nil {
//TODO: log this error
println(err.Error())
return 0, false
//panic(err)
}
return recordID, true
}
func deleteCategory(id int64) bool {
db := getSQLConnection()
defer db.Close()
_, err := db.Exec("DELETE FROM category WHERE category_id = $1;", id)
//TODO: log this if an error happened
if err != nil {
println(err.Error())
return false
}
return true
}
func getCategoryByID(id int64) (category Category, ok bool) {
db := getSQLConnection()
defer db.Close()
if err := db.QueryRow("SELECT * FROM category WHERE category_id = $1;", id).Scan(&category.ID, &category.Name); err != nil {
return category, false
}
subcategories, ok := getSubcategories(category.ID)
if !ok {
//TODO: do something about not getting the subcategories, for now ignore.
return category, true
}
category.Subcategories = subcategories
return category, true
}
// func getSubcategoryByID(id int64) (subcategory Subcategory, ok bool) {
// db := getSQLConnection()
// defer db.Close()
// if err := db.QueryRow("SELECT * FROM subcategory WHERE subcategory_id = $1;", id).Scan(&subcategory.ID, &subcategory.Name, &subcategory.CategoryId); err != nil {
// return subcategory, false
// }
// return subcategory, true
// }
func getSubcategories(categoryID int64) (subcategories []Subcategory, ok bool) {
db := getSQLConnection()
defer db.Close()
rows, err := db.Query("SELECT * FROM subcategory WHERE category_id = $1;", categoryID)
if err != nil {
//TODO: log this error
return nil, false
//panic(err)
}
defer rows.Close()
for rows.Next() {
var subcategory Subcategory
if err = rows.Scan(&subcategory.ID, &subcategory.Name, &subcategory.CategoryId); err != nil {
println(err.Error())
}
subcategories = append(subcategories, subcategory)
}
return subcategories, true
} }
+2
View File
@@ -7,6 +7,8 @@ import (
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
//TODO: maybe we can do the whole databaase connection things on application start and panic right away
//if we can't connect to the database.
func getSQLConnection() *sql.DB { func getSQLConnection() *sql.DB {
//Note: The postgres driver seems to get confused when no password is supplied, so omit it in the connection sting. //Note: The postgres driver seems to get confused when no password is supplied, so omit it in the connection sting.
//https://rajyavardhan.medium.com/when-you-get-relation-does-not-exist-in-postgres-7ffb0c3c674b //https://rajyavardhan.medium.com/when-you-get-relation-does-not-exist-in-postgres-7ffb0c3c674b
+1 -1
View File
@@ -2,4 +2,4 @@ module copyrightcrusader.org/inv
go 1.16 go 1.16
require github.com/lib/pq v1.10.2 // indirect require github.com/lib/pq v1.10.2
+34 -46
View File
@@ -1,18 +1,18 @@
package main package main
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
) )
//Go Best practices: https://github.com/golovers/effective-go //Go Best practices: https://github.com/golovers/effective-go
type Item struct { type Item struct {
Id int32 `json:"id"` ID int32 `json:"id"`
Name int32 `json:"name"` Name int32 `json:"name"`
ValueCents int32 `json:"valueCents"` ValueCents int32 `json:"valueCents"`
PurchaseCostCents int32 `json:"purchasedCostCents"` PurchaseCostCents int32 `json:"purchasedCostCents"`
@@ -27,8 +27,11 @@ type Item struct {
func main() { func main() {
http.HandleFunc("/categories", categories) http.HandleFunc("/categories", categories)
http.HandleFunc("/category", category) //Note: seems adding a trailing slash allows routing URLs like /category/ID#
err := http.ListenAndServe(":8080", nil) //No need for query parameters.
http.HandleFunc("/category/", category)
err := http.ListenAndServe(":8088", nil)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -39,7 +42,7 @@ func main() {
func categories(w http.ResponseWriter, r *http.Request) { func categories(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case "GET": case "GET":
getCategories(w, r) getCategoriesHandler(w, r)
default: default:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("Method not allowed")) w.Write([]byte("Method not allowed"))
@@ -49,19 +52,19 @@ func categories(w http.ResponseWriter, r *http.Request) {
func category(w http.ResponseWriter, r *http.Request) { func category(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case "GET": case "GET":
getCategory(w, r) getCategoryHandler(w, r)
case "POST": case "POST":
insertCategory(w, r) insertCategoryHandler(w, r)
case "DELETE": case "DELETE":
deleteCategory(w, r) deleteCategoryHandler(w, r)
default: default:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("Method not allowed")) w.Write([]byte("Method not allowed"))
} }
} }
func getCategory(w http.ResponseWriter, r *http.Request) { func getCategoryHandler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id") id := strings.TrimPrefix(r.URL.Path, "/category/")
value, err := strconv.ParseInt(id, 0, 64) value, err := strconv.ParseInt(id, 0, 64)
@@ -71,19 +74,11 @@ func getCategory(w http.ResponseWriter, r *http.Request) {
return return
} }
db := getSQLConnection() category, ok := getCategoryByID(value)
defer db.Close() if !ok {
w.WriteHeader(http.StatusInternalServerError)
var category Category w.Write([]byte("Failed to get category."))
if err := db.QueryRow("SELECT * FROM category WHERE category_id = $1;", value).Scan(&category.Id, &category.Name); err != nil {
if err == sql.ErrNoRows {
w.Write([]byte("No rows found"))
return
}
w.Write([]byte(err.Error()))
return return
} }
@@ -99,8 +94,8 @@ func getCategory(w http.ResponseWriter, r *http.Request) {
w.Write(jsonBytes) w.Write(jsonBytes)
} }
func deleteCategory(w http.ResponseWriter, r *http.Request) { func deleteCategoryHandler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id") id := strings.TrimPrefix(r.URL.Path, "/category/")
value, err := strconv.ParseInt(id, 0, 64) value, err := strconv.ParseInt(id, 0, 64)
@@ -110,24 +105,23 @@ func deleteCategory(w http.ResponseWriter, r *http.Request) {
return return
} }
db := getSQLConnection() if !deleteCategory(value) {
defer db.Close()
_, err = db.Exec("DELETE FROM category WHERE category_id = $1;", value)
if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error())) w.Write([]byte(fmt.Sprintf("Failed to delete %d", value)))
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("Deleted category '%d'.", value)))
} }
func getCategories(w http.ResponseWriter, r *http.Request) { func getCategoriesHandler(w http.ResponseWriter, r *http.Request) {
categories := getAllCategories() categories, ok := getAllCategories()
if !ok {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Failed to fetch a list of categories."))
return
}
jsonBytes, err := json.Marshal(categories) jsonBytes, err := json.Marshal(categories)
@@ -141,12 +135,12 @@ func getCategories(w http.ResponseWriter, r *http.Request) {
w.Write(jsonBytes) w.Write(jsonBytes)
} }
func insertCategory(w http.ResponseWriter, r *http.Request) { func insertCategoryHandler(w http.ResponseWriter, r *http.Request) {
ct := r.Header.Get("content-type") ct := r.Header.Get("content-type")
if ct != "application/json" { if ct != "application/json" {
w.WriteHeader(http.StatusUnsupportedMediaType) w.WriteHeader(http.StatusUnsupportedMediaType)
w.Write([]byte(fmt.Sprintf("Unsupported media type '%s'", ct))) w.Write([]byte(fmt.Sprintf("Unsupported media type '%s', expected application/json", ct)))
return return
} }
@@ -170,17 +164,11 @@ func insertCategory(w http.ResponseWriter, r *http.Request) {
return return
} }
db := getSQLConnection() id, ok := insertNewCategory(category.Name)
defer db.Close() if !ok {
id := 0
err = db.QueryRow("INSERT INTO category (category_name) VALUES ($1) RETURNING category_id;", category.Name).Scan(&id)
if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error())) w.Write([]byte("Failed to create the new category."))
return return
} }