diff options
| author | Přemysl Janouch <p@janouch.name> | 2019-04-15 01:10:48 +0200 | 
|---|---|---|
| committer | Přemysl Janouch <p@janouch.name> | 2019-04-15 02:40:55 +0200 | 
| commit | 37b7edb2efa819fe8b093157e9b26f671f390f95 (patch) | |
| tree | f26c401cca6426d9a7f754ba59302e0a8dc23c7d | |
| parent | 608ba10eecb1f5c02eb41b288d2af5ce2de3f4f5 (diff) | |
| download | sklad-37b7edb2efa819fe8b093157e9b26f671f390f95.tar.gz sklad-37b7edb2efa819fe8b093157e9b26f671f390f95.tar.xz sklad-37b7edb2efa819fe8b093157e9b26f671f390f95.zip  | |
sklad: create, update, remove
| -rw-r--r-- | cmd/sklad/container.tmpl | 16 | ||||
| -rw-r--r-- | cmd/sklad/db.go | 132 | ||||
| -rw-r--r-- | cmd/sklad/main.go | 125 | ||||
| -rw-r--r-- | cmd/sklad/series.tmpl | 14 | 
4 files changed, 262 insertions, 25 deletions
diff --git a/cmd/sklad/container.tmpl b/cmd/sklad/container.tmpl index 4bacae8..5bb5123 100644 --- a/cmd/sklad/container.tmpl +++ b/cmd/sklad/container.tmpl @@ -2,6 +2,22 @@  */}}{{ if .Container }}{{ .Container.Id }}{{ else }}Obaly{{ end }}{{ end }}  {{ define "Content" }} +{{ if .ErrorNoSuchSeries }} +<p>Chyba: Řada neexistuje. +{{ else if .ErrorContainerAlreadyExists }} +<p>Chyba: Obal s tímto ID už existuje. +{{ else if .ErrorNoSuchContainer }} +<p>Chyba: Obal neexistuje. +{{ else if .ErrorCannotChangeSeriesNotEmpty }} +<p>Chyba: Řadu u neprázdných obalů nelze měnit. +{{ else if .ErrorCannotChangeNumber }} +<p>Chyba: Číslo obalu v řadě nelze měnit. +{{ else if .ErrorContainerInUse }} +<p>Chyba: Obal se používá. +{{ else if .Error }} +<p>Chyba: {{ .Error }} +{{ end }} +  {{ if .Container }}  <section> diff --git a/cmd/sklad/db.go b/cmd/sklad/db.go index 0aba510..a6cdda8 100644 --- a/cmd/sklad/db.go +++ b/cmd/sklad/db.go @@ -14,6 +14,7 @@ import (  type Series struct {  	Prefix      string // PK: prefix  	Description string // what kind of containers this is for +	Counter     uint   // last used container number  }  func (s *Series) Containers() []*Container { @@ -71,9 +72,6 @@ var (  	labelFont *bdf.Font  ) -// TODO: Some functions to add, remove and change things in the database. -// Indexes must be kept valid, just like any invariants. -  func dbSearchSeries(query string) (result []*Series) {  	query = strings.ToLower(query)  	added := map[string]bool{} @@ -110,6 +108,134 @@ func dbSearchContainers(query string) (result []*Container) {  	return  } +var errInvalidPrefix = errors.New("invalid prefix") +var errSeriesAlreadyExists = errors.New("series already exists") +var errCannotChangePrefix = errors.New("cannot change the prefix") +var errNoSuchSeries = errors.New("no such series") +var errSeriesInUse = errors.New("series is in use") + +// Find and filter out the series in O(n). +func filterSeries(slice []*Series, s *Series) (filtered []*Series) { +	for _, series := range slice { +		if s != series { +			filtered = append(filtered, series) +		} +	} +	return +} + +func dbSeriesCreate(s *Series) error { +	if s.Prefix == "" { +		return errInvalidPrefix +	} +	if _, ok := indexSeries[s.Prefix]; ok { +		return errSeriesAlreadyExists +	} +	db.Series = append(db.Series, s) +	indexSeries[s.Prefix] = s +	return dbCommit() +} + +func dbSeriesUpdate(s *Series, updated Series) error { +	// It might be easily possible with no members, though this +	// is not reachable from the UI and can be solved by deletion. +	if updated.Prefix != s.Prefix { +		return errCannotChangePrefix +	} +	*s = updated +	return dbCommit() +} + +func dbSeriesRemove(s *Series) error { +	if len(s.Containers()) > 0 { +		return errSeriesInUse +	} + +	db.Series = filterSeries(db.Series, s) + +	delete(indexSeries, s.Prefix) +	delete(indexMembers, s.Prefix) +	return dbCommit() +} + +var errContainerAlreadyExists = errors.New("container already exists") +var errNoSuchContainer = errors.New("no such container") +var errCannotChangeSeriesNotEmpty = errors.New( +	"cannot change the series of a non-empty container") +var errCannotChangeNumber = errors.New("cannot change the number") +var errContainerInUse = errors.New("container is in use") + +// Find and filter out the container in O(n). +func filterContainer(slice []*Container, c *Container) (filtered []*Container) { +	for _, container := range slice { +		if c != container { +			filtered = append(filtered, container) +		} +	} +	return +} + +func dbContainerCreate(c *Container) error { +	if series, ok := indexSeries[c.Series]; !ok { +		return errNoSuchSeries +	} else if c.Number == 0 { +		c.Number = series.Counter +		for { +			c.Number++ +			if _, ok := indexContainer[c.Id()]; !ok { +				break +			} +		} +		series.Counter = c.Number +	} +	if _, ok := indexContainer[c.Id()]; ok { +		return errContainerAlreadyExists +	} +	if c.Parent != "" && indexContainer[c.Parent] == nil { +		return errNoSuchContainer +	} + +	db.Containers = append(db.Containers, c) + +	indexMembers[c.Series] = append(indexMembers[c.Series], c) +	indexChildren[c.Parent] = append(indexChildren[c.Parent], c) +	indexContainer[c.Id()] = c +	return dbCommit() +} + +func dbContainerUpdate(c *Container, updated Container) error { +	newId := updated.Id() +	if updated.Series != c.Series && len(c.Children()) > 0 { +		return errCannotChangeSeriesNotEmpty +	} +	if updated.Number != c.Number { +		return errCannotChangeNumber +	} +	if _, ok := indexContainer[newId]; ok && newId != c.Id() { +		return errContainerAlreadyExists +	} +	if updated.Parent != c.Parent { +		indexChildren[c.Parent] = filterContainer(indexChildren[c.Parent], c) +		indexChildren[newId] = append(indexChildren[newId], c) +	} +	*c = updated +	return dbCommit() +} + +func dbContainerRemove(c *Container) error { +	if len(indexChildren[c.Id()]) > 0 { +		return errContainerInUse +	} + +	db.Containers = filterContainer(db.Containers, c) +	indexMembers[c.Series] = filterContainer(indexMembers[c.Series], c) +	indexChildren[c.Parent] = filterContainer(indexChildren[c.Parent], c) + +	delete(indexContainer, c.Id()) +	delete(indexChildren, c.Id()) +	return dbCommit() +} +  func dbCommit() error {  	// Write a timestamp.  	e := json.NewEncoder(dbLog) diff --git a/cmd/sklad/main.go b/cmd/sklad/main.go index fd675b1..5dc2174 100644 --- a/cmd/sklad/main.go +++ b/cmd/sklad/main.go @@ -7,6 +7,7 @@ import (  	"log"  	"math/rand"  	"net/http" +	"net/url"  	"os"  	"path/filepath"  	"time" @@ -83,11 +84,40 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {  	http.Redirect(w, r, "/", http.StatusSeeOther)  } +func handleContainerPost(r *http.Request) error { +	id := ContainerId(r.FormValue("id")) +	description := r.FormValue("description") +	series := r.FormValue("series") +	parent := ContainerId(r.FormValue("parent")) +	_, remove := r.Form["remove"] + +	if container, ok := indexContainer[id]; ok { +		if remove { +			return dbContainerRemove(container) +		} else { +			c := *container +			c.Description = description +			c.Series = series +			return dbContainerUpdate(container, c) +		} +	} else if remove { +		return errNoSuchContainer +	} else { +		return dbContainerCreate(&Container{ +			Series:      series, +			Parent:      parent, +			Description: description, +		}) +	} +} +  func handleContainer(w http.ResponseWriter, r *http.Request) { +	var err error  	if r.Method == http.MethodPost { -		// TODO -	} -	if r.Method != http.MethodGet { +		err = handleContainerPost(r) +		// XXX: This is rather ugly. When removing, we want to keep +		// the context id, in addition to the id being changed. +	} else if r.Method != http.MethodGet {  		w.WriteHeader(http.StatusMethodNotAllowed)  		return  	} @@ -98,33 +128,70 @@ func handleContainer(w http.ResponseWriter, r *http.Request) {  	}  	var container *Container -	children := []*Container{} +	children := indexChildren[""] -	if id := ContainerId(r.FormValue("id")); id == "" { -		children = indexChildren[""] -	} else if c, ok := indexContainer[id]; ok { +	if c, ok := indexContainer[ContainerId(r.FormValue("id"))]; ok {  		children = c.Children()  		container = c  	}  	params := struct { -		Container *Container -		Children  []*Container -		AllSeries map[string]string +		Error                           error +		ErrorNoSuchSeries               bool +		ErrorContainerAlreadyExists     bool +		ErrorNoSuchContainer            bool +		ErrorCannotChangeSeriesNotEmpty bool +		ErrorCannotChangeNumber         bool +		ErrorContainerInUse             bool +		Container                       *Container +		Children                        []*Container +		AllSeries                       map[string]string  	}{ -		Container: container, -		Children:  children, -		AllSeries: allSeries, +		Error:                           err, +		ErrorNoSuchSeries:               err == errNoSuchSeries, +		ErrorContainerAlreadyExists:     err == errContainerAlreadyExists, +		ErrorNoSuchContainer:            err == errNoSuchContainer, +		ErrorCannotChangeSeriesNotEmpty: err == errCannotChangeSeriesNotEmpty, +		ErrorCannotChangeNumber:         err == errCannotChangeNumber, +		ErrorContainerInUse:             err == errContainerInUse, +		Container:                       container, +		Children:                        children, +		AllSeries:                       allSeries,  	}  	executeTemplate("container.tmpl", w, ¶ms)  } +func handleSeriesPost(r *http.Request) error { +	prefix := r.FormValue("prefix") +	description := r.FormValue("description") +	_, remove := r.Form["remove"] + +	if series, ok := indexSeries[prefix]; ok { +		if remove { +			return dbSeriesRemove(series) +		} else { +			s := *series +			s.Description = description +			return dbSeriesUpdate(series, s) +		} +	} else if remove { +		return errNoSuchSeries +	} else { +		return dbSeriesCreate(&Series{ +			Prefix:      prefix, +			Description: description, +		}) +	} +} +  func handleSeries(w http.ResponseWriter, r *http.Request) { +	var err error  	if r.Method == http.MethodPost { -		// TODO -	} -	if r.Method != http.MethodGet { +		err = handleSeriesPost(r) +		// XXX: This is rather ugly. +		r.Form = url.Values{} +	} else if r.Method != http.MethodGet {  		w.WriteHeader(http.StatusMethodNotAllowed)  		return  	} @@ -140,16 +207,30 @@ func handleSeries(w http.ResponseWriter, r *http.Request) {  	if prefix == "" {  	} else if series, ok := indexSeries[prefix]; ok {  		description = series.Description +	} else { +		err = errNoSuchSeries  	}  	params := struct { -		Prefix      string -		Description string -		AllSeries   map[string]*Series +		Error                    error +		ErrorInvalidPrefix       bool +		ErrorSeriesAlreadyExists bool +		ErrorCannotChangePrefix  bool +		ErrorNoSuchSeries        bool +		ErrorSeriesInUse         bool +		Prefix                   string +		Description              string +		AllSeries                map[string]*Series  	}{ -		Prefix:      prefix, -		Description: description, -		AllSeries:   allSeries, +		Error:                    err, +		ErrorInvalidPrefix:       err == errInvalidPrefix, +		ErrorSeriesAlreadyExists: err == errSeriesAlreadyExists, +		ErrorCannotChangePrefix:  err == errCannotChangePrefix, +		ErrorNoSuchSeries:        err == errNoSuchSeries, +		ErrorSeriesInUse:         err == errSeriesInUse, +		Prefix:                   prefix, +		Description:              description, +		AllSeries:                allSeries,  	}  	executeTemplate("series.tmpl", w, ¶ms) diff --git a/cmd/sklad/series.tmpl b/cmd/sklad/series.tmpl index 01e2539..0e31e0f 100644 --- a/cmd/sklad/series.tmpl +++ b/cmd/sklad/series.tmpl @@ -1,6 +1,20 @@  {{ define "Title" }}{{ or .Prefix "Řady" }}{{ end }}  {{ define "Content" }} +{{ if .ErrorInvalidPrefix }} +<p>Chyba: Neplatný prefix. +{{ else if .ErrorSeriesAlreadyExists }} +<p>Chyba: Řada s tímto prefixem už existuje. +{{ else if .ErrorCannotChangePrefix }} +<p>Chyba: Prefix nelze měnit. +{{ else if .ErrorNoSuchSeries }} +<p>Chyba: Řada neexistuje. +{{ else if .ErrorSeriesInUse }} +<p>Chyba: Řada se používá. +{{ else if .Error }} +<p>Chyba: {{ .Error }} +{{ end }} +  {{ if .Prefix }}  <h2>{{ .Prefix }}</h2>  | 
