Ported location recorder
This commit is contained in:
194
src/components/locationRecorder/locationRecorder.go
Normal file
194
src/components/locationRecorder/locationRecorder.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package locationRecorder
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
db *sql.DB
|
||||
)
|
||||
|
||||
const (
|
||||
currentDBVersion = 2
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
Person string `json:"person"`
|
||||
DateTime string `json:"datetime"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Altitude sql.NullFloat64 `json:"altitude,omitempty"`
|
||||
}
|
||||
|
||||
type LocationContent struct {
|
||||
Person string `json:"person"`
|
||||
Latitude string `json:"latitude"`
|
||||
Longitude string `json:"longitude"`
|
||||
Altitude string `json:"altitude,omitempty"`
|
||||
}
|
||||
|
||||
func Init() {
|
||||
initDb()
|
||||
}
|
||||
|
||||
func HandleRecordLocation(w http.ResponseWriter, r *http.Request) {
|
||||
var location LocationContent
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
err := decoder.Decode(&location)
|
||||
if err != nil {
|
||||
slog.Warn(fmt.Sprintln("HandleRecordLocation Error decoding request body", err))
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
latiF64, _ := strconv.ParseFloat(location.Latitude, 64)
|
||||
longiF64, _ := strconv.ParseFloat(location.Longitude, 64)
|
||||
altiF64, _ := strconv.ParseFloat(location.Altitude, 64)
|
||||
InsertLocationNow(location.Person, latiF64, longiF64, altiF64)
|
||||
}
|
||||
|
||||
func InsertLocation(person string, datetime time.Time, latitude float64, longitude float64, altitude float64) {
|
||||
_, err := db.Exec(`INSERT OR IGNORE INTO location (person, datetime, latitude, longitude, altitude) VALUES (?, ?, ?, ?, ?)`,
|
||||
person, datetime.UTC().Format(time.RFC3339), latitude, longitude, altitude)
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorder.InsertLocation Error inserting location", err))
|
||||
}
|
||||
}
|
||||
|
||||
func InsertLocationNow(person string, latitude float64, longitude float64, altitude float64) {
|
||||
InsertLocation(person, time.Now(), latitude, longitude, altitude)
|
||||
}
|
||||
|
||||
func initDb() {
|
||||
if !viper.InConfig("locationRecorder.dbPath") {
|
||||
slog.Info("LocationRecorderInit dbPath not found in config file, using default: location_recorder.db")
|
||||
viper.SetDefault("locationRecorder.dbPath", "location_recorder.db")
|
||||
}
|
||||
|
||||
dbPath := viper.GetString("locationRecorder.dbPath")
|
||||
err := error(nil)
|
||||
db, err = sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit Error opening database", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit Error pinging database", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
migrateDb()
|
||||
}
|
||||
|
||||
func migrateDb() {
|
||||
var userVersion int
|
||||
err := db.QueryRow("PRAGMA user_version").Scan(&userVersion)
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit Error getting db user version", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
if userVersion == 0 {
|
||||
migrateDb0To1(&userVersion)
|
||||
}
|
||||
if userVersion == 1 {
|
||||
migrateDb1To2(&userVersion)
|
||||
}
|
||||
if userVersion != currentDBVersion {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit Error unsupported database version", userVersion))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateDb0To1(userVersion *int) {
|
||||
// this is actually create new db
|
||||
slog.Info("Creating location recorder database version 1..")
|
||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS location (
|
||||
person TEXT NOT NULL,
|
||||
datetime TEXT NOT NULL,
|
||||
latitude REAL NOT NULL,
|
||||
longitude REAL NOT NULL,
|
||||
altitude REAL,
|
||||
PRIMARY KEY (person, datetime))`)
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit DB0To1 Error creating table", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
_, err = db.Exec(`PRAGMA user_version = 1`)
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit DB0To1 Error setting user version to 1", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
*userVersion = 1
|
||||
}
|
||||
|
||||
func migrateDb1To2(userVersion *int) {
|
||||
// this will change the datetime format into Real RFC3339
|
||||
slog.Info("Migrating location recorder database version 1 to 2..")
|
||||
dbTx, err := db.Begin()
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit DB1To2 Error beginning transaction", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
fail := func(err error, step string) {
|
||||
slog.Error(fmt.Sprintf("LocationRecorderInit DB1To2 Error %s: %s", step, err))
|
||||
dbTx.Rollback()
|
||||
os.Exit(1)
|
||||
}
|
||||
_, err = dbTx.Exec(`ALTER TABLE location RENAME TO location_old`)
|
||||
if err != nil {
|
||||
fail(err, "renaming table")
|
||||
}
|
||||
_, err = dbTx.Exec(`CREATE TABLE IF NOT EXISTS location (
|
||||
person TEXT NOT NULL,
|
||||
datetime TEXT NOT NULL,
|
||||
latitude REAL NOT NULL,
|
||||
longitude REAL NOT NULL,
|
||||
altitude REAL,
|
||||
PRIMARY KEY (person, datetime))`)
|
||||
if err != nil {
|
||||
fail(err, "creating new table")
|
||||
}
|
||||
row, err := dbTx.Query(`SELECT person, datetime, latitude, longitude, altitude FROM location_old`)
|
||||
if err != nil {
|
||||
fail(err, "selecting from old table")
|
||||
}
|
||||
defer row.Close()
|
||||
for row.Next() {
|
||||
var location Location
|
||||
err = row.Scan(&location.Person, &location.DateTime, &location.Latitude, &location.Longitude, &location.Altitude)
|
||||
if err != nil {
|
||||
fail(err, "scanning row")
|
||||
}
|
||||
dateTime, err := time.Parse("2006-01-02T15:04:05-0700", location.DateTime)
|
||||
if err != nil {
|
||||
fail(err, "parsing datetime")
|
||||
}
|
||||
_, err = dbTx.Exec(`INSERT INTO location (person, datetime, latitude, longitude, altitude) VALUES (?, ?, ?, ?, ?)`, location.Person, dateTime.UTC().Format(time.RFC3339), location.Latitude, location.Longitude, location.Altitude)
|
||||
if err != nil {
|
||||
fail(err, "inserting new row")
|
||||
}
|
||||
}
|
||||
|
||||
_, err = dbTx.Exec(`DROP TABLE location_old`)
|
||||
if err != nil {
|
||||
fail(err, "dropping old table")
|
||||
}
|
||||
|
||||
_, err = dbTx.Exec(`PRAGMA user_version = 2`)
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintln("LocationRecorderInit Error setting user version to 2", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
dbTx.Commit()
|
||||
*userVersion = 2
|
||||
}
|
||||
Reference in New Issue
Block a user