package store import ( "context" "errors" "github.com/jackc/pgx/v5" ) // Resolving a public reference to the uuid it names. // // Every id in this schema is a uuid and stays one. These three exist because a // uuid is not something a person can say, type or recognise, and three of the // four things anyone addresses by URL already HAD a human name that the API // simply refused to accept: a site has a slug, a camera has the id the engine // knows it by (and the one that lands in `visits.camera_id`), and a visitor now // has a per-tenant number. See api/refs.go for the formats. // // All three answer "" with a nil error when nothing matches. A found id is // never empty, so the two cases cannot be confused, and a mistyped reference is // a 404 rather than an error the handler has to classify. // SiteIDBySlug resolves a site slug within one tenant. func (s *Store) SiteIDBySlug(ctx context.Context, clientID, slug string) (string, error) { var id string err := s.pool.QueryRow(ctx, ` SELECT id::text FROM sites WHERE client_id = $1::uuid AND slug = $2`, clientID, slug).Scan(&id) if errors.Is(err, pgx.ErrNoRows) { return "", nil } return id, err } // CameraIDByRef resolves the engine's own camera id - "Office1" - to the row // uuid, within one tenant. // // A camera id is unique per SITE, not per tenant, so two shops may each have an // "Office1". Ambiguity is resolved as no match rather than by picking one: // silently acting on whichever row sorted first would edit or delete the wrong // shop's camera. A caller in that position has the uuid, or can scope by site. func (s *Store) CameraIDByRef(ctx context.Context, clientID, ref string) (string, error) { rows, err := s.pool.Query(ctx, ` SELECT id::text FROM site_cameras WHERE client_id = $1::uuid AND camera_id = $2 AND deleted_at IS NULL LIMIT 2`, clientID, ref) if err != nil { return "", err } defer rows.Close() var found []string for rows.Next() { var id string if err := rows.Scan(&id); err != nil { return "", err } found = append(found, id) } if err := rows.Err(); err != nil { return "", err } if len(found) != 1 { return "", nil } return found[0], nil } // VisitorIDByNumber resolves the number behind "V-42" within one tenant. func (s *Store) VisitorIDByNumber(ctx context.Context, clientID string, number int64) (string, error) { var id string err := s.pool.QueryRow(ctx, ` SELECT id::text FROM visitors WHERE client_id = $1::uuid AND number = $2`, clientID, number).Scan(&id) if errors.Is(err, pgx.ErrNoRows) { return "", nil } return id, err }