~hokiegeek/teadb

1b4b0a6acccf05dc669c1b38046c201761bcf229 — HokieGeek 5 months ago 87e0f08 v8.0.0
Added progenitor field and did some refactoring
4 files changed, 173 insertions(+), 72 deletions(-)

A cmd/dupefinder/main.go
M cmd/teadbd/main.go
M cmd/teas/main.go
M gcp.go
A cmd/dupefinder/main.go => cmd/dupefinder/main.go +83 -0
@@ 0,0 1,83 @@
package main

import (
	"flag"
	"fmt"
	"strings"

	"git.sr.ht/~hokiegeek/teadb"
)

func main() {
	projectIDPtr := flag.String("project", "", "Need to know the project name")
	flag.Parse()

	db, err := teadb.New(*projectIDPtr)
	if err != nil {
		panic(err)
	}

	teas, err := db.AllTeas()
	if err != nil {
		panic(err)
	}

	candidates := make(map[string][]teadb.Tea)
	for _, t := range teas {
		k := fmt.Sprintf("%s %s %s %d", strings.ToLower(t.Purchaselocation), strings.ToLower(t.Type), strings.ToLower(t.Name), t.Year)
		if _, ok := candidates[k]; !ok {
			candidates[k] = make([]teadb.Tea, 0)
		}
		candidates[k] = append(candidates[k], t)
	}

	type caught struct {
		tea teadb.Tea
		err error
	}
	catcher := make([]caught, 0)

	for k, teas := range candidates {
		switch {
		case len(teas) == 1:
			teas[0].Progenitor = -1
			if err := db.UpdateTea(teas[0]); err != nil {
				catcher = append(catcher, caught{teas[0], err})
			}
		case len(teas) > 1:
			fmt.Println(k)
			parent := teas[0].ID
			for _, tea := range teas {
				if tea.ID <= parent {
					parent = tea.ID
				}
			}

			for _, tea := range teas {
				switch {
				case tea.ID == parent:
					tea.Progenitor = -1
					fmt.Printf("\t%d> %s (parent)\n", tea.ID, tea.Name)
				default:
					tea.Progenitor = parent
					fmt.Printf("\t%d> %s (%d)\n", tea.ID, tea.Name, parent)
				}
				if err := db.UpdateTea(tea); err != nil {
					catcher = append(catcher, caught{tea, err})
				}
			}
		}
	}

	/*
		for _, teas := range candidates {
			if len(teas) == 1 {
				fmt.Printf("ORPHAN? %d> %s\n", teas[0].ID, teas[0].Name)
			}
		}
	*/

	for _, c := range catcher {
		fmt.Printf("error: could not resolve (%d) %s: %v\n", c.tea.ID, c.tea.Name, c.err)
	}
}

M cmd/teadbd/main.go => cmd/teadbd/main.go +65 -42
@@ 61,7 61,7 @@ func main() {
	exposedOk := handlers.ExposedHeaders([]string{"Content-Type", "Content-Length", "Accept-Encoding", "Authorization", "Etag"})
	methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"})

	http.ListenAndServe(fmt.Sprintf(":%d", *portPtr), handlers.CORS(originsOk, headersOk, methodsOk, exposedOk)(r))
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *portPtr), handlers.CORS(originsOk, headersOk, methodsOk, exposedOk)(r)))
}

