api.spaceplanner.app

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

commit ba9b17964ac7c30dea319c7b0cac5fff1fa37c0f
parent 7784691dbae237cb0e44b42c69c18659cb6eee0e
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Tue, 13 Aug 2024 15:43:44 -0700

Separate floorplan data manipulation functions into their own file

I'm going to design it a little differently than I had originally
intended, so a couple api endpoints which may or may not be back
have been removed for now.

(Also implemented a generic *sql.Rows collector function.)

Diffstat:
Mcmd/api/floorplans.go | 45---------------------------------------------
Mcmd/api/main.go | 4----
Ainternal/backend/db.go | 21+++++++++++++++++++++
Minternal/backend/floorplan.go | 85++++---------------------------------------------------------------------------
Ainternal/backend/floorplan_data.go | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/backend/go.mod | 2++
Minternal/backend/go.sum | 2++
7 files changed, 79 insertions(+), 130 deletions(-)

diff --git a/cmd/api/floorplans.go b/cmd/api/floorplans.go @@ -122,51 +122,6 @@ func (e *Env) GetFloorplan(c *gin.Context) { } } -func (e *Env) AddFloorplanPoints(c *gin.Context) { - user := c.Param("user") - floorplan := c.Param("floorplan") - - var points []Point - if err := c.ShouldBind(&points); err != nil { - RespondError(c, 400, "%s: Unable to read points", err.Error()) - return - } - - tx, err := e.backend.DB.Begin() - if err != nil { - RespondError(c, 400, "Unable to begin transaction") - return - } - - var rpoints []backend.Point - for _, point := range points { - rp, err := e.backend.AddFloorplanPoint(tx, user, floorplan, backend.Point{ X: *point.X, Y: *point.Y }) - if err != nil { - tx.Rollback() - RespondError(c, 400, "%s: Unable to add point", err.Error()) - return - } - rpoints = append(rpoints, rp) - } - if err := tx.Commit(); err != nil { - RespondError(c, 400, "Unable to commit") - } else { - Respond(c, 200, rpoints) - } -} - -func (e *Env) GetFloorplanPoints(c *gin.Context) { - user := c.Param("user") - floorplan := c.Param("floorplan") - - points, err := e.backend.GetFloorplanPoints(nil, user, floorplan) - if err != nil { - RespondError(c, 400, "Unable to get points") - } else { - Respond(c, 200, points) - } -} - func patchableToSettable(data map[string]interface{}) (*SettableFloorplan, error) { // Seems stupidly inefficient, but I can't find a better way at the moment jsondata, err := json.Marshal(data) diff --git a/cmd/api/main.go b/cmd/api/main.go @@ -95,10 +95,6 @@ func setAuthenticatedRoutes(env *Env, r *gin.RouterGroup) { fp.PATCH("", env.UpdateFloorplan) fp.DELETE("", env.DeleteFloorplan) fp.GET("", env.GetFloorplan) - - pt := fp.Group("/points") - pt.GET("", env.GetFloorplanPoints) - pt.POST("", env.AddFloorplanPoints) } func noRoute(c *gin.Context) { diff --git a/internal/backend/db.go b/internal/backend/db.go @@ -0,0 +1,21 @@ +package backend + +import "database/sql" + +type Scanner interface { + Scan(dest ...any) error +} + +func collectRows[T any](rows *sql.Rows, collector func(Scanner) (T, error)) ([]T, error) { + var things []T + + for rows.Next() { + thing, err := collector(rows) + if err != nil { + return nil, err + } + things = append(things, thing) + } + + return things, nil +} diff --git a/internal/backend/floorplan.go b/internal/backend/floorplan.go @@ -15,17 +15,6 @@ type Floorplan struct { Created time.Time `json:"created"` } -type Point struct { - //floorplan_id int - Id int `json:"id"` - X int `json:"x"` - Y int `json:"y"` -} - -type Scanner interface { - Scan(dest ...any) error -} - func (e *Env) CreateFloorplan(tx *sql.Tx, template *Floorplan) (*Floorplan, error) { stmt, err := e.CacheTxStmt(tx, "create_floorplan", `INSERT INTO spaceplanner.floorplans (owner, name, address, synopsis) VALUES ($1, $2, $3, $4) RETURNING *`) @@ -55,26 +44,20 @@ func (e *Env) GetFloorplan(tx *sql.Tx, user string, name string) (*Floorplan, er return scanFloorplan(stmt.QueryRow(user, name)) } -func (e *Env) GetFloorplans(tx *sql.Tx, user string) ([]Floorplan, error) { +func (e *Env) GetFloorplans(tx *sql.Tx, user string) ([]*Floorplan, error) { stmt, err := e.CacheTxStmt(tx, "get_floorplans", `SELECT * FROM spaceplanner.floorplans WHERE owner = $1 ORDER BY updated DESC, created DESC`) if err != nil { return nil, err } - var floorplans []Floorplan rows, err := stmt.Query(user) if err != nil { return nil, err } - for rows.Next() { - t, err := scanFloorplan(rows) - if err != nil { - return nil, err - } - floorplans = append(floorplans, *t) - } - return floorplans, nil + defer rows.Close() + + return collectRows(rows, scanFloorplan) } func (e *Env) DeleteFloorplan(tx *sql.Tx, user string, name string) (*Floorplan, error) { @@ -86,68 +69,8 @@ func (e *Env) DeleteFloorplan(tx *sql.Tx, user string, name string) (*Floorplan, return scanFloorplan(stmt.QueryRow(user, name)) } -func (e *Env) AddFloorplanPoint(tx *sql.Tx, user string, floorplan string, point Point) (Point, error) { - stmt, err := e.CacheTxStmt(tx, "add_point", `INSERT INTO floorplan_points VALUES ( - spaceplanner.floorplan_id($1, $2), (SELECT coalesce(max(id), 0) FROM - floorplan_points WHERE floorplan = spaceplanner.floorplan_id($1, $2)) + 1, $3, $4) RETURNING id, x, y`) - if err != nil { - return Point{}, err - } - - row := stmt.QueryRow(user, floorplan, point.X, point.Y) - return scanPoint(row) - -} - -func (e *Env) InsertFloorplanPoint(tx *sql.Tx, user string, floorplan string, point Point) error { - stmt, err := e.CacheTxStmt(tx, "ins_point", `WITH t AS ( - UPDATE floorplan_points - WHERE floorplan = spaceplanner.floorplan_id($1, $2) AND id > $3 SET id = id + 1 - ) INSERT INTO floorplan_points id, x, y VALUES (spaceplanner.floorplan_id($1, $2), $3, $4, $5)`) - - if err != nil { - return err - } - _, err = stmt.Exec(user, floorplan, point.Id, point.X, point.Y) - return err -} - -func (e *Env) GetFloorplanPoints(tx *sql.Tx, user string, floorplan string) ([]Point, error) { - stmt, err := e.CacheTxStmt(tx, "get_points", "SELECT id, x, y FROM floorplan_points WHERE floorplan = spaceplanner.floorplan_id($1, $2)") - if err != nil { - return nil, err - } - - rows, err := stmt.Query(user, floorplan) - if err != nil { - return nil, err - } - return collectPoints(rows) -} - func scanFloorplan(row Scanner) (*Floorplan, error) { var f Floorplan err := row.Scan(&f.id, &f.User, &f.Name, &f.Address, &f.Synopsis, &f.Updated, &f.Created) return &f, err } - -func collectPoints(rows *sql.Rows) ([]Point, error) { - var points []Point - - for rows.Next() { - point, err := scanPoint(rows) - if err != nil { - return nil, err - } - points = append(points, point) - } - - return points, nil -} - -func scanPoint(row Scanner) (Point, error) { - var point Point - - err := row.Scan(&point.Id, &point.X, &point.Y) - return point, err -} diff --git a/internal/backend/floorplan_data.go b/internal/backend/floorplan_data.go @@ -0,0 +1,50 @@ +package backend + +import ( + "database/sql" +) + +type Point struct { + Id int `json:"id" binding:"required"` + X int `json:"x" binding:"required"` + Y int `json:"y" binding:"required"` +} + +type FloorplanData struct { + points []Point +} + +func (e *Env) GetFloorplanData(tx *sql.Tx, user string, floorplan string) (FloorplanData, error) { + var data FloorplanData + var err error + + data.points, err = e.getFloorplanPoints(tx, user, floorplan) + if err != nil { + return data, err + } + + return data, nil +} + +func (e *Env) getFloorplanPoints(tx *sql.Tx, user string, floorplan string) ([]Point, error) { + stmt, err := e.CacheTxStmt(tx, "get_points", + `SELECT (id, x, y) FROM spaceplanner.floorplan_points WHERE + floorplan = spaceplanner.floorplan_id($1, $2)`) + if err != nil { + return nil, err + } + + rows, err := stmt.Query(user, floorplan) + if err != nil { + return nil, err + } + defer rows.Close() + return collectRows(rows, scanPoint) +} + +func scanPoint(row Scanner) (Point, error) { + var point Point + + err := row.Scan(&point.Id, &point.X, &point.Y) + return point, err +} diff --git a/internal/backend/go.mod b/internal/backend/go.mod @@ -3,3 +3,5 @@ module jacobedwards.org/spaceplanner.app/internal/backend go 1.22.1 require golang.org/x/crypto v0.25.0 + +require github.com/lib/pq v1.10.9 // indirect diff --git a/internal/backend/go.sum b/internal/backend/go.sum @@ -1,2 +1,4 @@ +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=