location.go
package cmd
import (
"context"
"flag"
"fmt"
"strconv"
"skraak/tools"
)
// RunLocationCreate creates a new location with GPS coordinates.
//
// JSON output schema:
//
// {
// "location": {
// "id": string, // Location ID (12 characters)
// "dataset_id": string, // Parent dataset ID
// "name": string, // Location name
// "latitude": float, // Latitude in decimal degrees
// "longitude": float, // Longitude in decimal degrees
// "description": string, // Optional description (nullable)
// "created_at": string, // Creation timestamp (RFC3339)
// "last_modified": string, // Last modification timestamp (RFC3339)
// "active": bool, // Whether the location is active
// "timezone_id": string // IANA timezone ID
// },
// "message": string // Success message
// }
func RunLocationCreate(args []string) error {
fs := flag.NewFlagSet("location create", flag.ExitOnError)
dbPath := fs.String("db", "", "Path to DuckDB database (required)")
datasetID := fs.String("dataset", "", "Dataset ID (required)")
name := fs.String("name", "", "Location name (required)")
lat := fs.String("lat", "", "Latitude in decimal degrees (required)")
lon := fs.String("lon", "", "Longitude in decimal degrees (required)")
tz := fs.String("timezone", "", "IANA timezone ID (required, e.g. Pacific/Auckland)")
description := fs.String("description", "", "Location description (optional)")
fs.Usage = usagePrinter(fs,
"skraak location create [options]",
"Create a new location with GPS coordinates.",
"skraak location create --db ./db/skraak.duckdb --dataset abc123 --name \"Site A\" --lat -36.85 --lon 174.76 --timezone Pacific/Auckland",
)
if err := fs.Parse(args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if err := requireFlags(fs, map[string]any{
"--db": *dbPath,
"--dataset": *datasetID,
"--name": *name,
"--lat": *lat,
"--lon": *lon,
"--timezone": *tz,
}); err != nil {
return err
}
latitude, err := strconv.ParseFloat(*lat, 64)
if err != nil {
return fmt.Errorf("invalid latitude: %w", err)
}
longitude, err := strconv.ParseFloat(*lon, 64)
if err != nil {
return fmt.Errorf("invalid longitude: %w", err)
}
defer initEventLog(*dbPath)()
input := tools.LocationInput{
DBPath: *dbPath,
DatasetID: datasetID,
Name: name,
Latitude: &latitude,
Longitude: &longitude,
TimezoneID: tz,
Description: description,
}
output, err := tools.CreateOrUpdateLocation(context.Background(), input)
if err != nil {
return fmt.Errorf("creating location: %w", err)
}
return printJSON(output)
}
// RunLocationUpdate updates an existing location.
//
// JSON output schema: same as RunLocationCreate
func RunLocationUpdate(args []string) error {
fs := flag.NewFlagSet("location update", flag.ExitOnError)
dbPath := fs.String("db", "", "Path to DuckDB database (required)")
id := fs.String("id", "", "Location ID (required)")
name := fs.String("name", "", "New location name (optional)")
lat := fs.String("lat", "", "New latitude (optional)")
lon := fs.String("lon", "", "New longitude (optional)")
tz := fs.String("timezone", "", "New IANA timezone ID (optional)")
description := fs.String("description", "", "New location description (optional)")
fs.Usage = usagePrinter(fs,
"skraak location update [options]",
"Update an existing location. Only provided fields are updated.",
"skraak location update --db ./db/skraak.duckdb --id loc123 --name \"New Name\"",
)
if err := fs.Parse(args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--id": *id}); err != nil {
return err
}
defer initEventLog(*dbPath)()
input, err := parseLocationUpdateInput(dbPath, id, name, lat, lon, tz, description)
if err != nil {
return err
}
output, err := tools.CreateOrUpdateLocation(context.Background(), input)
if err != nil {
return fmt.Errorf("updating location: %w", err)
}
return printJSON(output)
}
// parseLocationUpdateInput builds a LocationInput from parsed flags, handling optional float parsing.
func parseLocationUpdateInput(dbPath, id, name, lat, lon, tz, description *string) (tools.LocationInput, error) {
var latitude, longitude *float64
if *lat != "" {
latVal, err := strconv.ParseFloat(*lat, 64)
if err != nil {
return tools.LocationInput{}, fmt.Errorf("invalid latitude: %w", err)
}
latitude = &latVal
}
if *lon != "" {
lonVal, err := strconv.ParseFloat(*lon, 64)
if err != nil {
return tools.LocationInput{}, fmt.Errorf("invalid longitude: %w", err)
}
longitude = &lonVal
}
input := tools.LocationInput{
DBPath: *dbPath,
ID: id,
}
if *name != "" {
input.Name = name
}
if latitude != nil {
input.Latitude = latitude
}
if longitude != nil {
input.Longitude = longitude
}
if *tz != "" {
input.TimezoneID = tz
}
if *description != "" {
input.Description = description
}
return input, nil
}