aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p@janouch.name>2019-04-15 01:10:48 +0200
committerPřemysl Janouch <p@janouch.name>2019-04-15 02:40:55 +0200
commit37b7edb2efa819fe8b093157e9b26f671f390f95 (patch)
treef26c401cca6426d9a7f754ba59302e0a8dc23c7d
parent608ba10eecb1f5c02eb41b288d2af5ce2de3f4f5 (diff)
downloadsklad-37b7edb2efa819fe8b093157e9b26f671f390f95.tar.gz
sklad-37b7edb2efa819fe8b093157e9b26f671f390f95.tar.xz
sklad-37b7edb2efa819fe8b093157e9b26f671f390f95.zip
sklad: create, update, remove
-rw-r--r--cmd/sklad/container.tmpl16
-rw-r--r--cmd/sklad/db.go132
-rw-r--r--cmd/sklad/main.go125
-rw-r--r--cmd/sklad/series.tmpl14
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, &params)
}
+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, &params)
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>