floorplan_data.go (24800B)
1 package backend 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strings" 9 ) 10 11 type FloorplanData struct { 12 Points map[ObjectID]Point `json:"points"` 13 Pointmaps map[ObjectID]PointMap `json:"pointmaps"` 14 15 // I would like to have this in a FurnitureData struct, 16 // but don't want to rework the client to accept that 17 // at the moment. 18 Furniture map[ObjectID]Furniture `json:"furniture"` 19 FurnitureMaps map[ObjectID]FurnitureMap `json:"furniture_maps"` 20 } 21 22 /* 23 * NOTE: The id fields are not sent in response because it is encoded 24 * in the data structure. That is, they're in a map keyed by their ID 25 * (as seen above.) It's done this way so that the client can update 26 * individual objects with JSON Patch. 27 */ 28 type Point struct { 29 id ObjectID `json:"id"` 30 OldID *ObjectID `json:"old_id,omitempty"` 31 X int `json:"x" binding:"required"` 32 Y int `json:"y" binding:"required"` 33 } 34 35 type PointMap struct { 36 id ObjectID 37 OldID *ObjectID `json:"old_id,omitempty"` 38 Type string `json:"type" binding:"required"` 39 A ObjectID `json:"a" binding:"required"` 40 B ObjectID `json:"b" binding:"required"` 41 DoorSwing *string `json:"door_swing,omitempty"` 42 } 43 44 // NOTE: I'd like to allow every value to be omitted so 45 // long as it doesn't violate database rules, but without 46 // setting to null. 47 type Furniture struct { 48 id ObjectID 49 OldID *ObjectID `json:"old_id,omitempty"` 50 Type string `json:"type" binding:"required"` 51 UserType *string `json:"user_type,omitempty"` 52 Style *string `json:"style,omitempty"` 53 Name *string `json:"name"` 54 Width int `json:"width" binding:"required"` 55 Depth int `json:"depth" binding:"required"` 56 } 57 58 type FurnitureMap struct { 59 id ObjectID 60 OldID *ObjectID `json:"old_id,omitempty"` 61 FurnitureID ObjectID `json:"furniture_id" binding:"required"` 62 Layout string `json:"layout" binding:"required"` 63 X int `json:"x" binding:"required"` 64 Y int `json:"y" binding:"required"` 65 Angle int `json:"angle" binding:"required"` 66 } 67 68 type PatchError struct { 69 ID *ObjectID 70 Context string 71 Reason error 72 } 73 74 type DBObject interface { 75 ID() ObjectID 76 updateIDs(ObjectID, map[ObjectID]ObjectID) DBObject 77 SetOldID(ObjectID) DBObject 78 79 /* 80 * NOTE: these all take user and floorplan arguments solely for authorization, but 81 * I want a cleaner method 82 */ 83 Create(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) 84 Update(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) 85 Delete(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) 86 } 87 88 type FurnitureType struct { 89 Varieties map[string]FurnitureVariety `json:"varieties,omit_empty"` 90 Class *string `json:"class,omit_empty"` 91 Styles *[]string `json:"styles,omit_empty"` 92 } 93 94 type FurnitureVariety struct { 95 Width int `json:"width"` 96 Depth int `json:"depth"` 97 } 98 99 type Mappable interface { 100 Key() any 101 } 102 103 104 var tables map[ObjectType]string 105 106 func init() { 107 tables = map[ObjectType]string{ 108 IDTypePoint: "points", 109 IDTypePointMap: "pointmaps", 110 IDTypeFurniture: "furniture", 111 IDTypeFurnitureMap: "furniture_maps", 112 } 113 } 114 115 func (e *Env) FurnitureTypes(tx *sql.Tx) (map[string]*FurnitureType, error) { 116 types, err := e.CacheTxStmt(tx, "furn_types", 117 `SELECT name, class FROM spaceplanner.furniture_types`) 118 if err != nil { 119 return nil, err 120 } 121 122 vars, err := e.CacheTxStmt(tx, "furn_vars", 123 `SELECT type, name, width, depth FROM spaceplanner.furniture_varieties`) 124 if err != nil { 125 return nil, err 126 } 127 128 styles, err := e.CacheTxStmt(tx, "furn_styles", 129 `SELECT style FROM spaceplanner.furniture_styles WHERE type = $1`) 130 if err != nil { 131 return nil, err 132 } 133 134 data := make(map[string]*FurnitureType) 135 136 rows, err := types.Query() 137 if err != nil { 138 return nil, err 139 } 140 // Is this evaluating the expression at execution or now? 141 // that would change whether this should be run. 142 defer rows.Close() 143 for rows.Next() { 144 var key string 145 var class *string 146 if err := rows.Scan(&key, &class); err != nil { 147 return nil, err 148 } 149 d := FurnitureType{} 150 if class != nil { 151 d.Class = class 152 } 153 data[key] = &d 154 } 155 156 rows, err = vars.Query() 157 if err != nil { 158 return nil, err 159 } 160 defer rows.Close() 161 for rows.Next() { 162 var tn, vn string 163 var v FurnitureVariety 164 if err := rows.Scan(&tn, &vn, &v.Width, &v.Depth); err != nil { 165 return nil, err 166 } 167 168 d := data[tn] 169 if d.Varieties == nil { 170 d.Varieties = make(map[string]FurnitureVariety) 171 } 172 d.Varieties[vn] = v 173 } 174 rows.Close() 175 176 for k := range data { 177 a := make([]string, 0, 4) 178 kstyles, err := styles.Query(k) 179 if err != nil { 180 return nil, err 181 } 182 183 defer kstyles.Close() 184 for kstyles.Next() { 185 var s string 186 if err := kstyles.Scan(&s); err != nil { 187 return nil, err 188 } 189 a = append(a, s) 190 } 191 kstyles.Close() 192 193 if len(a) > 0 { 194 d := data[k] 195 d.Styles = &a 196 } 197 } 198 199 return data, nil 200 } 201 202 func (e *Env) PointmapTypes() ([]string, error) { 203 r, err := e.DB.Query("SELECT name FROM spaceplanner.pointmap_types") 204 if err != nil { 205 return nil, err 206 } 207 defer r.Close() 208 209 a := make([]string, 0, 8) 210 for r.Next() { 211 var name string 212 if err := r.Scan(&name); err != nil { 213 return nil, err 214 } 215 a = append(a, name) 216 } 217 if err := r.Err(); err != nil { 218 return nil, err 219 } 220 221 return a, nil 222 } 223 224 func (e *Env) GetFloorplanData(tx *sql.Tx, user string, floorplan ObjectID) (FloorplanData, error) { 225 var data FloorplanData 226 var err error 227 228 if floorplan.Type != IDTypeFloorplan { 229 return data, errors.New("Expected floorplan id") 230 } 231 232 a, err := e.userFloorplanAccess(tx, user, floorplan) 233 if err != nil { 234 return FloorplanData{}, err 235 } 236 if !a.Read() { 237 return FloorplanData{}, errors.New("You do not have read permission on this resource") 238 } 239 240 data.Points, err = e.getFloorplanPoints(tx, floorplan) 241 if err != nil { 242 return data, err 243 } 244 245 data.Pointmaps, err = e.getFloorplanPointMaps(tx, floorplan) 246 if err != nil { 247 return data, err 248 } 249 250 data.Furniture, err = e.getFloorplanFurnitureDefs(tx, floorplan) 251 if err != nil { 252 return data, err 253 } 254 255 data.FurnitureMaps, err = e.getFloorplanFurnitureMaps(tx, floorplan) 256 if err != nil { 257 return data, err 258 } 259 260 return data, nil 261 } 262 263 func (e *Env) getFloorplanPoints(tx *sql.Tx, floorplan ObjectID) (map[ObjectID]Point, error) { 264 stmt, err := e.CacheTxStmt(tx, "get_points", 265 `SELECT id, x, y FROM spaceplanner.floorplan_points 266 WHERE floorplan = $1`) 267 if err != nil { 268 return nil, err 269 } 270 271 rows, err := stmt.Query(floorplan.Seq) 272 if err != nil { 273 return nil, err 274 } 275 defer rows.Close() 276 277 points, err := collectRows(rows, scanPoint) 278 if err != nil { 279 return nil, err 280 } 281 return mapArray(points, mapPoint) 282 } 283 284 func (e *Env) getFloorplanPointMaps(tx *sql.Tx, floorplan ObjectID) (map[ObjectID]PointMap, error) { 285 stmt, err := e.CacheTxStmt(tx, "get_pointmaps", 286 `SELECT id, type, a, b, door_swing FROM spaceplanner.floorplan_pointmaps WHERE 287 floorplan = $1`) 288 if err != nil { 289 return nil, err 290 } 291 292 rows, err := stmt.Query(floorplan.Seq) 293 if err != nil { 294 return nil, err 295 } 296 defer rows.Close() 297 pointmaps, err := collectRows(rows, scanPointMap) 298 if err != nil { 299 return nil, err 300 } 301 return mapArray(pointmaps, mapPointMap) 302 } 303 304 func (e *Env) getFloorplanFurnitureDefs(tx *sql.Tx, floorplan ObjectID) (map[ObjectID]Furniture, error) { 305 defsStmt, err := e.CacheTxStmt(tx, "furniture_defs", 306 `SELECT id, type, user_type, style, name, width, depth 307 FROM spaceplanner.furniture 308 WHERE floorplan = $1`) 309 if err != nil { 310 return nil, err 311 } 312 313 rows, err := defsStmt.Query(floorplan.Seq) 314 if err != nil { 315 return nil, err 316 } 317 defer rows.Close() 318 319 defs, err := collectRows(rows, scanFurniture) 320 if err != nil { 321 return nil, err 322 } 323 324 return mapArray(defs, mapFurniture) 325 326 } 327 328 func (e *Env) getFloorplanFurnitureMaps(tx *sql.Tx, floorplan ObjectID) (map[ObjectID]FurnitureMap, error) { 329 mapsStmt, err := e.CacheTxStmt(tx, "furniture_maps", 330 `SELECT id, furniture_id, layout, x, y, angle 331 FROM spaceplanner.furniture_maps 332 WHERE furniture_id IN ( 333 SELECT id 334 FROM spaceplanner.furniture 335 WHERE floorplan = $1 336 )`) 337 if err != nil { 338 return nil, err 339 } 340 341 rows, err := mapsStmt.Query(floorplan.Seq) 342 if err != nil { 343 return nil, err 344 } 345 defer rows.Close() 346 347 maps, err := collectRows(rows, scanFurnitureMap) 348 if err != nil { 349 return nil, err 350 } 351 return mapArray(maps, mapFurnitureMap) 352 } 353 354 func (e *Env) ReplaceFloorplanData(tx *sql.Tx, user string, floorplan ObjectID, data *FloorplanData) (FloorplanData, error) { 355 if floorplan.Type != IDTypeFloorplan { 356 return FloorplanData{}, errors.New("Expected floorplan id") 357 } 358 359 mytx := false 360 if (tx == nil) { 361 mytx = true 362 var err error 363 tx, err = e.DB.Begin() 364 if err != nil { 365 return FloorplanData{}, err 366 } 367 defer tx.Rollback() 368 } 369 createPatch := func(id ObjectID, value any) Patch { 370 return Patch{ 371 Op: PatchNew, 372 Path: id.Path(), 373 Value: value, 374 } 375 } 376 377 if err := e.DeleteFloorplanData(tx, user, floorplan); err != nil { 378 return FloorplanData{}, err 379 } 380 381 patches := make([]Patch, 0) 382 for id, v := range data.Points { 383 patches = append(patches, createPatch(id, v)) 384 } 385 for id, v := range data.Pointmaps { 386 patches = append(patches, createPatch(id, v)) 387 } 388 for id, v := range data.Furniture { 389 patches = append(patches, createPatch(id, v)) 390 } 391 for id, v := range data.FurnitureMaps { 392 patches = append(patches, createPatch(id, v)) 393 } 394 395 newdata, err := e.PatchFloorplanData(tx, user, floorplan, patches) 396 if err != nil { 397 return FloorplanData{}, err 398 } 399 if (mytx) { 400 if err := tx.Commit(); err != nil { 401 return FloorplanData{}, err 402 } 403 } 404 return newdata, nil 405 } 406 407 func (e *Env) DeleteFloorplanData(tx *sql.Tx, user string, floorplan ObjectID) error { 408 if floorplan.Type != IDTypeFloorplan { 409 return errors.New("Expected floorplan id") 410 } 411 412 var mytx bool 413 if tx == nil { 414 mytx = true 415 var err error 416 tx, err = e.DB.Begin() 417 if err != nil { 418 return err 419 } 420 defer tx.Rollback() 421 } 422 423 a, err := e.userFloorplanAccess(tx, user, floorplan) 424 if err != nil { 425 return err 426 } 427 if !a.Write() { 428 return errors.New("You do not have write permission on this resource") 429 } 430 431 delPnt, err := e.CacheTxStmt(tx, "del_floorplan_pnt", 432 `DELETE FROM spaceplanner.floorplan_points 433 WHERE floorplan = $1`) 434 if err != nil { 435 return err 436 } 437 438 delFur, err := e.CacheTxStmt(tx, "del_floorplan_fur", 439 `DELETE FROM spaceplanner.furniture 440 WHERE floorplan = $1`) 441 if err != nil { 442 return err 443 } 444 445 _, err = delPnt.Exec(floorplan.Seq) 446 if err != nil { 447 return err 448 } 449 _, err = delFur.Exec(floorplan.Seq) 450 if err != nil { 451 return err 452 } 453 454 if (mytx) { 455 return tx.Commit() 456 } 457 return nil 458 } 459 460 func (e *Env) PatchFloorplanData(tx *sql.Tx, user string, floorplan ObjectID, patches []Patch) (FloorplanData, error) { 461 var err error 462 mytx := false 463 464 if floorplan.Type != IDTypeFloorplan { 465 return FloorplanData{}, errors.New("Expected floorplan id") 466 } 467 468 if (tx == nil) { 469 tx, err = e.DB.Begin() 470 if err != nil { 471 return FloorplanData{}, err 472 } 473 defer tx.Rollback() 474 mytx = true 475 } 476 477 a, err := e.userFloorplanAccess(tx, user, floorplan) 478 if err != nil { 479 return FloorplanData{}, err 480 } 481 if !a.Write() { 482 return FloorplanData{}, errors.New("You do not have write permission on this resource") 483 } 484 485 newIDs := make(map[ObjectID]ObjectID) 486 487 data := FloorplanData{} 488 data.Points = make(map[ObjectID]Point) 489 data.Pointmaps = make(map[ObjectID]PointMap) 490 data.Furniture = make(map[ObjectID]Furniture) 491 data.FurnitureMaps = make(map[ObjectID]FurnitureMap) 492 493 // Allowed operations are new, replace, and delete 494 // (new is an extention that ensures path's don't exist before creation) 495 for _, patch := range patches { 496 id, err := parsePath(patch.Path) 497 if err != nil { 498 return data, patchError(nil, patch.Path, err) 499 } 500 501 if (patch.Op == PatchNew || patch.Op == PatchReplace) && patch.Value == nil { 502 return data, id.Error("Requires value", nil) 503 } 504 505 if (id.Type == IDTypePoint) { 506 point, err := applyPatch[Point](e, tx, floorplan, &patch, id, newIDs) 507 if err != nil { 508 return data, id.Error("", err) 509 } 510 if patch.Op != PatchRemove { 511 data.Points[point.id] = point 512 } 513 } else if (id.Type == IDTypePointMap) { 514 pointmap, err := applyPatch[PointMap](e, tx, floorplan, &patch, id, newIDs) 515 if err != nil { 516 return data, id.Error("", err) 517 } 518 if patch.Op != PatchRemove { 519 data.Pointmaps[pointmap.id] = pointmap 520 } 521 } else if (id.Type == IDTypeFurniture) { 522 def, err := applyPatch[Furniture](e, tx, floorplan, &patch, id, newIDs) 523 if err != nil { 524 return data, id.Error("", err) 525 } 526 if patch.Op != PatchRemove { 527 data.Furniture[def.id] = def 528 } 529 } else if (id.Type == IDTypeFurnitureMap) { 530 fm, err := applyPatch[FurnitureMap](e, tx, floorplan, &patch, id, newIDs) 531 if err != nil { 532 return data, id.Error("", err) 533 } 534 if patch.Op != PatchRemove { 535 data.FurnitureMaps[fm.id] = fm 536 } 537 } else { 538 return data, id.Error("Path does not exist", nil) 539 } 540 } 541 542 if (mytx) { 543 err = tx.Commit() 544 if err != nil { 545 return data, patchError(nil, "Unable to commit patch", err) 546 } 547 } 548 549 return data, nil 550 } 551 552 func (e *Env) userFloorplanAccess(tx *sql.Tx, user string, floorplan ObjectID) (AccessPermission, error) { 553 checkPerms, err := e.CacheTxStmt(tx, "check_perms", 554 "SELECT owner FROM spaceplanner.floorplans WHERE id = $1") 555 if err != nil { 556 return NoAccess, err 557 } 558 559 var owner string 560 r := checkPerms.QueryRow(floorplan.Seq) 561 if err := r.Scan(&owner); err != nil { 562 return NoAccess, err 563 } 564 if owner == user { 565 return ReadAccess | WriteAccess, nil 566 } 567 return NoAccess, nil 568 } 569 570 func applyPatch[T DBObject](e *Env, tx *sql.Tx, floorplan ObjectID, patch *Patch, id ObjectID, 571 newIDs map[ObjectID]ObjectID) (T, error) { 572 inputID := id 573 id, exists := newIDs[inputID] 574 if !exists { 575 id = inputID 576 } 577 578 var thing T 579 if err := remarshal(patch.Value, &thing); err != nil { 580 return thing, inputID.Error("Invalid object", err) 581 } 582 583 // Could do with adding a id.Type check 584 585 // Tried to figure out the correct solution to this, but 586 // can't figure it out right now. I'll come back to it later 587 // but for now a few type assertions 588 thing = thing.updateIDs(id, newIDs).(T) 589 590 var err error 591 var dbo DBObject 592 switch patch.Op { 593 case PatchNew: 594 dbo, err = thing.Create(e, tx, floorplan) 595 case PatchReplace: 596 dbo, err = thing.Update(e, tx, floorplan) 597 case PatchRemove: 598 dbo, err = thing.Delete(e, tx, floorplan) 599 default: 600 return thing, inputID.Error("Unsupported operation", nil) 601 } 602 603 if err != nil { 604 return thing, inputID.Error("Unable to perform " + patch.Op + " operation", err) 605 } 606 607 thing = dbo.(T) 608 id = thing.ID() 609 if inputID == id { 610 return thing, nil 611 } 612 613 newIDs[inputID] = id 614 return thing.SetOldID(inputID).(T), nil 615 } 616 617 func remarshal[T any](value interface{}, result *T) (error) { 618 s, err := json.Marshal(value) 619 if err != nil { 620 return err 621 } 622 return json.Unmarshal(s, result) 623 } 624 625 /* 626 * NOTE: Tried getting this right for a while, settled on having 627 * these functions return an interface instead of their value 628 * Not sure exactly how this is suppost to be done. 629 */ 630 func (p Point) Create(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 631 stmt, err := e.CacheTxStmt(tx, "add_point",`INSERT INTO spaceplanner.floorplan_points (floorplan, x, y) 632 VALUES ($1, $2, $3) RETURNING id, x, y`) 633 if err != nil { 634 return Point{}, err 635 } 636 637 return scanPoint(stmt.QueryRow(floorplan.Seq, p.X, p.Y)) 638 } 639 640 func (p Point) Update(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 641 stmt, err := e.CacheTxStmt(tx, "repl_point", `UPDATE spaceplanner.floorplan_points SET (x, y) = ($3, $4) 642 WHERE floorplan = $1 AND id = $2 RETURNING id, x, y`) 643 if err != nil { 644 return Point{}, err 645 } 646 647 return scanPoint(stmt.QueryRow(floorplan.Seq, p.id.Seq, p.X, p.Y)) 648 } 649 650 func (p Point) Delete(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 651 return e.DeletePoint(tx, floorplan, p.id) 652 } 653 654 func (e *Env) DeletePoint(tx *sql.Tx, floorplan, id ObjectID) (Point, error) { 655 stmt, err := e.CacheTxStmt(tx, "dele_point", 656 `DELETE FROM spaceplanner.floorplan_points WHERE floorplan = $1 AND id = $2 657 RETURNING id, x, y`) 658 if err != nil { 659 return Point{}, err 660 } 661 662 return scanPoint(stmt.QueryRow(floorplan.Seq, id.Seq)) 663 } 664 665 func (pm PointMap) Create(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 666 stmt, err := e.CacheTxStmt(tx, "add_pointmap", 667 `INSERT INTO spaceplanner.floorplan_pointmaps (floorplan, type, a, b, door_swing) VALUES ( 668 $1, $2, $3, $4, $5 669 ) RETURNING id, type, a, b, door_swing`) 670 if err != nil { 671 return PointMap{}, err 672 } 673 674 return scanPointMap(stmt.QueryRow(floorplan.Seq, pm.Type, pm.A.Seq, pm.B.Seq, pm.DoorSwing)) 675 } 676 677 func (pm PointMap) Update(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 678 stmt, err := e.CacheTxStmt(tx, "repl_pointmap", 679 `UPDATE spaceplanner.floorplan_pointmaps SET (type, a, b, door_swing) = 680 ($3, $4, $5, $6) WHERE floorplan = $1 AND id = $2 681 RETURNING id, type, a, b, door_swing`) 682 if err != nil { 683 return PointMap{}, err 684 } 685 686 return scanPointMap(stmt.QueryRow(floorplan.Seq, pm.id.Seq, pm.Type, pm.A.Seq, pm.B.Seq, pm.DoorSwing)) 687 } 688 689 func (pm PointMap) Delete(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 690 return e.DeletePointMap(tx, floorplan, pm.id) 691 } 692 693 func (e *Env) DeletePointMap(tx *sql.Tx, floorplan, id ObjectID) (PointMap, error) { 694 stmt, err := e.CacheTxStmt(tx, "dele_pointmap", 695 `DELETE FROM spaceplanner.floorplan_pointmaps 696 WHERE floorplan = $1 AND id = $2 697 RETURNING id, type, a, b, door_swing`) 698 if err != nil { 699 return PointMap{}, err 700 } 701 702 return scanPointMap(stmt.QueryRow(floorplan.Seq, id.Seq)) 703 } 704 705 func (f Furniture) Create(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 706 ins, err := e.CacheTxStmt(tx, "add_furn", 707 `INSERT INTO spaceplanner.furniture (floorplan, type, user_type, style, name, width, depth) 708 VALUES ($1, $2, $3, $4, $5, $6, $7) 709 RETURNING id, type, user_type, style, name, width, depth`) 710 if err != nil { 711 return f, err 712 } 713 714 return scanFurniture(ins.QueryRow(floorplan.Seq, f.Type, f.UserType, f.Style, f.Name, f.Width, f.Depth)) 715 } 716 717 func (f Furniture) Update(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 718 update, err := e.CacheTxStmt(tx, "update_furn", 719 `UPDATE spaceplanner.furniture SET (type, user_type, style, name, width, depth) = 720 ($3, $4, $5, $6, $7, $8) WHERE floorplan = $1 AND id = $2 721 RETURNING id, type, user_type, style, name, width, depth`) 722 if err != nil { 723 return f, err 724 } 725 726 return scanFurniture(update.QueryRow(floorplan.Seq, f.id.Seq, f.Type, f.UserType, f.Style, f.Name, f.Width, f.Depth)) 727 } 728 729 func (f Furniture) Delete(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 730 del, err := e.CacheTxStmt(tx, "dele_furn", 731 `DELETE FROM spaceplanner.furniture 732 WHERE floorplan = $1 AND id = $2 733 RETURNING id, type, user_type, style, name, width, depth`) 734 if err != nil { 735 return f, err 736 } 737 738 return scanFurniture(del.QueryRow(floorplan.Seq, f.id.Seq)) 739 } 740 741 func (f FurnitureMap) Create(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 742 ins, err := e.CacheTxStmt(tx, "add_furnmap", 743 `INSERT INTO spaceplanner.furniture_maps (floorplan, furniture_id, layout, x, y, angle) 744 VALUES ($1, $2, $3, $4, $5, $6) 745 RETURNING id, furniture_id, layout, x, y, angle`) 746 if err != nil { 747 return f, err 748 } 749 750 return scanFurnitureMap(ins.QueryRow(floorplan.Seq, f.FurnitureID.Seq, f.Layout, f.X, f.Y, f.Angle)) 751 } 752 753 func (fm FurnitureMap) Update(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 754 update, err := e.CacheTxStmt(tx, "update_furnmap", 755 `UPDATE spaceplanner.furniture_maps SET (furniture_id, layout, x, y, angle) = 756 ($3, $4, $5, $6, $7) WHERE floorplan = $1 AND id = $2 757 RETURNING id, furniture_id, layout, x, y, angle`) 758 if err != nil { 759 return fm, err 760 } 761 762 return scanFurnitureMap(update.QueryRow(floorplan.Seq, fm.id.Seq, fm.FurnitureID.Seq, fm.Layout, fm.X, fm.Y, fm.Angle)) 763 } 764 765 func (f FurnitureMap) Delete(e *Env, tx *sql.Tx, floorplan ObjectID) (DBObject, error) { 766 del, err := e.CacheTxStmt(tx, "dele_furnmap", 767 `DELETE FROM spaceplanner.furniture_maps 768 WHERE floorplan = $1 AND id = $2 769 RETURNING id, furniture_id, layout, x, y, angle`) 770 if err != nil { 771 return f, err 772 } 773 774 return scanFurnitureMap(del.QueryRow(floorplan.Seq, f.id.Seq)) 775 } 776 777 func (f Furniture) ID() ObjectID { 778 return f.id 779 } 780 781 func (f Furniture) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { 782 if n, exists := newIDs[id]; exists { 783 f.id = n 784 } else { 785 f.id = id 786 } 787 return f 788 } 789 790 func (f Furniture) SetOldID(id ObjectID) DBObject { 791 if id != f.id { 792 f.OldID = &id 793 } 794 return f 795 } 796 797 func (fm FurnitureMap) ID() ObjectID { 798 return fm.id 799 } 800 801 func (fm FurnitureMap) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { 802 if n, exists := newIDs[id]; exists { 803 fm.id = n 804 } else { 805 fm.id = id 806 } 807 if n, exists := newIDs[fm.FurnitureID]; exists { 808 fm.FurnitureID = n 809 } 810 return fm 811 } 812 813 func (fm FurnitureMap) SetOldID(id ObjectID) DBObject { 814 if id != fm.id { 815 fm.OldID = &id 816 } 817 return fm 818 } 819 820 func (p Point) ID() ObjectID { 821 return p.id 822 } 823 824 func (p Point) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { 825 if n, exists := newIDs[id]; exists { 826 p.id = n 827 } else { 828 p.id = id 829 } 830 return p 831 } 832 833 func (p Point) SetOldID(id ObjectID) DBObject { 834 if id != p.id { 835 p.OldID = &id 836 } 837 return p 838 } 839 840 func (m PointMap) ID() ObjectID { 841 return m.id 842 } 843 844 func (m PointMap) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { 845 if n, exists := newIDs[id]; exists { 846 m.id = n 847 } else { 848 m.id = id 849 } 850 if n, exists := newIDs[m.A]; exists { 851 m.A = n 852 } 853 if n, exists := newIDs[m.B]; exists { 854 m.B = n 855 } 856 return m 857 } 858 859 func (m PointMap) SetOldID(id ObjectID) DBObject { 860 if id != m.id { 861 m.OldID = &id 862 } 863 return m 864 } 865 866 /* 867 * Parse patch path's of the form 868 * /table/id 869 */ 870 func parsePath(p string) (ObjectID, error) { 871 parts := strings.Split(p, "/") 872 if parts[0] != "" { 873 return ObjectID{}, errors.New(p + ": Requires absolute path (start with /)") 874 } 875 if len(parts) < 3 { 876 return ObjectID{}, errors.New(p + ": Object does not exist") 877 } 878 879 id, err := ParseObjectID(parts[len(parts) - 1]) 880 if err != nil { 881 return ObjectID{}, err 882 } 883 884 parts = parts[1:len(parts) - 1] 885 typ, err := pathIDType(strings.Join(parts, "/")) 886 if err != nil { 887 return ObjectID{}, err 888 } 889 890 if typ != id.Type { 891 return ObjectID{}, errors.New("Invalid ID for that path") 892 } 893 return id, nil 894 } 895 896 func pathIDType(p string) (ObjectType, error) { 897 switch p { 898 case "points": 899 return IDTypePoint, nil 900 case "pointmaps": 901 return IDTypePointMap, nil 902 case "furniture": 903 return IDTypeFurniture, nil 904 case "furniture_maps": 905 return IDTypeFurnitureMap, nil 906 } 907 return "", errors.New(p + ": Invalid path prefix") 908 } 909 910 func scanPoint(s Scanner) (Point, error) { 911 var p Point 912 var id int64 913 914 err := s.Scan(&id, &p.X, &p.Y) 915 if err != nil { 916 return p, err 917 } 918 p.id = makeID(IDTypePoint, id) 919 return p, nil 920 } 921 922 func scanPointMap(s Scanner) (PointMap, error) { 923 var pm PointMap 924 var id, a, b int64 925 926 err := s.Scan(&id, &pm.Type, &a, &b, &pm.DoorSwing) 927 if err != nil { 928 return pm, err 929 } 930 pm.id = makeID(IDTypePointMap, id) 931 pm.A = makeID(IDTypePoint, a) 932 pm.B = makeID(IDTypePoint, b) 933 return pm, nil 934 } 935 936 func scanFurniture(row Scanner) (Furniture, error) { 937 var f Furniture 938 var id int64 939 940 err := row.Scan(&id, &f.Type, &f.UserType, &f.Style, &f.Name, &f.Width, &f.Depth) 941 if err != nil { 942 return f, err 943 } 944 f.id = makeID(IDTypeFurniture, id) 945 return f, nil 946 } 947 948 func scanFurnitureMap(row Scanner) (FurnitureMap, error) { 949 var m FurnitureMap 950 var id int64 951 var fid int64 952 953 err := row.Scan(&id, &fid, &m.Layout, &m.X, &m.Y, &m.Angle) 954 if err != nil { 955 return m, err 956 } 957 m.id = makeID(IDTypeFurnitureMap, id) 958 m.FurnitureID = makeID(IDTypeFurniture, fid) 959 return m, nil 960 } 961 962 func mapArray[K comparable, V any](array []V, mapper func(V) (K, error)) (map[K]V, error) { 963 m := make(map[K]V, len(array)) 964 965 for _, v := range array { 966 key, err := mapper(v) 967 if err != nil { 968 return nil, err 969 } 970 m[key] = v 971 } 972 return m, nil 973 } 974 975 func mapPoint(p Point) (ObjectID, error) { 976 return p.id, nil 977 } 978 979 func mapPointMap(pm PointMap) (ObjectID, error) { 980 return pm.id, nil 981 } 982 983 func mapFurniture(f Furniture) (ObjectID, error) { 984 return f.id, nil 985 } 986 987 func mapFurnitureMap(m FurnitureMap) (ObjectID, error) { 988 return m.id, nil 989 } 990 991 func (o ObjectID) Path() string { 992 /* Type should be verified, so this shouldn't panic */ 993 return fmt.Sprintf("/%s/%s", tables[o.Type], o) 994 } 995 996 func (id ObjectID) Error(context string, reason error) PatchError { 997 return patchError(&id, context, reason) 998 } 999 1000 func patchError(id *ObjectID, context string, reason error) PatchError { 1001 if id != nil { 1002 // Copy 1003 d := *id 1004 id = &d 1005 } 1006 return PatchError{ID: id, Context: context, Reason: reason} 1007 } 1008 1009 func (e PatchError) Error() string { 1010 var s string 1011 1012 if e.ID != nil { 1013 s = fmt.Sprintf("%s", e.ID) 1014 } 1015 if len(e.Context) > 0 { 1016 s = fmt.Sprintf("%s: %s", s, e.Context) 1017 } 1018 if e.Reason != nil { 1019 s = fmt.Sprintf("%s: %s", s, e.Reason.Error()) 1020 } 1021 return s 1022 }