api.spaceplanner.app

Spaceplanner API
git clone git://jacobedwards.org/api.spaceplanner.app
Log | Files | Refs

commit 109fd2eb896c4d5d6c05d3de6850b830106b1237
parent bfc41dd7ac1e3a3afa390fe5fa9a5bcca00077bc
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Wed,  9 Oct 2024 10:46:56 -0700

Add furniture styles

Variety might have also been a good term for them, but it's already
taken. These are for things like round tables, etc.

Diffstat:
Acmd/api/migration/2024-10-09T04:46:11.sql | 44++++++++++++++++++++++++++++++++++++++++++++
Minternal/backend/floorplan_data.go | 106+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
2 files changed, 113 insertions(+), 37 deletions(-)

diff --git a/cmd/api/migration/2024-10-09T04:46:11.sql b/cmd/api/migration/2024-10-09T04:46:11.sql @@ -0,0 +1,44 @@ +BEGIN; + +CREATE TABLE spaceplanner.furniture_styles ( + type varchar REFERENCES spaceplanner.furniture_types(name) NOT NULL, + style varchar NOT NULL, + CONSTRAINT unique_style_for_type UNIQUE(style, type) +); + +INSERT INTO spaceplanner.furniture_styles (type, style) VALUES + ('table', 'round'), + ('table', 'semi-circle'); + +ALTER TABLE spaceplanner.furniture + ADD COLUMN style varchar; + +ALTER TABLE spaceplanner.furniture_types + ADD COLUMN class varchar; + +INSERT INTO spaceplanner.furniture_types (name, class) VALUES + ('toilet', 'fixture'); + +CREATE FUNCTION spaceplanner.is_style_for_type() + RETURNS trigger AS $is_style_for_type$ + DECLARE + bad int; + BEGIN + SELECT count(style) + INTO bad + FROM spaceplanner.furniture_styles + WHERE type = NEW.type AND style = NEW.style + LIMIT 1; + IF (NEW.style <> null AND bad <> 1) THEN + RAISE EXCEPTION 'Style (%s) does not exist for that type (%s)', NEW.style, NEW.type; + END IF; + RETURN NEW; + END; + $is_style_for_type$ LANGUAGE plpgsql; + +CREATE CONSTRAINT TRIGGER check_furniture_style + AFTER INSERT OR UPDATE ON spaceplanner.furniture + FOR EACH ROW + EXECUTE FUNCTION spaceplanner.is_style_for_type(); + +END; diff --git a/internal/backend/floorplan_data.go b/internal/backend/floorplan_data.go @@ -48,6 +48,7 @@ type Furniture struct { id ObjectID OldID *ObjectID `json:"old_id,omitempty"` Type string `json:"type" binding:"required"` + Style *string `json:"style,omitempty"` Name *string `json:"name"` Width int `json:"width" binding:"required"` Depth int `json:"depth" binding:"required"` @@ -85,6 +86,8 @@ type DBObject interface { type FurnitureType struct { Varieties map[string]FurnitureVariety `json:"varieties,omit_empty"` + Class *string `json:"class,omit_empty"` + Styles *[]string `json:"styles,omit_empty"` } type FurnitureVariety struct { @@ -108,58 +111,87 @@ func init() { } } -func (e *Env) FurnitureTypes(tx *sql.Tx) (map[string]FurnitureType, error) { +func (e *Env) FurnitureTypes(tx *sql.Tx) (map[string]*FurnitureType, error) { types, err := e.CacheTxStmt(tx, "furn_types", - `SELECT name from spaceplanner.furniture_types`) + `SELECT name, class FROM spaceplanner.furniture_types`) if err != nil { return nil, err } + vars, err := e.CacheTxStmt(tx, "furn_vars", - `SELECT type, name, width, depth from spaceplanner.furniture_varieties`) + `SELECT type, name, width, depth FROM spaceplanner.furniture_varieties`) if err != nil { return nil, err } - rows, err := vars.Query() + styles, err := e.CacheTxStmt(tx, "furn_styles", + `SELECT style FROM spaceplanner.furniture_styles WHERE type = $1`) if err != nil { return nil, err } - defer rows.Close() - data := make(map[string]FurnitureType) + data := make(map[string]*FurnitureType) + + rows, err := types.Query() + if err != nil { + return nil, err + } + // Is this evaluating the expression at execution or now? + // that would change whether this should be run. + defer rows.Close() for rows.Next() { - var tname, vname string - var v FurnitureVariety - if err := rows.Scan(&tname, &vname, &v.Width, &v.Depth); err != nil { + var key string + var class *string + if err := rows.Scan(&key, &class); err != nil { return nil, err } - - if t, exists := data[tname]; exists { - t.Varieties[vname] = v - } else { - t := FurnitureType{} - t.Varieties = make(map[string]FurnitureVariety) - t.Varieties[vname] = v - data[tname] = t + d := FurnitureType{} + if class != nil { + d.Class = class } + data[key] = &d } - rows.Close() - rows, err = types.Query() + rows, err = vars.Query() if err != nil { return nil, err } - // Is this evaluating the expression at execution or now? - // that would change whether this should be run. defer rows.Close() - for rows.Next() { - var key string - if err := rows.Scan(&key); err != nil { + var tn, vn string + var v FurnitureVariety + if err := rows.Scan(&tn, &vn, &v.Width, &v.Depth); err != nil { return nil, err } - if _, exists := data[key]; !exists { - data[key] = FurnitureType{} + + d := data[tn] + if d.Varieties == nil { + d.Varieties = make(map[string]FurnitureVariety) + } + d.Varieties[vn] = v + } + rows.Close() + + for k := range data { + a := make([]string, 0, 4) + kstyles, err := styles.Query(k) + if err != nil { + return nil, err + } + + defer kstyles.Close() + for kstyles.Next() { + var s string + if err := kstyles.Scan(&s); err != nil { + return nil, err + } + a = append(a, s) + } + kstyles.Close() + + if len(a) > 0 { + d := data[k] + d.Styles = &a } } @@ -248,7 +280,7 @@ func (e *Env) getFloorplanPointMaps(tx *sql.Tx, floorplan ObjectID) (map[ObjectI func (e *Env) getFloorplanFurnitureDefs(tx *sql.Tx, floorplan ObjectID) (map[ObjectID]Furniture, error) { defsStmt, err := e.CacheTxStmt(tx, "furniture_defs", - `SELECT id, type, name, width, depth + `SELECT id, type, style, name, width, depth FROM spaceplanner.furniture WHERE floorplan = $1`) if err != nil { @@ -649,33 +681,33 @@ func (e *Env) DeletePointMap(tx *sql.Tx, floorplan, id ObjectID) (PointMap, erro func (f Furniture) Create(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { ins, err := e.CacheTxStmt(tx, "add_furn", - `INSERT INTO spaceplanner.furniture (floorplan, type, name, width, depth) - VALUES ($1, $2, $3, $4, $5) - RETURNING id, type, name, width, depth`) + `INSERT INTO spaceplanner.furniture (floorplan, type, style, name, width, depth) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id, type, style, name, width, depth`) if err != nil { return f, err } - return scanFurniture(ins.QueryRow(floorplan.Seq, f.Type, f.Name, f.Width, f.Depth)) + return scanFurniture(ins.QueryRow(floorplan.Seq, f.Type, f.Style, f.Name, f.Width, f.Depth)) } func (f Furniture) Update(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { update, err := e.CacheTxStmt(tx, "update_furn", - `UPDATE spaceplanner.furniture SET (type, name, width, depth) = - ($3, $4, $5, $6) WHERE floorplan = $1 AND id = $2 - RETURNING id, type, name, width, depth`) + `UPDATE spaceplanner.furniture SET (type, style, name, width, depth) = + ($3, $4, $5, $6, $7) WHERE floorplan = $1 AND id = $2 + RETURNING id, type, style, name, width, depth`) if err != nil { return f, err } - return scanFurniture(update.QueryRow(floorplan.Seq, f.id.Seq, f.Type, f.Name, f.Width, f.Depth)) + return scanFurniture(update.QueryRow(floorplan.Seq, f.id.Seq, f.Type, f.Style, f.Name, f.Width, f.Depth)) } func (f Furniture) Delete(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { del, err := e.CacheTxStmt(tx, "dele_furn", `DELETE FROM spaceplanner.furniture WHERE floorplan = $1 AND id = $2 - RETURNING id, type, name, width, depth`) + RETURNING id, type, style, name, width, depth`) if err != nil { return f, err } @@ -882,7 +914,7 @@ func scanFurniture(row Scanner) (Furniture, error) { var f Furniture var id int64 - err := row.Scan(&id, &f.Type, &f.Name, &f.Width, &f.Depth) + err := row.Scan(&id, &f.Type, &f.Style, &f.Name, &f.Width, &f.Depth) if err != nil { return f, err }