func getAllTeasHandler(w http.ResponseWriter, r *http.Request, db *teadb.GcpClient, cache *teadb.Cache) {


@@ 122,6 122,7 @@ func teaHandler(w http.ResponseWriter, r *http.Request, db *teadb.GcpClient, cac

	switch r.Method {
	case http.MethodHead:
		return
	case http.MethodGet:
		tea, valid := cache.Tea(id)
		if !valid {


@@ 141,40 142,54 @@ func teaHandler(w http.ResponseWriter, r *http.Request, db *teadb.GcpClient, cac
			w.WriteHeader(http.StatusConflict)
			return
		}
		if tea, err := readTea(); err == nil {
			if err = db.CreateTea(tea); err != nil {
				w.WriteHeader(http.StatusInternalServerError)
			} else {
				cache.Invalidate()
				w.WriteHeader(http.StatusCreated)
			}

		tea, err := readTea()
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if err = db.CreateTea(tea); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		cache.Invalidate()
		w.WriteHeader(http.StatusCreated)
	case http.MethodPut:
		// Update existing TEA
		if _, err := db.TeaByID(id); err != nil {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if tea, err := readTea(); err == nil {
			if err = db.UpdateTea(tea); err != nil {
				w.WriteHeader(http.StatusInternalServerError)
			} else {
				cache.Invalidate()
				w.WriteHeader(http.StatusOK)
			}

		tea, err := readTea()
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if err = db.UpdateTea(tea); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		cache.Invalidate()
		w.WriteHeader(http.StatusOK)
	case http.MethodDelete:
		// Delete existing TEA
		if _, err := db.TeaByID(id); err != nil {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		if err = db.DeleteTea(id); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
		} else {
			cache.Invalidate()
			w.WriteHeader(http.StatusOK)
			return
		}

		cache.Invalidate()
		w.WriteHeader(http.StatusOK)
	}
}



@@ 229,6 244,7 @@ func entryHandler(w http.ResponseWriter, r *http.Request, db *teadb.GcpClient, c

	switch r.Method {
	case http.MethodHead:
		return
	case http.MethodGet:
		for _, teaEntry := range tea.Entries {
			if entryid == teaEntry.Datetime.UnixNano() {


@@ 238,24 254,34 @@ func entryHandler(w http.ResponseWriter, r *http.Request, db *teadb.GcpClient, c
		}
		w.WriteHeader(http.StatusNotFound)
	case http.MethodPost:
		if entry, err := readEntry(); err == nil {
			if err = db.CreateEntry(tea.ID, entry); err != nil {
				http.Error(w, "error creating new entry", http.StatusInternalServerError)
			} else {
				cache.Invalidate()
				w.WriteHeader(http.StatusCreated)
			}
		entry, err := readEntry()
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if err = db.CreateEntry(tea.ID, entry); err != nil {
			http.Error(w, "error creating new entry", http.StatusInternalServerError)
			return
		}

		cache.Invalidate()
		w.WriteHeader(http.StatusCreated)
	case http.MethodPut:
		// Update existing tea entry
		if entry, err := readEntry(); err == nil {
			if err = db.UpdateEntry(tea.ID, entry); err != nil {
				http.Error(w, "error updating entry", http.StatusInternalServerError)
			} else {
				cache.Invalidate()
				w.WriteHeader(http.StatusOK)
			}
		entry, err := readEntry()
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if err = db.UpdateEntry(tea.ID, entry); err != nil {
			http.Error(w, "error updating entry", http.StatusInternalServerError)
			return
		}

		cache.Invalidate()
		w.WriteHeader(http.StatusOK)
	case http.MethodDelete:
		for i, teaEntry := range tea.Entries {
			if entryid == teaEntry.Datetime.Unix() {


@@ 266,22 292,24 @@ func entryHandler(w http.ResponseWriter, r *http.Request, db *teadb.GcpClient, c

		if err = db.UpdateTea(tea); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
		} else {
			cache.Invalidate()
			w.WriteHeader(http.StatusOK)
			return
		}

		cache.Invalidate()
		w.WriteHeader(http.StatusOK)
	}
}

func postJSON(w http.ResponseWriter, r *http.Request, payload interface{}) {
	JSON, err := json.Marshal(payload)
	buf, err := json.Marshal(payload)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	hash := md5.Sum(buf)
	sum := hex.EncodeToString(hash[:])
	requestedSum := r.Header.Get("If-None-Match")
	sum := checksum(JSON)
	if requestedSum == sum {
		w.WriteHeader(http.StatusNotModified)
		return


@@ 300,8 328,3 @@ func postJSON(w http.ResponseWriter, r *http.Request, payload interface{}) {
		http.Error(w, "error sending payload", http.StatusInternalServerError)
	}
}

func checksum(body []byte) string {
	hash := md5.Sum(body)
	return hex.EncodeToString(hash[:])
}

M cmd/teas/main.go => cmd/teas/main.go +18 -17
@@ 6,23 6,17 @@ import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"git.sr.ht/~hokiegeek/teadb"
)

func main() {
	// Command flags: load
	loadCommand := flag.NewFlagSet("load", flag.ExitOnError)
	loadFilePtr := loadCommand.String("file", "", "The filename to use")

	// Command flags: save
	saveCommand := flag.NewFlagSet("save", flag.ExitOnError)
	saveFilePtr := saveCommand.String("file", "", "The filename to use")

	filePtr := flag.String("file", "", "The filename to use")
	projectIDPtr := flag.String("project", "", "Need to know the project name")

	command := os.Args[1]
	flag.Parse()

	command := flag.Arg(0)

	db, err := teadb.New(*projectIDPtr)
	if err != nil {


@@ 31,13 25,11 @@ func main() {

	switch command {
	case "load":
		loadCommand.Parse(os.Args[2:])
		if err := loadFromJSON(*loadFilePtr, db); err != nil {
		if err := loadFromJSON(*filePtr, db); err != nil {
			panic(err)
		}
	case "save":
		saveCommand.Parse(os.Args[2:])
		if err := saveToFile(*saveFilePtr, db); err != nil {
		if err := saveToFile(*filePtr, db); err != nil {
			panic(err)
		}
	case "purge":


@@ 61,12 53,21 @@ func loadFromJSON(filename string, db *teadb.GcpClient) error {
}

func createTeas(teas []teadb.Tea, db *teadb.GcpClient) error {
	type caught struct {
		tea teadb.Tea
		err error
	}
	catcher := make([]caught, 0)
	for _, tea := range teas {
		if err := db.CreateTea(tea); err != nil {
			fmt.Printf("Could not create tea %d: %v\n", tea.ID, err)
		} else {
			fmt.Printf("Tea (%d): %s\n", tea.ID, tea.Name)
			catcher = append(catcher, caught{tea, err})
			continue
		}
		fmt.Printf("Tea (%d): %s\n", tea.ID, tea.Name)
	}

	for _, c := range catcher {
		fmt.Printf("error: could not create tea (%d) %s: %v\n", c.tea.ID, c.tea.Name, c.err)
	}

	return nil

M gcp.go => gcp.go +7 -13
@@ 53,6 53,7 @@ type Tea struct {
	Packaging        string     `json:"packaging"`
	Sample           bool       `json:"sample"`
	Entries          []TeaEntry `json:"entries"`
	Progenitor       int        `json:"progenitor"`
}

// GcpClient is the client struct


@@ 67,20 68,22 @@ func teaKey(id int) *datastore.Key {

// CreateTea creates a new tea entity
func (c *GcpClient) CreateTea(tea Tea) error {
	// TODO: validate?
	return c.saveTea(tea)
}

// UpdateTea updates a new tea entity
func (c *GcpClient) UpdateTea(tea Tea) error {
	// TODO: validate?
	return c.saveTea(tea)
}

// DeleteTea deletes an existing tea
func (c *GcpClient) DeleteTea(teaID int) error {
	// TODO: validate?
	return c.removeTea(teaID)
	// Saves the new entity.
	if err := c.client.Delete(c.ctx, teaKey(teaID)); err != nil {
		return fmt.Errorf("failed to remove tea: %v", err)
	}

	return nil
}

// CreateEntry creates a new entry on an existing tea


@@ 159,15 162,6 @@ func (c *GcpClient) saveTea(tea Tea) error {
	return nil
}

func (c *GcpClient) removeTea(teaID int) error {
	// Saves the new entity.
	if err := c.client.Delete(c.ctx, teaKey(teaID)); err != nil {
		return fmt.Errorf("failed to remove tea: %v", err)
	}

	return nil
}

// New creates a new GcpClient
func New(projectID string) (c *GcpClient, err error) {
	c = new(GcpClient)