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 }} +

Chyba: Řada neexistuje. +{{ else if .ErrorContainerAlreadyExists }} +

Chyba: Obal s tímto ID už existuje. +{{ else if .ErrorNoSuchContainer }} +

Chyba: Obal neexistuje. +{{ else if .ErrorCannotChangeSeriesNotEmpty }} +

Chyba: Řadu u neprázdných obalů nelze měnit. +{{ else if .ErrorCannotChangeNumber }} +

Chyba: Číslo obalu v řadě nelze měnit. +{{ else if .ErrorContainerInUse }} +

Chyba: Obal se používá. +{{ else if .Error }} +

Chyba: {{ .Error }} +{{ end }} + {{ if .Container }}

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 handleContainer(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - // TODO +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, + }) } - if r.Method != http.MethodGet { +} + +func handleContainer(w http.ResponseWriter, r *http.Request) { + var err error + if r.Method == http.MethodPost { + 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 handleSeries(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - // TODO +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, + }) } - if r.Method != http.MethodGet { +} + +func handleSeries(w http.ResponseWriter, r *http.Request) { + var err error + if r.Method == http.MethodPost { + 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 }} +

Chyba: Neplatný prefix. +{{ else if .ErrorSeriesAlreadyExists }} +

Chyba: Řada s tímto prefixem už existuje. +{{ else if .ErrorCannotChangePrefix }} +

Chyba: Prefix nelze měnit. +{{ else if .ErrorNoSuchSeries }} +

Chyba: Řada neexistuje. +{{ else if .ErrorSeriesInUse }} +

Chyba: Řada se používá. +{{ else if .Error }} +

Chyba: {{ .Error }} +{{ end }} + {{ if .Prefix }}

{{ .Prefix }}