Files
home-automation-backend/src/components/pooRecorder/pooRecorder.go

211 lines
6.0 KiB
Go
Raw Normal View History

package pooRecorder
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"log/slog"
"github.com/go-co-op/gocron/v2"
"github.com/spf13/viper"
"github.com/t-liu93/home-automation-backend/util/homeassistantutil"
"github.com/t-liu93/home-automation-backend/util/notion"
_ "modernc.org/sqlite"
)
var (
db *sql.DB
scheduler *gocron.Scheduler
)
type recordDetail struct {
Status string `json:"status"`
Latitude string `json:"latitude"`
Longitude string `json:"longitude"`
}
type pooStatusSensorAttributes struct {
LastPoo string `json:"last_poo"`
}
type pooStatusWebhookBody struct {
Status string `json:"status"`
}
type pooStatusDbEntry struct {
Timestamp string
Status string
Latitude float64
Longitude float64
}
func Init(mainScheduler *gocron.Scheduler) {
initDb()
initScheduler(mainScheduler)
}
func HandleRecordPoo(w http.ResponseWriter, r *http.Request) {
var record recordDetail
if !viper.InConfig("pooRecorder.tableId") {
slog.Warn("HandleRecordPoo Table ID not found in config file")
http.Error(w, "Table ID not found in config file", http.StatusInternalServerError)
return
}
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
err := decoder.Decode(&record)
if err != nil {
slog.Warn(fmt.Sprintln("HandleRecordPoo Error decoding request body", err))
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
now := time.Now()
err = storeStatus(record, now)
if err != nil {
slog.Warn(fmt.Sprintln("HandleRecordPoo Error storing status", err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
pooStatus := homeassistantutil.HttpSensor{
EntityId: "sensor.test_poo_sensor",
State: record.Status,
Attributes: pooStatusSensorAttributes{
LastPoo: now.Format("Mon | 2006-01-02 | 15:04"),
},
}
homeassistantutil.PublishSensor(pooStatus)
if viper.InConfig("pooRecorder.webhookId") {
homeassistantutil.TriggerWebhook(viper.GetString("pooRecorder.webhookId"), pooStatusWebhookBody{Status: record.Status})
} else {
slog.Warn("HandleRecordPoo Webhook ID not found in config file")
}
}
func HandleNotifyLatestPoo(w http.ResponseWriter, r *http.Request) {
var latest pooStatusDbEntry
err := db.QueryRow(`SELECT timestamp, status, latitude, longitude FROM poo_records ORDER BY timestamp DESC LIMIT 1`).Scan(&latest.Timestamp, &latest.Status, &latest.Latitude, &latest.Longitude)
if err != nil {
slog.Warn(fmt.Sprintln("HandleGetLatestPoo Error getting latest poo", err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
recordTime, err := time.Parse("2006-01-02T15:04Z07:00", latest.Timestamp)
if err != nil {
slog.Warn(fmt.Sprintln("HandleGetLatestPoo Error parsing timestamp", err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
recordTime = recordTime.Local()
pooStatus := homeassistantutil.HttpSensor{
EntityId: "sensor.test_poo_sensor",
State: latest.Status,
Attributes: pooStatusSensorAttributes{
LastPoo: recordTime.Format("Mon | 2006-01-02 | 15:04"),
},
}
homeassistantutil.PublishSensor(pooStatus)
slog.Debug(fmt.Sprintln("HandleGetLatestPoo Latest poo", pooStatus.State, "at", pooStatus.Attributes.(pooStatusSensorAttributes).LastPoo))
}
func initDb() {
if !viper.InConfig("pooRecorder.dbPath") {
slog.Info("HandleRecordPoo dbPath not found in config file, using default: pooRecorder.db")
viper.SetDefault("pooRecorder.dbPath", "pooRecorder.db")
}
dbPath := viper.GetString("pooRecorder.dbPath")
err := error(nil)
db, err = sql.Open("sqlite", dbPath)
if err != nil {
slog.Error(fmt.Sprintln("PooRecorderInit Error opening database", err))
os.Exit(1)
}
err = db.Ping()
if err != nil {
slog.Error(fmt.Sprintln("PooRecorderInit 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("PooRecorderInit Error getting db user version", err))
os.Exit(1)
}
if userVersion == 0 {
migrateDb0To1(&userVersion)
}
}
func migrateDb0To1(userVersion *int) {
// this is actually create new db
slog.Info("Creating database version 1..")
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS poo_records (
timestamp TEXT NOT NULL,
status TEXT NOT NULL,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
PRIMARY KEY (timestamp))`)
if err != nil {
slog.Error(fmt.Sprintln("PooRecorderInit Error creating table", err))
os.Exit(1)
}
_, err = db.Exec(`PRAGMA user_version = 1`)
if err != nil {
slog.Error(fmt.Sprintln("PooRecorderInit Error setting user version to 1", err))
os.Exit(1)
}
*userVersion = 1
}
func initScheduler(mainScheduler *gocron.Scheduler) {
scheduler = mainScheduler
_, err := (*scheduler).NewJob(gocron.CronJob("0 5 * * *", false), gocron.NewTask(
per,
))
if err != nil {
slog.Error(fmt.Sprintln("PooRecorderInit Error creating scheduled task", err))
os.Exit(1)
}
}
func per() {
slog.Info("PooRecorderInit Running scheduled task ")
}
func storeStatus(record recordDetail, timestamp time.Time) error {
tableId := viper.GetString("pooRecorder.tableId")
recordDate := timestamp.Format("2006-01-02")
recordTime := timestamp.Format("15:04")
slog.Debug(fmt.Sprintln("Recording poo", record.Status, "at", record.Latitude, record.Longitude))
_, err := db.Exec(`INSERT OR IGNORE INTO poo_records (timestamp, status, latitude, longitude) VALUES (?, ?, ?, ?)`,
timestamp.UTC().Format("2006-01-02T15:04Z07:00"), record.Status, record.Latitude, record.Longitude)
if err != nil {
return err
}
go func() {
header, err := notion.GetTableRows(tableId, 1, "")
if err != nil {
slog.Warn(fmt.Sprintln("HandleRecordPoo Failed to get table header", err))
return
}
if len(header) == 0 {
slog.Warn("HandleRecordPoo Table header not found")
return
}
headerId := header[0].GetID()
err = notion.WriteTableRow([]string{recordDate, recordTime, record.Status, record.Latitude + "," + record.Longitude}, tableId, headerId.String())
if err != nil {
slog.Warn(fmt.Sprintln("HandleRecordPoo Failed to write table row", err))
}
}()
return nil
}