From a08f38180070e1bf8ebe2735615cee0c9e456639 Mon Sep 17 00:00:00 2001 From: blazSmehov Date: Tue, 24 Feb 2026 16:04:29 +0100 Subject: [PATCH] feat: add switching between ml and filter algorithm, various fixes in context use, script for creating docker images, various bug fixes regarding persistence in db, persisting alerts in db and CRUD operations for alerts --- build/docker-compose.dev.yml | 62 ++------ build/docker-compose.yaml | 66 +++------ build/env/db.env | 2 + build/env/kafdrop.env | 1 + build/env/kafka-init.env | 1 + build/env/kafka.env | 13 ++ build/env/presense-bridge.env | 5 + build/env/presense-decoder.env | 1 + build/env/presense-location.env | 8 + build/env/presense-server.env | 15 ++ build/package/Dockerfile.decoder | 1 - build/package/Dockerfile.server | 1 + internal/app/location/app.go | 31 +++- internal/app/server/events.go | 12 +- internal/app/server/routes.go | 38 ++--- internal/pkg/apiclient/data.go | 1 + internal/pkg/common/appcontext/context.go | 10 +- internal/pkg/config/config.go | 75 +++++----- internal/pkg/controller/alerts_controller.go | 50 +++++++ .../pkg/controller/gateways_controller.go | 57 ++++--- internal/pkg/controller/parser_controller.go | 80 +++++----- .../pkg/controller/settings_controller.go | 42 ++---- .../pkg/controller/trackers_controller.go | 77 +++++----- .../pkg/controller/trackerzones_controller.go | 52 ++++--- internal/pkg/controller/tracks_controller.go | 14 +- internal/pkg/controller/zone_controller.go | 52 ++++--- internal/pkg/database/database.go | 2 +- internal/pkg/location/inference.go | 20 ++- internal/pkg/model/alerts.go | 8 + internal/pkg/model/trackers.go | 5 +- internal/pkg/model/types.go | 10 +- internal/pkg/service/alert_service.go | 39 +++++ internal/pkg/service/beacon_service.go | 140 +++++++++++++----- scripts/build/build.sh | 15 ++ 34 files changed, 630 insertions(+), 376 deletions(-) create mode 100644 build/env/db.env create mode 100644 build/env/kafdrop.env create mode 100644 build/env/kafka-init.env create mode 100644 build/env/kafka.env create mode 100644 build/env/presense-bridge.env create mode 100644 build/env/presense-decoder.env create mode 100644 build/env/presense-location.env create mode 100644 build/env/presense-server.env create mode 100644 internal/pkg/controller/alerts_controller.go create mode 100644 internal/pkg/model/alerts.go create mode 100644 internal/pkg/service/alert_service.go create mode 100755 scripts/build/build.sh diff --git a/build/docker-compose.dev.yml b/build/docker-compose.dev.yml index 2c73db6..a7501b7 100644 --- a/build/docker-compose.dev.yml +++ b/build/docker-compose.dev.yml @@ -6,9 +6,8 @@ services: restart: always ports: - "127.0.0.1:5432:5432" - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres + env_file: + - ./env/db.env healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s @@ -21,8 +20,8 @@ services: restart: "no" ports: - "127.0.0.1:9000:9000" - environment: - KAFKA_BROKERCONNECT: "kafka:29092" + env_file: + - ./env/kafdrop.env depends_on: - "kafka" kafka: @@ -32,26 +31,14 @@ services: # - "127.0.0.1:2181:2181" - "127.0.0.1:9092:9092" - "127.0.0.1:9093:9093" + env_file: + - ./env/kafka.env healthcheck: # <-- ADD THIS BLOCK test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list"] interval: 10s timeout: 5s retries: 10 start_period: 20s - environment: - KAFKA_NODE_ID: 1 - KAFKA_PROCESS_ROLES: broker,controller - KAFKA_LISTENERS: INTERNAL://:29092,EXTERNAL://:9092,CONTROLLER://127.0.0.1:9093 - KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092,EXTERNAL://localhost:9092 - KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT - KAFKA_CONTROLLER_QUORUM_VOTERS: 1@127.0.0.1:9093 - KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 - KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 - KAFKA_NUM_PARTITIONS: 3 kafka-init: image: apache/kafka:3.9.0 @@ -61,8 +48,8 @@ services: condition: service_healthy volumes: - ./init-scripts/create_topic.sh:/tmp/create_topic.sh - environment: - - TOPIC_NAMES=topic1,topic2,topic3 + env_file: + - ./env/kafka-init.env valkey: image: valkey/valkey:9.0.0 @@ -76,8 +63,8 @@ services: dockerfile: build/package/Dockerfile.dev image: presense-decoder container_name: presense-decoder - environment: - - KAFKA_URL=kafka:29092 + env_file: + - ./env/presense-decoder.env depends_on: kafka-init: condition: service_completed_successfully @@ -94,21 +81,8 @@ services: dockerfile: build/package/Dockerfile.dev image: presense-server container_name: presense-server - environment: - - VALKEY_URL=valkey:6379 - - KAFKA_URL=kafka:29092 - - DBHost=db - - DBUser=postgres - - DBPass=postgres - - DBName=postgres - - HTTPClientID=Fastapi - - ClientSecret=wojuoB7Z5xhlPFrF2lIxJSSdVHCApEgC - - HTTPUsername=core - - HTTPPassword=C0r3_us3r_Cr3d3nt14ls - - HTTPAudience=Fastapi - - HTTPADDR=0.0.0.0:1902 - - CONFIG_PATH=/app/cmd/server/config.json - - API_BASE_URL=https://10.251.0.30:5050 + env_file: + - ./env/presense-server.env ports: - "127.0.0.1:1902:1902" depends_on: @@ -129,12 +103,8 @@ services: dockerfile: build/package/Dockerfile.dev image: presense-bridge container_name: presense-bridge - environment: - - KAFKA_URL=kafka:29092 - - MQTT_HOST=192.168.1.101 - - MQTT_USERNAME=user - - MQTT_PASSWORD=pass - - MQTT_CLIENT_ID=bridge + env_file: + - ./env/presense-bridge.env depends_on: kafka-init: condition: service_completed_successfully @@ -151,8 +121,8 @@ services: dockerfile: build/package/Dockerfile.dev image: presense-location container_name: presense-location - environment: - - KAFKA_URL=kafka:29092 + env_file: + - ./env/presense-location.env depends_on: kafka-init: condition: service_completed_successfully diff --git a/build/docker-compose.yaml b/build/docker-compose.yaml index 0f85a0d..780e9bb 100644 --- a/build/docker-compose.yaml +++ b/build/docker-compose.yaml @@ -1,4 +1,3 @@ -version: "2" services: db: image: postgres @@ -6,9 +5,8 @@ services: restart: always ports: - "127.0.0.1:5432:5432" - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres + env_file: + - ./env/db.env healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s @@ -21,37 +19,24 @@ services: restart: "no" ports: - "127.0.0.1:9000:9000" - environment: - KAFKA_BROKERCONNECT: "kafka:29092" + env_file: + - ./env/kafdrop.env depends_on: - "kafka" kafka: image: apache/kafka:3.9.0 restart: "no" ports: - # - "127.0.0.1:2181:2181" - "127.0.0.1:9092:9092" - "127.0.0.1:9093:9093" - healthcheck: # <-- ADD THIS BLOCK + env_file: + - ./env/kafka.env + healthcheck: test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list"] interval: 10s timeout: 5s retries: 10 start_period: 20s - environment: - KAFKA_NODE_ID: 1 - KAFKA_PROCESS_ROLES: broker,controller - KAFKA_LISTENERS: INTERNAL://:29092,EXTERNAL://:9092,CONTROLLER://127.0.0.1:9093 - KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092,EXTERNAL://localhost:9092 - KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT - KAFKA_CONTROLLER_QUORUM_VOTERS: 1@127.0.0.1:9093 - KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 - KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 - KAFKA_NUM_PARTITIONS: 3 kafka-init: image: apache/kafka:3.9.0 @@ -61,8 +46,8 @@ services: condition: service_healthy volumes: - ./init-scripts/create_topic.sh:/tmp/create_topic.sh - environment: - - TOPIC_NAMES=topic1,topic2,topic3 + env_file: + - ./env/kafka-init.env valkey: image: valkey/valkey:9.0.0 @@ -76,8 +61,8 @@ services: dockerfile: build/package/Dockerfile.decoder image: presense-decoder container_name: presense-decoder - environment: - - KAFKA_URL=kafka:29092 + env_file: + - ./env/presense-decoder.env depends_on: kafka-init: condition: service_completed_successfully @@ -91,21 +76,8 @@ services: dockerfile: build/package/Dockerfile.server image: presense-server container_name: presense-server - environment: - - KAFKA_URL=kafka:29092 - - DBHost=db - - DBUser=postgres - - DBPass=postgres - - DBName=postgres - - HTTPClientID=Fastapi - - ClientSecret=wojuoB7Z5xhlPFrF2lIxJSSdVHCApEgC - - HTTPUsername=core - - HTTPPassword=C0r3_us3r_Cr3d3nt14ls - - HTTPAudience=Fastapi - - HTTPADDR=0.0.0.0:1902 - - CONFIG_PATH=/app/cmd/server/config.json - - API_BASE_URL=https://10.251.0.30:5050 - - API_AUTH_URL=https://10.251.0.30:10002 + env_file: + - ./env/presense-server.env ports: - "127.0.0.1:1902:1902" depends_on: @@ -123,12 +95,8 @@ services: dockerfile: build/package/Dockerfile.bridge image: presense-bridge container_name: presense-bridge - environment: - - KAFKA_URL=kafka:29092 - - MQTT_HOST=192.168.1.101 - - MQTT_USERNAME=user - - MQTT_PASSWORD=pass - - MQTT_CLIENT_ID=bridge + env_file: + - ./env/presense-bridge.env depends_on: kafka-init: condition: service_completed_successfully @@ -142,8 +110,8 @@ services: dockerfile: build/package/Dockerfile.location image: presense-location container_name: presense-location - environment: - - KAFKA_URL=kafka:29092 + env_file: + - ./env/presense-location.env depends_on: kafka-init: condition: service_completed_successfully diff --git a/build/env/db.env b/build/env/db.env new file mode 100644 index 0000000..b0f3e3d --- /dev/null +++ b/build/env/db.env @@ -0,0 +1,2 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres \ No newline at end of file diff --git a/build/env/kafdrop.env b/build/env/kafdrop.env new file mode 100644 index 0000000..63b3a1e --- /dev/null +++ b/build/env/kafdrop.env @@ -0,0 +1 @@ +KAFKA_BROKERCONNECT=kafka:29092 \ No newline at end of file diff --git a/build/env/kafka-init.env b/build/env/kafka-init.env new file mode 100644 index 0000000..b2277e8 --- /dev/null +++ b/build/env/kafka-init.env @@ -0,0 +1 @@ +TOPIC_NAMES=topic1,topic2,topic3 diff --git a/build/env/kafka.env b/build/env/kafka.env new file mode 100644 index 0000000..5a3f7f8 --- /dev/null +++ b/build/env/kafka.env @@ -0,0 +1,13 @@ +KAFKA_NODE_ID=1 +KAFKA_PROCESS_ROLES=broker,controller +KAFKA_LISTENERS=INTERNAL://:29092,EXTERNAL://:9092,CONTROLLER://127.0.0.1:9093 +KAFKA_ADVERTISED_LISTENERS=INTERNAL://kafka:29092,EXTERNAL://localhost:9092 +KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER +KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT +KAFKA_CONTROLLER_QUORUM_VOTERS=1@127.0.0.1:9093 +KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL +KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 +KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 +KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 +KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS=0 +KAFKA_NUM_PARTITIONS=3 \ No newline at end of file diff --git a/build/env/presense-bridge.env b/build/env/presense-bridge.env new file mode 100644 index 0000000..7fa1315 --- /dev/null +++ b/build/env/presense-bridge.env @@ -0,0 +1,5 @@ +KAFKA_URL=kafka:29092 +MQTT_HOST=192.168.1.101 +MQTT_USERNAME=user +MQTT_PASSWORD=pass +MQTT_CLIENT_ID=bridge diff --git a/build/env/presense-decoder.env b/build/env/presense-decoder.env new file mode 100644 index 0000000..b59bfb8 --- /dev/null +++ b/build/env/presense-decoder.env @@ -0,0 +1 @@ +KAFKA_URL=kafka:29092 diff --git a/build/env/presense-location.env b/build/env/presense-location.env new file mode 100644 index 0000000..441268a --- /dev/null +++ b/build/env/presense-location.env @@ -0,0 +1,8 @@ +KAFKA_URL=kafka:29092 +HTTPClientID=Fastapi +ClientSecret=wojuoB7Z5xhlPFrF2lIxJSSdVHCApEgC +HTTPUsername=core +HTTPPassword=C0r3_us3r_Cr3d3nt14ls +HTTPAudience=Fastapi +API_AUTH_URL=https://10.251.0.30:10002 +ALGORITHM=ai diff --git a/build/env/presense-server.env b/build/env/presense-server.env new file mode 100644 index 0000000..4964fcd --- /dev/null +++ b/build/env/presense-server.env @@ -0,0 +1,15 @@ +KAFKA_URL=kafka:29092 +DBHost=db +DBUser=postgres +DBPass=postgres +DBName=postgres +HTTPClientID=Fastapi +ClientSecret=wojuoB7Z5xhlPFrF2lIxJSSdVHCApEgC +HTTPUsername=core +HTTPPassword=C0r3_us3r_Cr3d3nt14ls +HTTPAudience=Fastapi +HTTPADDR=0.0.0.0:1902 +CONFIG_PATH=/app/cmd/server/config.json +API_BASE_URL=https://10.251.0.30:5050 +API_AUTH_URL=https://10.251.0.30:10002 +ALGORITHM=ai diff --git a/build/package/Dockerfile.decoder b/build/package/Dockerfile.decoder index 1f12ce6..1dc4338 100644 --- a/build/package/Dockerfile.decoder +++ b/build/package/Dockerfile.decoder @@ -13,6 +13,5 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates WORKDIR /app COPY --from=builder /app/decoder . -COPY --from=builder /app/cmd/decoder/config.json ./cmd/decoder/config.json ENTRYPOINT ["./decoder"] diff --git a/build/package/Dockerfile.server b/build/package/Dockerfile.server index 464f1b1..3933a44 100644 --- a/build/package/Dockerfile.server +++ b/build/package/Dockerfile.server @@ -13,6 +13,7 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates WORKDIR /app COPY --from=builder /app/server . +COPY --from=builder /app/cmd/server/config.json ./cmd/server/config.json EXPOSE 1902 ENTRYPOINT ["./server"] diff --git a/internal/app/location/app.go b/internal/app/location/app.go index 88ac84c..14f9692 100644 --- a/internal/app/location/app.go +++ b/internal/app/location/app.go @@ -2,7 +2,7 @@ package location import ( "context" - "fmt" + "encoding/json" "log/slog" "sync" "time" @@ -10,9 +10,10 @@ import ( "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/config" "github.com/AFASystems/presence/internal/pkg/kafkaclient" - "github.com/AFASystems/presence/internal/pkg/logger" pkglocation "github.com/AFASystems/presence/internal/pkg/location" + "github.com/AFASystems/presence/internal/pkg/logger" "github.com/AFASystems/presence/internal/pkg/model" + "github.com/segmentio/kafka-go" ) // LocationApp holds dependencies for the location service. @@ -58,7 +59,7 @@ func (a *LocationApp) Run(ctx context.Context) { go kafkaclient.Consume(a.KafkaManager.GetReader("rawbeacons"), a.ChRaw, ctx, &a.wg) go kafkaclient.Consume(a.KafkaManager.GetReader("settings"), a.ChSettings, ctx, &a.wg) - locTicker := time.NewTicker(config.SMALL_TICKER_INTERVAL) + locTicker := time.NewTicker(config.LARGE_TICKER_INTERVAL) defer locTicker.Stop() for { @@ -67,7 +68,7 @@ func (a *LocationApp) Run(ctx context.Context) { return case <-locTicker.C: settings := a.AppState.GetSettings() - slog.Info("location tick", "settings", fmt.Sprintf("%+v", settings)) + slog.Info("current algorithm", "algorithm", settings.CurrentAlgorithm) switch settings.CurrentAlgorithm { case "filter": pkglocation.GetLikelyLocations(a.AppState, a.KafkaManager.GetWriter("locevents")) @@ -77,6 +78,28 @@ func (a *LocationApp) Run(ctx context.Context) { slog.Error("AI inference", "err", err) continue } + + for _, item := range inferred.Items { + r := model.HTTPLocation{ + Method: "AI", + Y: item.Y, + X: item.X, + Z: item.Z, + MAC: item.Mac, + LastSeen: time.Now().Unix(), + } + + js, err := json.Marshal(r) + if err != nil { + slog.Error("marshaling location", "err", err, "beacon_id", item.Mac) + continue + } + + if err := a.KafkaManager.GetWriter("locevents").WriteMessages(ctx, kafka.Message{Value: js}); err != nil { + slog.Error("sending kafka location message", "err", err, "beacon_id", item.Mac) + } + } + slog.Info("AI algorithm", "count", inferred.Count, "items", len(inferred.Items)) } case msg := <-a.ChRaw: diff --git a/internal/app/server/events.go b/internal/app/server/events.go index f5cdeaf..f33fb20 100644 --- a/internal/app/server/events.go +++ b/internal/app/server/events.go @@ -23,9 +23,17 @@ func RunEventLoop(ctx context.Context, a *ServerApp) { case <-ctx.Done(): return case msg := <-a.ChLoc: - service.LocationToBeaconService(msg, a.DB, a.KafkaManager.GetWriter("alert"), ctx) + switch msg.Method { + case "Standard": + service.LocationToBeaconService(msg, a.DB, a.KafkaManager.GetWriter("alert"), ctx) + case "AI": + service.LocationToBeaconServiceAI(msg, a.DB, a.KafkaManager.GetWriter("alert"), ctx) + default: + slog.Error("unknown method", "method", msg.Method) + continue + } + case msg := <-a.ChEvents: - slog.Info("decoder event", "event", msg) id := msg.ID if err := a.DB.First(&model.Tracker{}, "id = ?", id).Error; err != nil { slog.Error("decoder event for untracked beacon", "id", id) diff --git a/internal/app/server/routes.go b/internal/app/server/routes.go index eeef9bb..996b934 100644 --- a/internal/app/server/routes.go +++ b/internal/app/server/routes.go @@ -18,41 +18,45 @@ func (a *ServerApp) RegisterRoutes() http.Handler { r.HandleFunc("/ready", handler.Ready(a.DB)).Methods("GET") // Gateways - r.HandleFunc("/reslevis/getGateways", controller.GatewayListController(a.DB)).Methods("GET") - r.HandleFunc("/reslevis/postGateway", controller.GatewayAddController(a.DB)).Methods("POST") - r.HandleFunc("/reslevis/removeGateway/{id}", controller.GatewayDeleteController(a.DB)).Methods("DELETE") - r.HandleFunc("/reslevis/updateGateway/{id}", controller.GatewayUpdateController(a.DB)).Methods("PUT") + r.HandleFunc("/reslevis/getGateways", controller.GatewayListController(a.DB, a.ctx)).Methods("GET") + r.HandleFunc("/reslevis/postGateway", controller.GatewayAddController(a.DB, a.ctx)).Methods("POST") + r.HandleFunc("/reslevis/removeGateway/{id}", controller.GatewayDeleteController(a.DB, a.ctx)).Methods("DELETE") + r.HandleFunc("/reslevis/updateGateway/{id}", controller.GatewayUpdateController(a.DB, a.ctx)).Methods("PUT") // Zones - r.HandleFunc("/reslevis/getZones", controller.ZoneListController(a.DB)).Methods("GET") - r.HandleFunc("/reslevis/postZone", controller.ZoneAddController(a.DB)).Methods("POST") - r.HandleFunc("/reslevis/removeZone/{id}", controller.ZoneDeleteController(a.DB)).Methods("DELETE") - r.HandleFunc("/reslevis/updateZone", controller.ZoneUpdateController(a.DB)).Methods("PUT") + r.HandleFunc("/reslevis/getZones", controller.ZoneListController(a.DB, a.ctx)).Methods("GET") + r.HandleFunc("/reslevis/postZone", controller.ZoneAddController(a.DB, a.ctx)).Methods("POST") + r.HandleFunc("/reslevis/removeZone/{id}", controller.ZoneDeleteController(a.DB, a.ctx)).Methods("DELETE") + r.HandleFunc("/reslevis/updateZone", controller.ZoneUpdateController(a.DB, a.ctx)).Methods("PUT") // Tracker zones - r.HandleFunc("/reslevis/getTrackerZones", controller.TrackerZoneListController(a.DB)).Methods("GET") - r.HandleFunc("/reslevis/postTrackerZone", controller.TrackerZoneAddController(a.DB)).Methods("POST") - r.HandleFunc("/reslevis/removeTrackerZone/{id}", controller.TrackerZoneDeleteController(a.DB)).Methods("DELETE") - r.HandleFunc("/reslevis/updateTrackerZone", controller.TrackerZoneUpdateController(a.DB)).Methods("PUT") + r.HandleFunc("/reslevis/getTrackerZones", controller.TrackerZoneListController(a.DB, a.ctx)).Methods("GET") + r.HandleFunc("/reslevis/postTrackerZone", controller.TrackerZoneAddController(a.DB, a.ctx)).Methods("POST") + r.HandleFunc("/reslevis/removeTrackerZone/{id}", controller.TrackerZoneDeleteController(a.DB, a.ctx)).Methods("DELETE") + r.HandleFunc("/reslevis/updateTrackerZone", controller.TrackerZoneUpdateController(a.DB, a.ctx)).Methods("PUT") // Trackers - r.HandleFunc("/reslevis/getTrackers", controller.TrackerList(a.DB)).Methods("GET") + r.HandleFunc("/reslevis/getTrackers", controller.TrackerList(a.DB, a.ctx)).Methods("GET") r.HandleFunc("/reslevis/postTracker", controller.TrackerAdd(a.DB, a.KafkaManager.GetWriter("apibeacons"), a.ctx)).Methods("POST") r.HandleFunc("/reslevis/removeTracker/{id}", controller.TrackerDelete(a.DB, a.KafkaManager.GetWriter("apibeacons"), a.ctx)).Methods("DELETE") - r.HandleFunc("/reslevis/updateTracker", controller.TrackerUpdate(a.DB)).Methods("PUT") + r.HandleFunc("/reslevis/updateTracker", controller.TrackerUpdate(a.DB, a.ctx)).Methods("PUT") // Parser configs - r.HandleFunc("/configs/beacons", controller.ParserListController(a.DB)).Methods("GET") + r.HandleFunc("/configs/beacons", controller.ParserListController(a.DB, a.ctx)).Methods("GET") r.HandleFunc("/configs/beacons", controller.ParserAddController(a.DB, a.KafkaManager.GetWriter("parser"), a.ctx)).Methods("POST") r.HandleFunc("/configs/beacons/{id}", controller.ParserUpdateController(a.DB, a.KafkaManager.GetWriter("parser"), a.ctx)).Methods("PUT") r.HandleFunc("/configs/beacons/{id}", controller.ParserDeleteController(a.DB, a.KafkaManager.GetWriter("parser"), a.ctx)).Methods("DELETE") // Settings r.HandleFunc("/reslevis/settings", controller.SettingsUpdateController(a.DB, a.KafkaManager.GetWriter("settings"), a.ctx)).Methods("PATCH") - r.HandleFunc("/reslevis/settings", controller.SettingsListController(a.DB)).Methods("GET") + r.HandleFunc("/reslevis/settings", controller.SettingsListController(a.DB, a.ctx)).Methods("GET") + + r.HandleFunc("/reslevis/alerts/{id}", controller.AlertDeleteController(a.DB, a.ctx)).Methods("DELETE") + r.HandleFunc("/reslevis/alerts", controller.AlertsListController(a.DB, a.ctx)).Methods("GET") + r.HandleFunc("/reslevis/alerts/{id}", controller.ListAlertsByTrackerIDController(a.DB, a.ctx)).Methods("GET") // Tracks - r.HandleFunc("/reslevis/getTracks/{id}", controller.TracksListController(a.DB)).Methods("GET") + r.HandleFunc("/reslevis/getTracks/{id}", controller.TracksListController(a.DB, a.ctx)).Methods("GET") chain := middleware.Recovery(middleware.Logging(middleware.RequestID(middleware.CORS(nil, nil, nil)(r)))) return chain diff --git a/internal/pkg/apiclient/data.go b/internal/pkg/apiclient/data.go index 24a8a5e..3fc119a 100644 --- a/internal/pkg/apiclient/data.go +++ b/internal/pkg/apiclient/data.go @@ -74,6 +74,7 @@ func GetZones(token string, client *http.Client, cfg *config.Config) ([]model.Zo func InferPosition(token string, client *http.Client, cfg *config.Config) (model.PositionResponse, error) { url := fmt.Sprintf("%s/ble-ai/infer", cfg.APIBaseURL) + fmt.Printf("url: %s\n", url) req, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Printf("error new request: %+v\n", err) diff --git a/internal/pkg/common/appcontext/context.go b/internal/pkg/common/appcontext/context.go index 83c3401..9c4d98b 100644 --- a/internal/pkg/common/appcontext/context.go +++ b/internal/pkg/common/appcontext/context.go @@ -3,6 +3,7 @@ package appcontext import ( "fmt" "log/slog" + "os" "github.com/AFASystems/presence/internal/pkg/model" "github.com/mitchellh/mapstructure" @@ -16,6 +17,13 @@ type AppState struct { beaconsLookup model.BeaconsLookup } +func getEnv(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + // NewAppState creates a new application context AppState with default values func NewAppState() *AppState { return &AppState{ @@ -24,7 +32,7 @@ func NewAppState() *AppState { }, settings: model.Settings{ ID: 1, - CurrentAlgorithm: "filter", // possible values filter or AI + CurrentAlgorithm: getEnv("ALGORITHM", "filter"), LocationConfidence: 4, LastSeenThreshold: 15, BeaconMetricSize: 30, diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index ae73c4b..5e3198b 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -56,25 +56,25 @@ func getEnvPanic(key string) string { func Load() *Config { return &Config{ - HTTPAddr: getEnv("HTTP_HOST_PATH", "0.0.0.0:1902"), - WSAddr: getEnv("HTTPWS_HOST_PATH", "0.0.0.0:8088"), - MQTTHost: getEnv("MQTT_HOST", "192.168.1.101"), - MQTTUser: getEnvPanic("MQTT_USERNAME"), - MQTTPass: getEnvPanic("MQTT_PASSWORD"), - MQTTClientID: getEnvPanic("MQTT_CLIENT_ID"), - KafkaURL: getEnv("KAFKA_URL", "127.0.0.1:9092"), - DBHost: getEnv("DBHost", "127.0.0.1"), - DBUser: getEnvPanic("DBUser"), - DBPass: getEnvPanic("DBPass"), - DBName: getEnv("DBName", "go_crud_db"), - HTTPClientID: getEnvPanic("HTTPClientID"), - ClientSecret: getEnvPanic("ClientSecret"), - HTTPUsername: getEnvPanic("HTTPUsername"), - HTTPPassword: getEnvPanic("HTTPPassword"), - HTTPAudience: getEnvPanic("HTTPAudience"), - ConfigPath: getEnv("CONFIG_PATH", "/app/cmd/server/config.json"), - APIBaseURL: getEnv("API_BASE_URL", "https://10.251.0.30:5050"), - TLSInsecureSkipVerify: getEnvBool("TLS_INSECURE_SKIP_VERIFY", false), + HTTPAddr: getEnv("HTTP_HOST_PATH", "0.0.0.0:1902"), + WSAddr: getEnv("HTTPWS_HOST_PATH", "0.0.0.0:8088"), + MQTTHost: getEnv("MQTT_HOST", "192.168.1.101"), + MQTTUser: getEnvPanic("MQTT_USERNAME"), + MQTTPass: getEnvPanic("MQTT_PASSWORD"), + MQTTClientID: getEnvPanic("MQTT_CLIENT_ID"), + KafkaURL: getEnv("KAFKA_URL", "127.0.0.1:9092"), + DBHost: getEnv("DBHost", "127.0.0.1"), + DBUser: getEnvPanic("DBUser"), + DBPass: getEnvPanic("DBPass"), + DBName: getEnv("DBName", "go_crud_db"), + HTTPClientID: getEnvPanic("HTTPClientID"), + ClientSecret: getEnvPanic("ClientSecret"), + HTTPUsername: getEnvPanic("HTTPUsername"), + HTTPPassword: getEnvPanic("HTTPPassword"), + HTTPAudience: getEnvPanic("HTTPAudience"), + ConfigPath: getEnv("CONFIG_PATH", "/app/cmd/server/config.json"), + APIBaseURL: getEnv("API_BASE_URL", "https://10.251.0.30:5050"), + TLSInsecureSkipVerify: getEnvBool("TLS_INSECURE_SKIP_VERIFY", false), } } @@ -86,21 +86,21 @@ func LoadDecoder() *Config { func LoadServer() *Config { return &Config{ - KafkaURL: getEnv("KAFKA_URL", "127.0.0.1:9092"), - HTTPAddr: getEnv("HTTP_HOST_PATH", "0.0.0.0:1902"), - DBHost: getEnv("DBHost", "127.0.0.1"), - DBUser: getEnvPanic("DBUser"), - DBPass: getEnvPanic("DBPass"), - DBName: getEnv("DBName", "go_crud_db"), - HTTPClientID: getEnvPanic("HTTPClientID"), - ClientSecret: getEnvPanic("ClientSecret"), - HTTPUsername: getEnvPanic("HTTPUsername"), - HTTPPassword: getEnvPanic("HTTPPassword"), - HTTPAudience: getEnvPanic("HTTPAudience"), - ConfigPath: getEnv("CONFIG_PATH", "/app/cmd/server/config.json"), - APIBaseURL: getEnv("API_BASE_URL", "https://10.251.0.30:5050"), - APIAuthURL: getEnv("API_AUTH_URL", "https://10.251.0.30:10002"), - TLSInsecureSkipVerify: getEnvBool("TLS_INSECURE_SKIP_VERIFY", false), + KafkaURL: getEnv("KAFKA_URL", "127.0.0.1:9092"), + HTTPAddr: getEnv("HTTP_HOST_PATH", "0.0.0.0:1902"), + DBHost: getEnv("DBHost", "127.0.0.1"), + DBUser: getEnvPanic("DBUser"), + DBPass: getEnvPanic("DBPass"), + DBName: getEnv("DBName", "go_crud_db"), + HTTPClientID: getEnvPanic("HTTPClientID"), + ClientSecret: getEnvPanic("ClientSecret"), + HTTPUsername: getEnvPanic("HTTPUsername"), + HTTPPassword: getEnvPanic("HTTPPassword"), + HTTPAudience: getEnvPanic("HTTPAudience"), + ConfigPath: getEnv("CONFIG_PATH", "/app/cmd/server/config.json"), + APIBaseURL: getEnv("API_BASE_URL", "https://10.251.0.30:5050"), + APIAuthURL: getEnv("API_AUTH_URL", "https://10.251.0.30:10002"), + TLSInsecureSkipVerify: getEnvBool("TLS_INSECURE_SKIP_VERIFY", false), } } @@ -118,6 +118,13 @@ func LoadLocation() *Config { return &Config{ KafkaURL: getEnv("KAFKA_URL", "127.0.0.1:9092"), TLSInsecureSkipVerify: getEnvBool("TLS_INSECURE_SKIP_VERIFY", false), + HTTPClientID: getEnvPanic("HTTPClientID"), + ClientSecret: getEnvPanic("ClientSecret"), + HTTPUsername: getEnvPanic("HTTPUsername"), + HTTPPassword: getEnvPanic("HTTPPassword"), + HTTPAudience: getEnvPanic("HTTPAudience"), + APIAuthURL: getEnv("API_AUTH_URL", "https://10.251.0.30:10002"), + APIBaseURL: getEnv("API_BASE_URL", "https://10.251.0.30:5050"), } } diff --git a/internal/pkg/controller/alerts_controller.go b/internal/pkg/controller/alerts_controller.go new file mode 100644 index 0000000..49c44d1 --- /dev/null +++ b/internal/pkg/controller/alerts_controller.go @@ -0,0 +1,50 @@ +package controller + +import ( + "context" + "errors" + "net/http" + + "github.com/AFASystems/presence/internal/pkg/api/response" + "github.com/AFASystems/presence/internal/pkg/service" + "github.com/gorilla/mux" + "gorm.io/gorm" +) + +func AlertsListController(db *gorm.DB, ctx context.Context) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + alerts, err := service.GetAllAlerts(db, ctx) + if err != nil { + response.InternalError(w, "failed to list alerts", err) + return + } + response.JSON(w, http.StatusOK, alerts) + } +} + +func ListAlertsByTrackerIDController(db *gorm.DB, ctx context.Context) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + alert, err := service.GetAlertById(id, db, ctx) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + response.NotFound(w, "alert not found") + return + } + response.InternalError(w, "failed to get alert", err) + return + } + response.JSON(w, http.StatusOK, alert) + } +} + +func AlertDeleteController(db *gorm.DB, ctx context.Context) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + if err := service.DeleteAlertByTrackerID(id, db, ctx); err != nil { + response.InternalError(w, "failed to delete alert", err) + return + } + response.JSON(w, http.StatusOK, map[string]string{"status": "deleted"}) + } +} diff --git a/internal/pkg/controller/gateways_controller.go b/internal/pkg/controller/gateways_controller.go index fbb9324..f4404e0 100644 --- a/internal/pkg/controller/gateways_controller.go +++ b/internal/pkg/controller/gateways_controller.go @@ -1,73 +1,82 @@ package controller import ( + "context" "encoding/json" "net/http" + "github.com/AFASystems/presence/internal/pkg/api/response" "github.com/AFASystems/presence/internal/pkg/model" "github.com/gorilla/mux" "gorm.io/gorm" ) -func GatewayAddController(db *gorm.DB) http.HandlerFunc { +func GatewayAddController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) var gateway model.Gateway + if err := json.NewDecoder(r.Body).Decode(&gateway); err != nil { + response.BadRequest(w, "invalid request body") + return + } - if err := decoder.Decode(&gateway); err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).Create(&gateway).Error; err != nil { + response.InternalError(w, "failed to create gateway", err) return } - db.Create(&gateway) - w.Write([]byte("ok")) + response.JSON(w, http.StatusCreated, map[string]string{"status": "created"}) } } -func GatewayListController(db *gorm.DB) http.HandlerFunc { +func GatewayListController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var gateways []model.Gateway - db.Find(&gateways) - res, err := json.Marshal(gateways) - if err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).Find(&gateways).Error; err != nil { + response.InternalError(w, "failed to list gateways", err) return } - w.Write(res) + response.JSON(w, http.StatusOK, gateways) } } -func GatewayDeleteController(db *gorm.DB) http.HandlerFunc { +func GatewayDeleteController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - if res := db.Delete(&model.Gateway{}, "id = ?", id); res.RowsAffected == 0 { - http.Error(w, "no gateway with such ID found", 400) + res := db.WithContext(context).Delete(&model.Gateway{}, "id = ?", id) + if res.RowsAffected == 0 { + response.NotFound(w, "gateway not found") + return + } + if res.Error != nil { + response.InternalError(w, "failed to delete gateway", res.Error) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } } -func GatewayUpdateController(db *gorm.DB) http.HandlerFunc { +func GatewayUpdateController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - if err := db.First(&model.Gateway{}, "id = ?", id).Error; err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).First(&model.Gateway{}, "id = ?", id).Error; err != nil { + response.NotFound(w, "gateway not found") return } - decoder := json.NewDecoder(r.Body) var gateway model.Gateway + if err := json.NewDecoder(r.Body).Decode(&gateway); err != nil { + response.BadRequest(w, "invalid request body") + return + } - if err := decoder.Decode(&gateway); err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).Save(&gateway).Error; err != nil { + response.InternalError(w, "failed to update gateway", err) return } - db.Save(&gateway) - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "updated"}) } } diff --git a/internal/pkg/controller/parser_controller.go b/internal/pkg/controller/parser_controller.go index 1bcd2b3..530c136 100644 --- a/internal/pkg/controller/parser_controller.go +++ b/internal/pkg/controller/parser_controller.go @@ -3,10 +3,10 @@ package controller import ( "context" "encoding/json" - "fmt" "log/slog" "net/http" + "github.com/AFASystems/presence/internal/pkg/api/response" "github.com/AFASystems/presence/internal/pkg/model" "github.com/AFASystems/presence/internal/pkg/service" "github.com/gorilla/mux" @@ -14,53 +14,55 @@ import ( "gorm.io/gorm" ) -func ParserAddController(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc { +func ParserAddController(db *gorm.DB, writer *kafka.Writer, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) var config model.Config - - if err := decoder.Decode(&config); err != nil { - http.Error(w, err.Error(), 400) + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + response.BadRequest(w, "invalid request body") return } - db.Create(&config) + if err := db.WithContext(context).Create(&config).Error; err != nil { + response.InternalError(w, "failed to create parser config", err) + return + } kp := model.KafkaParser{ ID: "add", Config: config, } - if err := service.SendParserConfig(kp, writer, ctx); err != nil { - http.Error(w, "Unable to send parser config to kafka broker", 400) - msg := fmt.Sprintf("Unable to send parser config to kafka broker %v", err) - slog.Error(msg) + if err := service.SendParserConfig(kp, writer, context); err != nil { + slog.Error("failed to send parser config to Kafka", "err", err) + response.InternalError(w, "failed to publish parser config", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusCreated, map[string]string{"status": "created"}) } } -func ParserListController(db *gorm.DB) http.HandlerFunc { +func ParserListController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var configs []model.Config - db.Find(&configs) - res, err := json.Marshal(configs) - if err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).Find(&configs).Error; err != nil { + response.InternalError(w, "failed to list parser configs", err) return } - - w.Write(res) + response.JSON(w, http.StatusOK, configs) } } -func ParserDeleteController(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc { +func ParserDeleteController(db *gorm.DB, writer *kafka.Writer, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - if res := db.Delete(&model.Config{}, "name = ?", id); res.RowsAffected == 0 { - http.Error(w, "no parser config with such name found", 400) + res := db.WithContext(context).Delete(&model.Config{}, "name = ?", id) + if res.RowsAffected == 0 { + response.NotFound(w, "parser config not found") + return + } + if res.Error != nil { + response.InternalError(w, "failed to delete parser config", res.Error) return } @@ -69,31 +71,33 @@ func ParserDeleteController(db *gorm.DB, writer *kafka.Writer, ctx context.Conte Name: id, } - if err := service.SendParserConfig(kp, writer, ctx); err != nil { - http.Error(w, "Unable to send parser config to kafka broker", 400) - msg := fmt.Sprintf("Unable to send parser config to kafka broker %v", err) - slog.Error(msg) + if err := service.SendParserConfig(kp, writer, context); err != nil { + slog.Error("failed to send parser config to Kafka", "err", err) + response.InternalError(w, "failed to publish parser config deletion", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } } -func ParserUpdateController(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc { +func ParserUpdateController(db *gorm.DB, writer *kafka.Writer, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - if err := db.First(&model.Config{}, "name = ?", id).Error; err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).First(&model.Config{}, "name = ?", id).Error; err != nil { + response.NotFound(w, "parser config not found") return } - decoder := json.NewDecoder(r.Body) var config model.Config + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + response.BadRequest(w, "invalid request body") + return + } - if err := decoder.Decode(&config); err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).Save(&config).Error; err != nil { + response.InternalError(w, "failed to update parser config", err) return } @@ -103,14 +107,12 @@ func ParserUpdateController(db *gorm.DB, writer *kafka.Writer, ctx context.Conte Config: config, } - db.Save(&config) - if err := service.SendParserConfig(kp, writer, ctx); err != nil { - http.Error(w, "Unable to send parser config to kafka broker", 400) - msg := fmt.Sprintf("Unable to send parser config to kafka broker %v", err) - slog.Error(msg) + if err := service.SendParserConfig(kp, writer, context); err != nil { + slog.Error("failed to send parser config to Kafka", "err", err) + response.InternalError(w, "failed to publish parser config update", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "updated"}) } } diff --git a/internal/pkg/controller/settings_controller.go b/internal/pkg/controller/settings_controller.go index 2cefa50..1ec2d0b 100644 --- a/internal/pkg/controller/settings_controller.go +++ b/internal/pkg/controller/settings_controller.go @@ -3,66 +3,54 @@ package controller import ( "context" "encoding/json" - "fmt" "log/slog" "net/http" + "github.com/AFASystems/presence/internal/pkg/api/response" "github.com/AFASystems/presence/internal/pkg/model" "github.com/segmentio/kafka-go" "gorm.io/gorm" ) -func SettingsListController(db *gorm.DB) http.HandlerFunc { +func SettingsListController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var settings []model.Settings - db.Find(&settings) - res, err := json.Marshal(settings) - if err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).Find(&settings).Error; err != nil { + response.InternalError(w, "failed to list settings", err) return } - - w.Write(res) + response.JSON(w, http.StatusOK, settings) } } -func SettingsUpdateController(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc { +func SettingsUpdateController(db *gorm.DB, writer *kafka.Writer, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var updates map[string]any if err := json.NewDecoder(r.Body).Decode(&updates); err != nil { - http.Error(w, "Invalid JSON", 400) + response.BadRequest(w, "invalid request body") return } - inMsg := fmt.Sprintf("updates: %+v", updates) - slog.Info(inMsg) + slog.Info("updating settings", "updates", updates) - if err := db.Model(&model.Settings{}).Where("id = ?", 1).Updates(updates).Error; err != nil { - msg := fmt.Sprintf("Error in updating settings: %v", err) - slog.Error(msg) - http.Error(w, err.Error(), 500) + if err := db.WithContext(context).Model(&model.Settings{}).Where("id = ?", 1).Updates(updates).Error; err != nil { + response.InternalError(w, "failed to update settings", err) return } eMsg, err := json.Marshal(updates) if err != nil { - http.Error(w, "Error in marshaling settings updates", 400) - msg := fmt.Sprintf("Error in marshaling settings updates: %v", err) - slog.Error(msg) + response.InternalError(w, "failed to marshal settings for publish", err) return } - msg := kafka.Message{ - Value: eMsg, - } - - if err := writer.WriteMessages(ctx, msg); err != nil { + kafkaMsg := kafka.Message{Value: eMsg} + if err := writer.WriteMessages(context, kafkaMsg); err != nil { slog.Error("writing settings to Kafka", "err", err) - http.Error(w, "Failed to publish settings update", 500) + response.InternalError(w, "failed to publish settings update", err) return } - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"status":"Settings updated"}`)) + response.JSON(w, http.StatusOK, map[string]string{"status": "updated"}) } } diff --git a/internal/pkg/controller/trackers_controller.go b/internal/pkg/controller/trackers_controller.go index 76fd717..6de84f2 100644 --- a/internal/pkg/controller/trackers_controller.go +++ b/internal/pkg/controller/trackers_controller.go @@ -7,13 +7,14 @@ import ( "log/slog" "net/http" + "github.com/AFASystems/presence/internal/pkg/api/response" "github.com/AFASystems/presence/internal/pkg/model" "github.com/gorilla/mux" "github.com/segmentio/kafka-go" "gorm.io/gorm" ) -func SendKafkaMessage(writer *kafka.Writer, value *model.ApiUpdate, ctx context.Context) error { +func SendKafkaMessage(writer *kafka.Writer, value *model.ApiUpdate, context context.Context) error { valueStr, err := json.Marshal(&value) if err != nil { msg := fmt.Sprintf("error in encoding: %v", err) @@ -24,7 +25,7 @@ func SendKafkaMessage(writer *kafka.Writer, value *model.ApiUpdate, ctx context. Value: valueStr, } - if err := writer.WriteMessages(ctx, msg); err != nil { + if err := writer.WriteMessages(context, msg); err != nil { msg := fmt.Sprintf("Error in sending kafka message: %v", err) slog.Error(msg) return err @@ -33,14 +34,17 @@ func SendKafkaMessage(writer *kafka.Writer, value *model.ApiUpdate, ctx context. return nil } -func TrackerAdd(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc { +func TrackerAdd(db *gorm.DB, writer *kafka.Writer, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var tracker model.Tracker if err := json.NewDecoder(r.Body).Decode(&tracker); err != nil { - http.Error(w, err.Error(), 400) + response.BadRequest(w, "invalid request body") + return + } + if err := db.WithContext(context).Create(&tracker).Error; err != nil { + response.InternalError(w, "failed to create tracker", err) return } - db.Create(&tracker) apiUpdate := model.ApiUpdate{ Method: "POST", @@ -48,58 +52,66 @@ func TrackerAdd(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.Han MAC: tracker.MAC, } - if err := SendKafkaMessage(writer, &apiUpdate, ctx); err != nil { - msg := "error in sending Kafka POST message" - slog.Error(msg) - http.Error(w, "Error in sending kafka message", 500) + if err := SendKafkaMessage(writer, &apiUpdate, context); err != nil { + slog.Error("error sending Kafka POST message", "err", err) + response.InternalError(w, "failed to publish tracker update", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusCreated, map[string]string{"status": "created"}) } } -func TrackerList(db *gorm.DB) http.HandlerFunc { +func TrackerList(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var list []model.Tracker - db.Find(&list) - json.NewEncoder(w).Encode(list) + if err := db.WithContext(context).Find(&list).Error; err != nil { + response.InternalError(w, "failed to list trackers", err) + return + } + response.JSON(w, http.StatusOK, list) } } -func TrackerUpdate(db *gorm.DB) http.HandlerFunc { +func TrackerUpdate(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var tracker model.Tracker - if err := json.NewDecoder(r.Body).Decode(&tracker); err != nil { - http.Error(w, "Invalid JSON", 400) + response.BadRequest(w, "invalid request body") return } id := tracker.ID - - if err := db.First(&model.Tracker{}, "id = ?", id).Error; err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).First(&model.Tracker{}, "id = ?", id).Error; err != nil { + response.NotFound(w, "tracker not found") return } - if err := db.Save(&tracker).Error; err != nil { - http.Error(w, err.Error(), 500) + if err := db.WithContext(context).Save(&tracker).Error; err != nil { + response.InternalError(w, "failed to update tracker", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "updated"}) } } -func TrackerDelete(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc { +func TrackerDelete(db *gorm.DB, writer *kafka.Writer, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] var tracker model.Tracker - db.Find(&tracker, "id = ?", id) + if err := db.WithContext(context).First(&tracker, "id = ?", id).Error; err != nil { + response.NotFound(w, "tracker not found") + return + } - if res := db.Delete(&model.Tracker{}, "id = ?", id); res.RowsAffected == 0 { - http.Error(w, "no tracker with such ID found", 400) + res := db.WithContext(context).Delete(&model.Tracker{}, "id = ?", id) + if res.RowsAffected == 0 { + response.NotFound(w, "tracker not found") + return + } + if res.Error != nil { + response.InternalError(w, "failed to delete tracker", res.Error) return } @@ -107,17 +119,14 @@ func TrackerDelete(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http. Method: "DELETE", MAC: tracker.MAC, } + slog.Info("sending DELETE tracker message", "id", id) - msg := fmt.Sprintf("Sending DELETE tracker id: %s message", id) - slog.Info(msg) - - if err := SendKafkaMessage(writer, &apiUpdate, ctx); err != nil { - msg := "error in sending Kafka DELETE message" - slog.Error(msg) - http.Error(w, "Error in sending kafka message", 500) + if err := SendKafkaMessage(writer, &apiUpdate, context); err != nil { + slog.Error("error sending Kafka DELETE message", "err", err) + response.InternalError(w, "failed to publish tracker deletion", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } } diff --git a/internal/pkg/controller/trackerzones_controller.go b/internal/pkg/controller/trackerzones_controller.go index f03a213..346a600 100644 --- a/internal/pkg/controller/trackerzones_controller.go +++ b/internal/pkg/controller/trackerzones_controller.go @@ -1,67 +1,79 @@ package controller import ( + "context" "encoding/json" "net/http" + "github.com/AFASystems/presence/internal/pkg/api/response" "github.com/AFASystems/presence/internal/pkg/model" "github.com/gorilla/mux" "gorm.io/gorm" ) -func TrackerZoneAddController(db *gorm.DB) http.HandlerFunc { +func TrackerZoneAddController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var tz model.TrackerZones if err := json.NewDecoder(r.Body).Decode(&tz); err != nil { - http.Error(w, err.Error(), 400) + response.BadRequest(w, "invalid request body") return } - db.Create(&tz) - w.Write([]byte("ok")) + if err := db.WithContext(context).Create(&tz).Error; err != nil { + response.InternalError(w, "failed to create tracker zone", err) + return + } + + response.JSON(w, http.StatusCreated, map[string]string{"status": "created"}) } } -func TrackerZoneListController(db *gorm.DB) http.HandlerFunc { +func TrackerZoneListController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var list []model.TrackerZones - db.Find(&list) - json.NewEncoder(w).Encode(list) + if err := db.WithContext(context).Find(&list).Error; err != nil { + response.InternalError(w, "failed to list tracker zones", err) + return + } + response.JSON(w, http.StatusOK, list) } } -func TrackerZoneUpdateController(db *gorm.DB) http.HandlerFunc { +func TrackerZoneUpdateController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var tz model.TrackerZones - if err := json.NewDecoder(r.Body).Decode(&tz); err != nil { - http.Error(w, "Invalid JSON", 400) + response.BadRequest(w, "invalid request body") return } id := tz.ID - - if err := db.First(&model.TrackerZones{}, "id = ?", id).Error; err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).First(&model.TrackerZones{}, "id = ?", id).Error; err != nil { + response.NotFound(w, "tracker zone not found") return } - if err := db.Save(&tz).Error; err != nil { - http.Error(w, err.Error(), 500) + if err := db.WithContext(context).Save(&tz).Error; err != nil { + response.InternalError(w, "failed to update tracker zone", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "updated"}) } } -func TrackerZoneDeleteController(db *gorm.DB) http.HandlerFunc { +func TrackerZoneDeleteController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - if res := db.Delete(&model.TrackerZones{}, "id = ?", id); res.RowsAffected == 0 { - http.Error(w, "no tracker zone with such ID found", 400) + res := db.WithContext(context).Delete(&model.TrackerZones{}, "id = ?", id) + if res.RowsAffected == 0 { + response.NotFound(w, "tracker zone not found") + return + } + if res.Error != nil { + response.InternalError(w, "failed to delete tracker zone", res.Error) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } } diff --git a/internal/pkg/controller/tracks_controller.go b/internal/pkg/controller/tracks_controller.go index 0664540..dd69c8e 100644 --- a/internal/pkg/controller/tracks_controller.go +++ b/internal/pkg/controller/tracks_controller.go @@ -1,17 +1,18 @@ package controller import ( - "encoding/json" + "context" "net/http" "strconv" "time" + "github.com/AFASystems/presence/internal/pkg/api/response" "github.com/AFASystems/presence/internal/pkg/model" "github.com/gorilla/mux" "gorm.io/gorm" ) -func TracksListController(db *gorm.DB) http.HandlerFunc { +func TracksListController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] var tracks []model.Tracks @@ -34,13 +35,10 @@ func TracksListController(db *gorm.DB) http.HandlerFunc { from := parseTime("from", time.Now().AddDate(0, 0, -1)) to := parseTime("to", time.Now()) - db.Where("uuid = ? AND timestamp BETWEEN ? AND ?", id, from, to).Order("timestamp DESC").Limit(limit).Find(&tracks) - res, err := json.Marshal(tracks) - if err != nil { - http.Error(w, err.Error(), 400) + if err := db.WithContext(context).Where("uuid = ? AND timestamp BETWEEN ? AND ?", id, from, to).Order("timestamp DESC").Limit(limit).Find(&tracks).Error; err != nil { + response.InternalError(w, "failed to list tracks", err) return } - - w.Write(res) + response.JSON(w, http.StatusOK, tracks) } } diff --git a/internal/pkg/controller/zone_controller.go b/internal/pkg/controller/zone_controller.go index 33c49c1..f986bed 100644 --- a/internal/pkg/controller/zone_controller.go +++ b/internal/pkg/controller/zone_controller.go @@ -1,67 +1,79 @@ package controller import ( + "context" "encoding/json" "net/http" + "github.com/AFASystems/presence/internal/pkg/api/response" "github.com/AFASystems/presence/internal/pkg/model" "github.com/gorilla/mux" "gorm.io/gorm" ) -func ZoneAddController(db *gorm.DB) http.HandlerFunc { +func ZoneAddController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var zone model.Zone if err := json.NewDecoder(r.Body).Decode(&zone); err != nil { - http.Error(w, err.Error(), 400) + response.BadRequest(w, "invalid request body") return } - db.Create(&zone) - w.Write([]byte("ok")) + if err := db.WithContext(context).Create(&zone).Error; err != nil { + response.InternalError(w, "failed to create zone", err) + return + } + + response.JSON(w, http.StatusCreated, map[string]string{"status": "created"}) } } -func ZoneListController(db *gorm.DB) http.HandlerFunc { +func ZoneListController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var zones []model.Zone - db.Find(&zones) - json.NewEncoder(w).Encode(zones) // Groups will appear as ["a", "b"] in JSON + if err := db.WithContext(context).Find(&zones).Error; err != nil { + response.InternalError(w, "failed to list zones", err) + return + } + response.JSON(w, http.StatusOK, zones) } } -func ZoneUpdateController(db *gorm.DB) http.HandlerFunc { +func ZoneUpdateController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var zone model.Zone - if err := json.NewDecoder(r.Body).Decode(&zone); err != nil { - http.Error(w, err.Error(), 400) + response.BadRequest(w, "invalid request body") return } id := zone.ID - - if err := db.First(&model.Zone{}, "id = ?", id); err != nil { - http.Error(w, "zone with this ID does not yet exist", 500) + if err := db.WithContext(context).First(&model.Zone{}, "id = ?", id).Error; err != nil { + response.NotFound(w, "zone not found") return } - if err := db.Save(&zone).Error; err != nil { - http.Error(w, err.Error(), 500) + if err := db.WithContext(context).Save(&zone).Error; err != nil { + response.InternalError(w, "failed to update zone", err) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "updated"}) } } -func ZoneDeleteController(db *gorm.DB) http.HandlerFunc { +func ZoneDeleteController(db *gorm.DB, context context.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - if res := db.Delete(&model.Zone{}, "id = ?", id); res.RowsAffected == 0 { - http.Error(w, "no zone with such ID found", 400) + res := db.WithContext(context).Delete(&model.Zone{}, "id = ?", id) + if res.RowsAffected == 0 { + response.NotFound(w, "zone not found") + return + } + if res.Error != nil { + response.InternalError(w, "failed to delete zone", res.Error) return } - w.Write([]byte("ok")) + response.JSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } } diff --git a/internal/pkg/database/database.go b/internal/pkg/database/database.go index 264b59b..bab8b76 100644 --- a/internal/pkg/database/database.go +++ b/internal/pkg/database/database.go @@ -25,7 +25,7 @@ func Connect(cfg *config.Config) (*gorm.DB, error) { return nil, err } - if err := db.AutoMigrate(&model.Gateway{}, model.Zone{}, model.TrackerZones{}, model.Tracker{}, model.Config{}, model.Settings{}, model.Tracks{}); err != nil { + if err := db.AutoMigrate(&model.Gateway{}, model.Zone{}, model.TrackerZones{}, model.Tracker{}, model.Config{}, model.Settings{}, model.Tracks{}, &model.Alert{}); err != nil { return nil, err } diff --git a/internal/pkg/location/inference.go b/internal/pkg/location/inference.go index fabcb87..8f0dc24 100644 --- a/internal/pkg/location/inference.go +++ b/internal/pkg/location/inference.go @@ -3,6 +3,7 @@ package location import ( "context" "crypto/tls" + "fmt" "net/http" "github.com/AFASystems/presence/internal/pkg/apiclient" @@ -18,24 +19,29 @@ type Inferencer interface { // DefaultInferencer uses apiclient to get token and call the inference API. type DefaultInferencer struct { Client *http.Client + Token string } // NewDefaultInferencer creates an inferencer with optional TLS skip verify (e.g. from config.TLSInsecureSkipVerify). func NewDefaultInferencer(skipTLSVerify bool) *DefaultInferencer { tr := &http.Transport{} - if skipTLSVerify { - tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - } + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} return &DefaultInferencer{ Client: &http.Client{Transport: tr}, + Token: "", } } // Infer gets a token and calls the inference API. func (d *DefaultInferencer) Infer(ctx context.Context, cfg *config.Config) (model.PositionResponse, error) { - token, err := apiclient.GetToken(ctx, cfg, d.Client) - if err != nil { - return model.PositionResponse{}, err + if d.Token == "" { + fmt.Printf("getting token\n") + token, err := apiclient.GetToken(ctx, cfg, d.Client) + if err != nil { + return model.PositionResponse{}, err + } + d.Token = token } - return apiclient.InferPosition(token, d.Client, cfg) + + return apiclient.InferPosition(d.Token, d.Client, cfg) } diff --git a/internal/pkg/model/alerts.go b/internal/pkg/model/alerts.go new file mode 100644 index 0000000..f6e07bb --- /dev/null +++ b/internal/pkg/model/alerts.go @@ -0,0 +1,8 @@ +package model + +type Alert struct { + ID string `json:"id" gorm:"primaryKey"` + TrackerID string `json:"tracker_id"` + Type string `json:"type"` + Value string `json:"value"` +} diff --git a/internal/pkg/model/trackers.go b/internal/pkg/model/trackers.go index 096a272..f629c73 100644 --- a/internal/pkg/model/trackers.go +++ b/internal/pkg/model/trackers.go @@ -6,14 +6,13 @@ type Tracker struct { MAC string `json:"mac"` Status string `json:"status"` Model string `json:"model"` + IP string `json:"ip"` Position string `json:"position"` - Notes string `json:"notes"` X float32 `json:"x"` Y float32 `json:"y"` + Notes string `json:"notes"` Floor string `json:"floor"` Building string `json:"building"` - Location string `json:"location"` - Distance float64 `json:"distance"` Battery uint32 `json:"battery,string"` BatteryThreshold uint32 `json:"batteryThreshold"` Temperature uint16 `json:"temperature,string"` diff --git a/internal/pkg/model/types.go b/internal/pkg/model/types.go index 4b61144..780bc6a 100644 --- a/internal/pkg/model/types.go +++ b/internal/pkg/model/types.go @@ -44,6 +44,10 @@ type HTTPLocation struct { Location string `json:"location"` LastSeen int64 `json:"last_seen"` RSSI int64 `json:"rssi"` + X float32 `json:"x"` + Y float32 `json:"y"` + Z float32 `json:"z"` + MAC string `json:"mac"` } // Beacon holds all relevant information about a tracked beacon device. @@ -134,9 +138,3 @@ type ApiUpdate struct { ID string MAC string } - -type Alert struct { - ID string `json:"id"` // tracker id - Type string `json:"type"` // type of alert - Value string `json:"value"` // possible value -} diff --git a/internal/pkg/service/alert_service.go b/internal/pkg/service/alert_service.go new file mode 100644 index 0000000..4d4ebac --- /dev/null +++ b/internal/pkg/service/alert_service.go @@ -0,0 +1,39 @@ +package service + +import ( + "context" + + "github.com/AFASystems/presence/internal/pkg/model" + "gorm.io/gorm" +) + +func InsertAlert(alert model.Alert, db *gorm.DB, ctx context.Context) error { + if err := db.WithContext(ctx).Create(&alert).Error; err != nil { + return err + } + return nil +} + +func DeleteAlertByTrackerID(trackerID string, db *gorm.DB, ctx context.Context) error { + if err := db.WithContext(ctx).Where("id = ?", trackerID).Delete(&model.Alert{}).Error; err != nil { + return err + } + return nil +} + +func GetAllAlerts(db *gorm.DB, ctx context.Context) ([]model.Alert, error) { + var alerts []model.Alert + if err := db.WithContext(ctx).Find(&alerts).Error; err != nil { + return []model.Alert{}, err + } + + return alerts, nil +} + +func GetAlertById(id string, db *gorm.DB, ctx context.Context) (model.Alert, error) { + var alert model.Alert + if err := db.WithContext(ctx).First(&alert, id).Error; err != nil { + return alert, err + } + return alert, nil +} diff --git a/internal/pkg/service/beacon_service.go b/internal/pkg/service/beacon_service.go index 4ee23ac..ce495db 100644 --- a/internal/pkg/service/beacon_service.go +++ b/internal/pkg/service/beacon_service.go @@ -3,6 +3,7 @@ package service import ( "context" "encoding/json" + "errors" "fmt" "log/slog" "slices" @@ -10,6 +11,7 @@ import ( "time" "github.com/AFASystems/presence/internal/pkg/model" + "github.com/google/uuid" "github.com/segmentio/kafka-go" "gorm.io/gorm" ) @@ -19,45 +21,131 @@ type KafkaWriter interface { WriteMessages(ctx context.Context, msgs ...kafka.Message) error } +func findTracker(msg model.HTTPLocation, db *gorm.DB) (model.Tracker, error) { + var tracker model.Tracker + if msg.MAC != "" { + if err := db.Where("mac = ?", msg.MAC).Find(&tracker).Error; err != nil { + return model.Tracker{}, err + } + + return tracker, nil + } + + if msg.ID != "" { + if err := db.Where("id = ?", msg.ID).Find(&tracker).Error; err != nil { + return model.Tracker{}, err + } + + return tracker, nil + } + + return model.Tracker{}, errors.New("both ID and MAC are not provided") +} + +func findZones(trackerID string, db *gorm.DB) ([]string, error) { + var zones []model.TrackerZones + if err := db.Select("zoneList").Where("tracker = ?", trackerID).Find(&zones).Error; err != nil { + return nil, err + } + + var allowedZones []string + for _, z := range zones { + allowedZones = append(allowedZones, z.ZoneList...) + } + + return allowedZones, nil +} + func LocationToBeaconService(msg model.HTTPLocation, db *gorm.DB, writer KafkaWriter, ctx context.Context) { - if msg.ID == "" { - msg := "empty ID" + tracker, err := findTracker(msg, db) + if err != nil { + msg := fmt.Sprintf("Error in finding tracker: %v", err) slog.Error(msg) return } - var zones []model.TrackerZones - if err := db.Select("zoneList").Where("tracker = ?", msg.ID).Find(&zones).Error; err != nil { - msg := fmt.Sprintf("Error in selecting zones: %v", err) + allowedZones, err := findZones(tracker.ID, db) + if err != nil { + msg := fmt.Sprintf("Error in finding zones: %v", err) slog.Error(msg) return } - var tracker model.Tracker - if err := db.Where("id = ?", msg.ID).Find(&tracker).Error; err != nil { - msg := fmt.Sprintf("Error in selecting tracker: %v", err) + var gw model.Gateway + mac := formatMac(msg.Location) + if err := db.Select("*").Where("mac = ?", mac).First(&gw).Error; err != nil { + msg := fmt.Sprintf("Gateway not found for MAC: %s", mac) slog.Error(msg) return } - var allowedZones []string - for _, z := range zones { - allowedZones = append(allowedZones, z.ZoneList...) + if err := db.Create(&model.Tracks{UUID: msg.ID, Timestamp: time.Now(), Gateway: gw.ID, GatewayMac: gw.MAC, Tracker: msg.ID, Floor: gw.Floor, Building: gw.Building, TrackerMac: tracker.MAC, Signal: msg.RSSI}).Error; err != nil { + msg := fmt.Sprintf("Error in saving distance for beacon: %v", err) + slog.Error(msg) + return + } + + err = db.Where("id = ?", msg.ID).Updates(model.Tracker{Position: gw.ID, X: gw.X, Y: gw.Y}).Error + if err != nil { + msg := fmt.Sprintf("Error in updating tracker: %v", err) + slog.Error(msg) + return + } + + sendAlert(gw.ID, msg.ID, writer, ctx, allowedZones, db) +} + +func LocationToBeaconServiceAI(msg model.HTTPLocation, db *gorm.DB, writer KafkaWriter, ctx context.Context) { + tracker, err := findTracker(msg, db) + if err != nil { + msg := fmt.Sprintf("Error in finding tracker: %v", err) + slog.Error(msg) + return + } + + allowedZones, err := findZones(tracker.ID, db) + if err != nil { + msg := fmt.Sprintf("Error in finding zones: %v", err) + slog.Error(msg) + return } var gw model.Gateway - mac := formatMac(msg.Location) - if err := db.Select("id").Where("mac = ?", mac).First(&gw).Error; err != nil { - msg := fmt.Sprintf("Gateway not found for MAC: %s", mac) + if err := db.Order(fmt.Sprintf("POW(x - %f, 2) + POW(y - %f, 2)", msg.X, msg.Y)).First(&gw).Error; err != nil { + msg := fmt.Sprintf("Error in finding gateway: %v", err) + slog.Error(msg) + return + } + + if err := db.Create(&model.Tracks{UUID: tracker.ID, Timestamp: time.Now(), Gateway: gw.ID, GatewayMac: gw.MAC, Tracker: tracker.ID, Floor: gw.Floor, Building: gw.Building, TrackerMac: tracker.MAC}).Error; err != nil { + msg := fmt.Sprintf("Error in saving distance for beacon: %v", err) + slog.Error(msg) + return + } + + err = db.Where("id = ?", tracker.ID).Updates(model.Tracker{Position: gw.ID, X: msg.X, Y: msg.Y}).Error + if err != nil { + msg := fmt.Sprintf("Error in updating tracker: %v", err) slog.Error(msg) return - } + } + + sendAlert(gw.ID, tracker.ID, writer, ctx, allowedZones, db) +} - if len(allowedZones) != 0 && !slices.Contains(allowedZones, gw.ID) { +func sendAlert(gwId, trackerId string, writer KafkaWriter, ctx context.Context, allowedZones []string, db *gorm.DB) { + if len(allowedZones) != 0 && !slices.Contains(allowedZones, gwId) { alert := model.Alert{ - ID: msg.ID, - Type: "Restricted zone", - Value: gw.ID, + ID: uuid.New().String(), + TrackerID: trackerId, + Type: "Restricted zone", + Value: gwId, + } + + if err := InsertAlert(alert, db, ctx); err != nil { + msg := fmt.Sprintf("Error in inserting alert: %v", err) + slog.Error(msg) + return } eMsg, err := json.Marshal(alert) @@ -70,22 +158,8 @@ func LocationToBeaconService(msg model.HTTPLocation, db *gorm.DB, writer KafkaWr Value: eMsg, } writer.WriteMessages(ctx, msg) - return } } - - // status, subject, subject name? - if err := db.Create(&model.Tracks{UUID: msg.ID, Timestamp: time.Now(), Gateway: gw.ID, GatewayMac: gw.MAC, Tracker: msg.ID, Floor: gw.Floor, Building: gw.Building, TrackerMac: tracker.MAC, Signal: msg.RSSI}).Error; err != nil { - msg := fmt.Sprintf("Error in saving distance for beacon: %v", err) - slog.Error(msg) - return - } - - if err := db.Updates(&model.Tracker{ID: msg.ID, Location: gw.ID, Distance: msg.Distance, X: gw.X, Y: gw.Y}).Error; err != nil { - msg := fmt.Sprintf("Error in saving distance for beacon: %v", err) - slog.Error(msg) - return - } } func formatMac(MAC string) string { diff --git a/scripts/build/build.sh b/scripts/build/build.sh new file mode 100755 index 0000000..48e9a5d --- /dev/null +++ b/scripts/build/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Build the server +docker build -t presense:server_v1.0.0 -f ../../build/package/Dockerfile.server ../../ + +# Build the location +docker build -t presense:location_v1.0.0 -f ../../build/package/Dockerfile.location ../../ + +# Build the decoder +docker build -t presense:decoder_v1.0.0 -f ../../build/package/Dockerfile.decoder ../../ + +# Build the bridge +docker build -t presense:bridge_v1.0.0 -f ../../build/package/Dockerfile.bridge ../../ + +docker image ls \ No newline at end of file