Procházet zdrojové kódy

chore: clean code

master
Blaz Smehov před 2 týdny
rodič
revize
8a47bafcfa
29 změnil soubory, kde provedl 301 přidání a 3351 odebrání
  1. +288
    -0
      CODE_REVIEW_REPORT.md
  2. +0
    -1
      Makefile
  3. +0
    -421
      README.md
  4. +0
    -415
      SCORE.md
  5. +0
    -8
      api/README.md
  6. +0
    -50
      api/openapi.yml
  7. +0
    -3
      assets/README.md
  8. +4
    -0
      cmd/location/main.go
  9. +0
    -9
      examples/README.md
  10. +0
    -3
      githooks/README.md
  11. +7
    -14
      go.mod
  12. +1
    -28
      go.sum
  13. +0
    -3
      init/README.md
  14. +0
    -106
      internal/pkg/bridge/mqtthandler/mqtthandler.go
  15. +0
    -29
      internal/pkg/common/appcontext/context.go
  16. +0
    -11
      internal/pkg/common/utils/distance.go
  17. +1
    -1
      internal/pkg/controller/trackers_controller.go
  18. +0
    -22
      internal/pkg/model/type_methods.go
  19. +0
    -44
      internal/pkg/redis/redis.go
  20. +0
    -17
      internal/pkg/service/beacon_service.go
  21. +0
    -1467
      logging.md
  22. +0
    -676
      refactor.md
  23. +0
    -3
      third_party/README.md
  24. +0
    -9
      tools/README.md
  25. +0
    -3
      web/README.md
  26. +0
    -0
      web/app/.keep
  27. +0
    -0
      web/static/.keep
  28. +0
    -0
      web/template/.keep
  29. +0
    -8
      website/README.md

+ 288
- 0
CODE_REVIEW_REPORT.md Zobrazit soubor

@@ -0,0 +1,288 @@
# Code Review Report: AFASystems Presence Services

**Review Date:** February 11, 2025
**Scope:** Four services (bridge, server, location, decoder) and their internal packages
**Reviewer:** Automated Code Review

---

## Executive Summary

This report reviews the custom packages used across the four main services of the presence detection system. The codebase demonstrates a coherent architecture with clear separation of concerns, but several reliability issues, unused code paths, and refactoring opportunities were identified.

**Overall Rating: 6.5/10**

---

## 1. Package Inventory by Service

### Bridge (`cmd/bridge/main.go`)

| Package | Purpose |
| -------------------------------- | -------------------------------------------------- |
| `internal/pkg/common/appcontext` | Shared application state (beacon lookup, settings) |
| `internal/pkg/config` | Environment-based configuration |
| `internal/pkg/kafkaclient` | Kafka consumer/producer management |
| `internal/pkg/logger` | Structured logging setup |
| `internal/pkg/model` | Data structures |

### Server (`cmd/server/main.go`)

| Package | Purpose |
| -------------------------------- | --------------------------------------------- |
| `internal/pkg/apiclient` | External API authentication and data fetching |
| `internal/pkg/common/appcontext` | Shared application state |
| `internal/pkg/config` | Configuration |
| `internal/pkg/controller` | HTTP handlers for REST API |
| `internal/pkg/database` | PostgreSQL connection (GORM) |
| `internal/pkg/kafkaclient` | Kafka management |
| `internal/pkg/logger` | Logging |
| `internal/pkg/model` | Data structures |
| `internal/pkg/service` | Business logic (location, parser) |

### Location (`cmd/location/main.go`)

| Package | Purpose |
| -------------------------------- | ---------------------- |
| `internal/pkg/common/appcontext` | Beacon state, settings |
| `internal/pkg/common/utils` | Distance calculation |
| `internal/pkg/config` | Configuration |
| `internal/pkg/kafkaclient` | Kafka management |
| `internal/pkg/logger` | Logging |
| `internal/pkg/model` | Data structures |

### Decoder (`cmd/decoder/main.go`)

| Package | Purpose |
| -------------------------------- | ---------------------------------- |
| `internal/pkg/common/appcontext` | Beacon events state |
| `internal/pkg/common/utils` | AD structure parsing, flag removal |
| `internal/pkg/config` | Configuration |
| `internal/pkg/kafkaclient` | Kafka management |
| `internal/pkg/logger` | Logging |
| `internal/pkg/model` | Data structures, parser registry |

---

## 2. Critical Issues (Must Fix)

### 2.1 Tracker Delete Method Case Mismatch (Bug)

Resolved

### 2.2 Potential Panic in Location Algorithm

**Location:** `cmd/location/main.go:99-101`

Resolved

### 2.3 Hardcoded Config Path

**Location:** `cmd/server/main.go:60`

```go
configFile, err := os.Open("/app/cmd/server/config.json")
```

This path is Docker-specific and fails in local development or other deployment environments.

**Fix:** Use configurable path (e.g., `CONFIG_PATH` env var) or relative path based on executable location.

---

## 3. Security Concerns

### 3.1 Hardcoded Credentials

**Locations:**

- `internal/pkg/config/config.go`: Default values include `ClientSecret`, `HTTPPassword` with production-like strings
- `internal/pkg/apiclient/auth.go`: GetToken() hardcodes credentials in formData (lines 21-24) instead of using `cfg.HTTPClientID`, `cfg.ClientSecret`, etc.
- Config struct has `HTTPClientID`, `ClientSecret`, `HTTPUsername`, etc., but `auth.go` ignores them

**Recommendation:** Wire config values into auth; never commit production credentials.

### 3.2 TLS Verification Disabled

**Location:** `internal/pkg/apiclient/updatedb.go:21-22`

```go
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
```

**Recommendation:** Use proper certificates or make this configurable for dev only.

---

## 4. Unused Code & Directories

### 4.1 Orphaned Packages (Not Imported)

Resolved

### 4.2 Unused Functions / Methods

Resolved

### 4.3 Dead Code in bridge/mqtthandler

**Location:** `internal/pkg/bridge/mqtthandler/mqtthandler.go:75-83`

Resolved

### 4.4 Unnecessary Compile-Time Assertion

**Location:** `cmd/server/main.go:31`

```go
var _ io.Writer = (*os.File)(nil)
```

Redundant; `*os.File` implements `io.Writer`. Safe to remove.

### 4.5 Unused go.mod Dependencies

| Package | Notes |
| ------------------------------ | ------------------------------- |
| `github.com/boltdb/bolt` | Not imported in any source file |
| `github.com/yosssi/gmq` | Not imported |
| `github.com/gorilla/websocket` | Not imported |

**Recommendation:** Run `go mod tidy` after removing dead imports, or explicitly remove if kept for future use.

---

## 5. Reliability & Error Handling

### 5.1 Kafka Consumer Error Handling

**Location:** `internal/pkg/kafkaclient/consumer.go:20-23`

On `ReadMessage` or `Unmarshal` error, the consumer logs and continues. For `context.Canceled` or partition errors, this may cause tight loops. Consider backoff or bounded retries.

### 5.2 KafkaManager GetReader/GetWriter Lock Usage

**Location:** `internal/pkg/kafkaclient/manager.go:101-111`

`GetReader` and `GetWriter` hold the lock for the entire call including return. If the returned pointer is used after the lock is released, that's fine, but the pattern holds the lock longer than necessary. Prefer:

```go
func (m *KafkaManager) GetReader(topic string) *kafka.Reader {
m.kafkaReadersMap.KafkaReadersLock.RLock()
defer m.kafkaReadersMap.KafkaReadersLock.RUnlock()
return m.kafkaReadersMap.KafkaReaders[topic]
}
```

Use `RLock` for read-only access.

### 5.3 Logger File Handle Leak

**Location:** `internal/pkg/logger/logger.go`

The opened file `f` is never closed. For long-running processes this is usually acceptable (log files stay open), but worth documenting. If multiple loggers are created, each holds a file descriptor.

### 5.4 Silent JSON Unmarshal

**Location:** `cmd/server/main.go:68`

```go
json.Unmarshal(b, &configs)
```

Error is ignored. Invalid JSON would leave `configs` empty without feedback.

---

## 6. Code Quality & Maintainability

### 6.1 Inconsistent Logging

- Mix of `log.Printf`, `fmt.Println`, `fmt.Printf`, and `slog.Info/Error`
- Italian message: "Messaggio CSV non valido" in bridge
- Typo: "Beggining" → "Beginning" (bridge, location, decoder, server)

### 6.2 Magic Numbers

- Channel sizes: 200, 500, 2000 without named constants
- RSSI weights in location: `seenW := 1.5`, `rssiW := 0.75`
- Ticker intervals: 1s, 2s without configuration

### 6.3 Duplication

- Bridge defines `mqtthandler` inline while `internal/pkg/bridge/mqtthandler` exists with similar logic
- Both use `appcontext.BeaconExists` for lookup; bridge version also sets `adv.ID` from lookup

### 6.4 Parser ID Inconsistency

**Decoder** expects `msg.ID` values: `"add"`, `"delete"`, `"update"`.
**ParserDeleteController** sends `ID: "delete"` ✓
**ParserAddController** sends `ID: "add"` ✓
**ParserUpdateController** sends `ID: "update"` ✓

Decoder’s update case re-registers; add and update are effectively the same.

---

## 7. AppContext Thread Safety

`AppState` mixes safe and unsafe access:

- `beaconsLookup` (map) has no mutex; `AddBeaconToLookup`, `RemoveBeaconFromLookup`, `CleanLookup`, `BeaconExists` are not thread-safe
- Bridge goroutines (Kafka consumers + event loop) and MQTT handler may access it concurrently

**Recommendation:** Protect `beaconsLookup` with `sync.RWMutex` or use `sync.Map`.

---

## 8. Refactoring Suggestions

1. **Unify config loading:** Support JSON config file path via env; keep env overrides for sensitivity.
2. **Extract constants:** Kafka topic names, channel sizes, ticker intervals.
3. **Consolidate MQTT handling:** Use `internal/pkg/bridge/mqtthandler` and fix it, or remove the package and keep logic in bridge.
4. **API client:** Use config for URLs and credentials; add timeouts to HTTP client.
5. **Controllers:** Add request validation, consistent error responses, and structured error types.
6. **Service layer:** `formatMac` in `beacon_service.go` could move to `internal/pkg/common/utils` for reuse and testing.

---

## 9. Directory Structure Notes

| Directory | Status |
| ------------------------------------------ | ----------------------------------------------------- |
| `internal/app/_your_app_/` | Placeholder with `.keep`; safe to remove or repurpose |
| `internal/pkg/model/.keep` | Placeholder; low impact |
| `web/app/`, `web/static/`, `web/template/` | Empty except `.keep`; clarify if planned |
| `build/package/` | Contains Dockerfiles; structure is reasonable |

---

## 10. Summary of Recommendations

| Priority | Action |
| -------- | ------------------------------------------------------------------ |
| **P0** | Fix TrackerDelete `"Delete"` → `"DELETE"` |
| **P0** | Guard empty `BeaconMetrics` in location |
| **P1** | Make config.json path configurable |
| **P1** | Fix `beaconsLookup` concurrency in AppState |
| **P2** | Remove or integrate `internal/pkg/redis` and `internal/pkg/bridge` |
| **P2** | Remove unused functions (ValidateRSSI, EventToBeaconService, etc.) |
| **P2** | Replace hardcoded credentials in apiclient with config |
| **P3** | Unify logging (slog), fix typos, extract constants |
| **P3** | Run `go mod tidy` and drop unused dependencies |

---

## 11. Rating Breakdown

| Category | Score | Notes |
| ---------------- | ----- | ----------------------------------------------------------------------- |
| Architecture | 7/10 | Clear service boundaries; some shared-state issues |
| Reliability | 5/10 | Critical bugs (case mismatch, panic risk); error handling could improve |
| Security | 4/10 | Hardcoded credentials; disabled TLS verification |
| Maintainability | 6/10 | Duplication, magic numbers, inconsistent logging |
| Code Cleanliness | 5/10 | Unused code, dead packages, redundant assertions |

**Overall: 6.5/10**

The system has a solid foundation and sensible separation of concerns. Addressing the critical bugs, security issues, and removing dead code would materially improve reliability and maintainability.

+ 0
- 1
Makefile Zobrazit soubor

@@ -1 +0,0 @@
# note: call scripts from /scripts

+ 0
- 421
README.md Zobrazit soubor

@@ -1,421 +0,0 @@
# AFA Systems Presence Detection

A comprehensive **Bluetooth Low Energy (BLE) presence detection system** that tracks beacon devices in real-time, calculates locations based on signal strength, and integrates with popular IoT platforms like Home Assistant and Node-RED.

## 🏗️ System Architecture

The system follows a **microservices architecture** with Apache Kafka as the central message bus:

```
┌─────────────┐ MQTT ┌─────────────┐ Kafka ┌─────────────┐
│ MQTT │ ────────► │ Bridge │ ────────► │ Decoder │
│ Gateway │ │ Service │ │ Service │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐ WebSocket ┌─────────────┐ Kafka ┌─────────────┐
│ Web UI │ ◄────────── │ Server │ ◄───────── │ Location │
│ Dashboard │ │ Service │ │ Algorithm │
└─────────────┘ └─────────────┘ └─────────────┘
```

### Core Components

#### 🔌 **Bridge Service** (`cmd/bridge/`)
- **Purpose**: MQTT to Kafka message gateway
- **Function**: Subscribes to `publish_out/#` MQTT topics and forwards messages to Kafka `rawbeacons` topic
- **Features**: Configurable MQTT connection, automatic reconnection, error handling

#### 🔍 **Decoder Service** (`cmd/decoder/`)
- **Purpose**: Processes raw BLE beacon advertisements
- **Supported Formats**: Ingics, Eddystone, Minew B7
- **Functions**:
- Battery level monitoring
- Fall detection events
- Button press detection
- Device telemetry extraction
- **Output**: Processed events to `alertbeacons` Kafka topic

#### 📍 **Location Algorithm** (`cmd/location/`)
- **Purpose**: Calculates device locations based on RSSI and proximity
- **Features**:
- Weighted location calculation using RSSI values
- Confidence scoring system
- Location change notifications
- Configurable thresholds and parameters
- **Output**: Location events to `locevents` Kafka topic

#### 🌐 **Server Service** (`cmd/server/`)
- **Purpose**: HTTP API and WebSocket server
- **Features**:
- RESTful API for beacon management (CRUD operations)
- Real-time WebSocket communication
- Settings management interface
- CORS-enabled web interface
- Home Assistant integration endpoints

#### 📊 **Supporting Services**
- **Kafka 3.9.0**: Central message bus with automatic topic creation
- **Kafdrop**: Web-based Kafka monitoring and management UI
- **Node-RED**: IoT workflow automation with pre-configured flows
- **Redis**: Real-time data caching and WebSocket session management
- **BoltDB**: Embedded database for persistent storage

## 🚀 Quick Start

### Prerequisites

- Docker and Docker Compose
- Go 1.24+ (for local development)
- MQTT broker (compatible with BLE gateways)

### Installation

1. **Clone the repository**:
```bash
git clone https://github.com/AFASystems/presence.git
cd presence
```

2. **Start the system**:
```bash
cd build
docker-compose up -d
```

3. **Verify services**:
- **Web Interface**: http://localhost:8080
- **Kafdrop (Kafka UI)**: http://localhost:9000
- **Node-RED**: http://localhost:1880

### Configuration

Set the following environment variables:

```bash
# Web Server
HTTP_HOST_PATH=":8080"
HTTP_WS_HOST_PATH=":8081"

# MQTT Configuration
MQTT_HOST="tcp://mqtt-broker:1883"
MQTT_USERNAME="your_username"
MQTT_PASSWORD="your_password"

# Kafka Configuration
KAFKA_URL="kafka:29092"

# Database
DB_PATH="./volumes/presence.db"
```

## 📡 Supported Beacon Types

### Ingics Beacons
- Battery level monitoring
- Event detection (fall detection, button presses)
- Signal strength tracking

### Eddystone Beacons
- Eddystone-UID protocol support
- Battery telemetry (Eddystone-TLM)
- Proximity detection

### Minew B7 Beacons
- Vendor-specific format decoding
- Button counter tracking
- Battery status monitoring
- Multiple button modes

## 🔧 Configuration Options

### System Settings (`SettingsVal`)

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `location_confidence` | int64 | 80 | Minimum confidence level for location changes |
| `last_seen_threshold` | int64 | 300 | Time (seconds) before beacon considered offline |
| `beacon_metrics_size` | int | 10 | Number of RSSI measurements to keep for averaging |
| `ha_send_interval` | int64 | 60 | Home Assistant update interval (seconds) |
| `ha_send_changes_only` | bool | true | Send updates only on location changes |
| `rssi_min_threshold` | int64 | -90 | Minimum RSSI value for beacon detection |
| `enforce_rssi_threshold` | bool | true | Filter out weak signals |

### API Endpoints

#### Beacon Management
- `GET /api/beacons` - List all registered beacons
- `POST /api/beacons` - Register a new beacon
- `PUT /api/beacons/{id}` - Update beacon information
- `DELETE /api/beacons/{id}` - Remove a beacon

#### Settings
- `GET /api/settings` - Get current system settings
- `POST /api/settings` - Update system settings

#### WebSocket
- `/ws/broadcast` - Real-time beacon updates and notifications

## 🏠 Home Assistant Integration

### MQTT Auto-Discovery

The system automatically generates MQTT messages for Home Assistant:

```yaml
# Example device tracker configuration
device_tracker:
- platform: mqtt
devices:
beacon_001: "Presence/Beacons/beacon_001"
```

### Battery Monitoring

```yaml
# Battery level sensor
sensor:
- platform: mqtt
name: "Beacon Battery"
state_topic: "Presence/Beacons/beacon_001/battery"
unit_of_measurement: "%"
device_class: battery
```

### Button Detection

```yaml
# Button press sensor
binary_sensor:
- platform: mqtt
name: "Beacon Button"
state_topic: "Presence/Beacons/beacon_001/button"
device_class: "button"
```

## 📊 Data Models

### Core Entities

#### Beacon
```go
type Beacon struct {
Name string `json:"name"`
ID string `json:"beacon_id"`
BeaconType string `json:"beacon_type"`
BeaconLocation string `json:"beacon_location"`
LastSeen int64 `json:"last_seen"`
Distance float64 `json:"distance"`
LocationConfidence int64 `json:"location_confidence"`
HSButtonCounter int64 `json:"hs_button_counter"`
HSBattery int64 `json:"hs_button_battery"`
// ... additional fields
}
```

#### BeaconAdvertisement
```go
type BeaconAdvertisement struct {
Hostname string `json:"hostname"`
MAC string `json:"mac"`
RSSI int64 `json:"rssi"`
Data string `json:"data"`
BeaconType string `json:"beacon_type"`
UUID string `json:"uuid"`
Major string `json:"major"`
Minor string `json:"minor"`
// ... additional fields
}
```

#### LocationChange
```go
type LocationChange struct {
Method string `json:"method"`
BeaconRef Beacon `json:"beacon_info"`
Name string `json:"name"`
PreviousLocation string `json:"previous_location"`
NewLocation string `json:"new_location"`
Timestamp int64 `json:"timestamp"`
}
```

## 🐳 Docker Services

### Available Services

| Service | Image | Ports | Description |
|---------|-------|-------|-------------|
| kafka | apache/kafka:3.9.0 | 9092, 9093 | Apache Kafka message broker |
| kafdrop | obsidiandynamics/kafdrop | 9000 | Kafka monitoring UI |
| presence-bridge | local/build | - | MQTT to Kafka bridge |
| presence-decoder | local/build | - | BLE beacon decoder |
| presence-location | local/build | - | Location calculation service |
| presence-server | local/build | 8080, 8081 | HTTP API and WebSocket server |

### Volumes

- `./volumes/presence.db` - BoltDB database file
- `./volumes/node-red/` - Node-RED configuration and flows
- `./volumes/kafka-data/` - Kafka persistent data

## 🔧 Development

### Local Development Setup

1. **Install dependencies**:
```bash
go mod download
```

2. **Run individual services**:
```bash
# Run bridge service
go run cmd/bridge/main.go

# Run decoder service
go run cmd/decoder/main.go

# Run location algorithm
go run cmd/location/main.go

# Run API server
go run cmd/server/main.go
```

3. **Run tests**:
```bash
go test ./...
```

### Building Docker Images

```bash
# Build all services
docker build -t presence-system .

# Build individual service
docker build -f build/package/Dockerfile.bridge -t presence-bridge .
```

### Project Structure

```
/
├── cmd/ # Main application entry points
│ ├── server/ # HTTP API & WebSocket server
│ ├── bridge/ # MQTT to Kafka bridge
│ ├── decoder/ # BLE beacon decoder
│ ├── location/ # Location calculation algorithm
│ └── testbench/ # Testing/development utilities
├── internal/ # Private application code
│ ├── app/ # Application components
│ └── pkg/ # Shared internal packages
│ ├── model/ # Data structures and types
│ ├── kafkaclient/ # Kafka producer/consumer
│ ├── config/ # Configuration management
│ ├── persistence/ # BoltDB operations
│ ├── redis/ # Redis client
│ └── bridge/ # MQTT bridge logic
├── build/ # Build artifacts and Docker configs
│ ├── package/ # Dockerfile for each component
│ └── docker-compose.yaml # Complete system deployment
├── web/ # Web interface files
├── volumes/ # Persistent data and configurations
├── scripts/ # Utility scripts
└── docs/ # Documentation
```

## 📈 Monitoring and Debugging

### Kafka Topics

- `rawbeacons` - Raw BLE beacon advertisements
- `alertbeacons` - Processed beacon events (battery, buttons)
- `locevents` - Location change notifications

### Health Checks

- **Kafka**: Topic creation and broker connectivity
- **Services**: Automatic restart on failure
- **Database**: BoltDB integrity checks

### Logs

```bash
# View service logs
docker-compose logs -f [service-name]

# View all logs
docker-compose logs -f
```

## 🔌 Integrations

### Node-RED Flows

Pre-configured Node-RED flows are available in `/volumes/node-red/`:

- **Beacon monitoring dashboards**
- **Location-based automations**
- **Battery level alerts**
- **Notification systems**

### MQTT Topics

System publishes to the following MQTT topics:

- `Presence/Beacons/{beacon_id}/location` - Current beacon location
- `Presence/Beacons/{beacon_id}/battery` - Battery level
- `Presence/Beacons/{beacon_id}/button` - Button press events
- `Presence/Beacons/{beacon_id}/distance` - Distance from nearest gateway

## 🤝 Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

### Development Guidelines

- Follow Go conventions and best practices
- Write comprehensive tests for new features
- Update documentation for API changes
- Use meaningful commit messages

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## 🆘 Support

- **Issues**: [GitHub Issues](https://github.com/AFASystems/presence/issues)
- **Documentation**: [Project Wiki](https://github.com/AFASystems/presence/wiki)
- **Discussions**: [GitHub Discussions](https://github.com/AFASystems/presence/discussions)

## 🔮 Roadmap

### Upcoming Features

- [ ] Enhanced location algorithms with machine learning
- [ ] Support for additional beacon types (iBeacon, AltBeacon)
- [ ] Mobile application for beacon management
- [ ] Advanced analytics and reporting dashboard
- [ ] Multi-tenant architecture
- [ ] Cloud deployment templates

### Current Development Status

- ✅ **Bridge Service**: Complete and stable
- ✅ **Decoder Service**: Core functionality implemented
- ✅ **Location Algorithm**: Basic algorithm functional
- ✅ **Server Service**: API and WebSocket implementation
- 🚧 **Web Interface**: Basic UI, enhancements in progress
- 🚧 **Documentation**: Comprehensive documentation being created
- 📋 **Testing**: Automated tests being expanded

---

**AFA Systems Presence Detection** - Real-time BLE beacon tracking and location intelligence for modern IoT environments.

+ 0
- 415
SCORE.md Zobrazit soubor

@@ -1,415 +0,0 @@
# Code Review: AFASystems Presence Detection System

**Date**: 2026-01-15
**Reviewer**: Claude Code
**Project**: BLE Beacon Presence Detection System

---

## Overall Assessment

Your system is a well-structured microservices architecture for BLE beacon presence detection. The code is functional but has several areas that need refactoring for production readiness, maintainability, and robustness.

**Code Quality Score**: 6.5/10

- ✅ Good architecture and separation
- ✅ Thread-safe concurrent access
- ❌ No testing
- ❌ Poor error handling
- ❌ Security concerns
- ❌ Code duplication

---

## 🔴 Critical Issues (Fix Immediately)

### 1. Hardcoded Credentials in Config
**Location**: [internal/pkg/config/config.go:46-49](internal/pkg/config/config.go#L46)
**Risk**: Security vulnerability - default credentials exposed in source code

```go
ClientSecret: getEnv("ClientSecret", "wojuoB7Z5xhlPFrF2lIxJSSdVHCApEgC"),
HTTPPassword: getEnv("HTTPPassword", "C0r3_us3r_Cr3d3nt14ls"),
```

**Fix**: Remove default credentials, require explicit environment configuration:
```go
ClientSecret: getEnvOrFatal("ClientSecret"), // Helper that panics if not set
HTTPPassword: getEnvOrFatal("HTTPPassword"),
```

### 2. Global Database Variable
**Location**: [internal/pkg/database/database.go:12](internal/pkg/database/database.go#L12)
```go
var DB *gorm.DB // ❌ Global variable
```

**Issues**:
- Hard to test
- Implicit dependencies
- Cannot have multiple DB connections

**Fix**: Return `*gorm.DB` from `Connect()` and inject it into services.

### 3. Missing Error Context
Errors are logged but lose context:
```go
fmt.Println("Error in sending Kafka message:", err) // ❌ No context
```

**Fix**: Use structured logging:
```go
slog.Error("failed to send kafka message",
"topic", topic,
"beacon_id", id,
"error", err)
```

### 4. Unsafe Map Access
**Location**: [cmd/bridge/main.go:28](cmd/bridge/main.go#L28)
```go
hostname := strings.Split(topic, "/")[1] // ❌ Panic if index doesn't exist
```

**Fix**: Validate before accessing:
```go
parts := strings.Split(topic, "/")
if len(parts) < 2 {
slog.Warn("invalid topic format", "topic", topic)
return
}
hostname := parts[1]
```

---

## 🟡 High Priority Refactoring (Fix Soon)

### 5. Code Duplication Across Services
**Affected Files**: All 4 main files

**Pattern**: Logging setup (lines 124-131 in all services)
```go
logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
w := io.MultiWriter(os.Stderr, logFile)
logger := slog.New(slog.NewJSONHandler(w, nil))
slog.SetDefault(logger)
```

**Refactor**: Create `internal/pkg/common/logger/logger.go`:
```go
func SetupLogger(filename string) *slog.Logger {
logFile, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Failed to open log file: %v\n", err)
}
w := io.MultiWriter(os.Stderr, logFile)
logger := slog.New(slog.NewJSONHandler(w, nil))
slog.SetDefault(logger)
return logger
}
```

### 6. Inconsistent Error Handling
Mixed error handling patterns across codebase:

**In controller**: [internal/pkg/controller/trackers_controller.go:48](internal/pkg/controller/trackers_controller.go#L48)
```go
if err != nil {
fmt.Println("error in sending Kafka POST message")
http.Error(w, "Error in sending kafka message", 500)
return
}
```

**In main**: [cmd/decoder/main.go:97](cmd/decoder/main.go#L97)
```go
if err != nil {
eMsg := fmt.Sprintf("Error in decoding: %v", err)
fmt.Println(eMsg)
return
}
```

**Fix**: Use consistent error handling with structured responses.

### 7. Missing Configuration Validation
**Location**: [internal/pkg/config/config.go](internal/pkg/config/config.go)

Config doesn't validate required fields:
```go
func Load() *Config {
return &Config{
// No validation
}
}
```

**Fix**: Add validation:
```go
func (c *Config) Validate() error {
if c.DBHost == "" {
return errors.New("DBHost is required")
}
if c.KafkaURL == "" {
return errors.New("KafkaURL is required")
}
// ... other validations
return nil
}
```

### 8. Potential Memory Inefficiency
**Location**: [cmd/location/main.go:217-222](cmd/location/main.go#L217)
```go
if len(beacon.BeaconMetrics) >= settings.BeaconMetricSize {
copy(beacon.BeaconMetrics, beacon.BeaconMetrics[1:])
beacon.BeaconMetrics[settings.BeaconMetricSize-1] = metric
} else {
beacon.BeaconMetrics = append(beacon.BeaconMetrics, metric)
}
```

**Issue**: This logic is correct but could be optimized with a circular buffer.

---

## 🟢 Medium Priority Improvements (Plan Refactor)

### 9. Tight Coupling in Controllers
Controllers directly use `*gorm.DB` instead of repository pattern:

```go
func TrackerAdd(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc
```

**Refactor**: Introduce repository interface:
```go
type TrackerRepository interface {
Create(tracker *model.Tracker) error
Find(id string) (*model.Tracker, error)
// ...
}

func TrackerAdd(repo TrackerRepository, writer *kafka.Writer, ctx context.Context) http.HandlerFunc
```

### 10. Poor Separation of Concerns
**Location**: [internal/pkg/common/appcontext/context.go](internal/pkg/common/appcontext/context.go)

The AppState mixes state management with Kafka client creation (lines 60-76).

**Refactor**: Separate concerns:
```go
// internal/pkg/infrastructure/kafka/pool.go
type KafkaPool struct {
writers []*kafka.Writer
readers []*kafka.Reader
}

// internal/pkg/domain/appstate/state.go
type AppState struct {
beacons *model.BeaconsList
settings *model.Settings
kafkaPool *kafka.KafkaPool // Composed, not owned
}
```

### 11. Magic Numbers
**Location**: [cmd/location/main.go:107-108](cmd/location/main.go#L107)
```go
seenW := 1.5 // What does this mean?
rssiW := 0.75 // What does this mean?
```

**Fix**: Extract to named constants in settings:
```go
type SettingsVal struct {
LocationConfidence int64 `json:"location_confidence"`
SeenWeight float64 `json:"seen_weight"` // 1.5
RSSIWeight float64 `json:"rssi_weight"` // 0.75
// ...
}
```

### 12. Inefficient JSON Marshaling
**Location**: [cmd/server/main.go:206-207](cmd/server/main.go#L206)
```go
js, err := json.Marshal(list)
if err != nil {
js = []byte("error") // ❌ Invalid JSON!
}
```

**Fix**: Return proper error response:
```go
if err != nil {
http.Error(w, "Failed to marshal trackers", http.StatusInternalServerError)
return
}
```

---

## 🔵 Low Priority / Technical Debt

### 13. Zero Test Coverage
The codebase has **no test files**. This is critical for production systems.

**Recommendation**: Add unit tests for:
- All business logic in `service/` package
- Controller handlers
- Kafka message processing
- Beacon parsing logic

**Target**: 70%+ code coverage

### 14. Missing Context Timeouts
**Location**: [cmd/bridge/main.go:68](cmd/bridge/main.go#L68)
```go
err = writer.WriteMessages(context.Background(), msg)
```

**Fix**: Use timeouts:
```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = writer.WriteMessages(ctx, msg)
```

### 15. Inefficient String Concatenation
**Location**: [cmd/bridge/main.go:182](cmd/bridge/main.go#L182)
```go
lMsg := fmt.Sprintf("Beacon added to lookup: %s", id)
slog.Info(lMsg)
```

**Fix**: Direct logging:
```go
slog.Info("beacon added to lookup", "id", id)
```

### 16. Dead Code
**Location**: [cmd/bridge/main.go:76-103](cmd/bridge/main.go#L76)

Large block of commented code should be removed.

### 17. Incomplete Graceful Shutdown
**Location**: [cmd/bridge/main.go:212](cmd/bridge/main.go#L212)

The MQTT client disconnects with timeout but doesn't wait for pending messages:
```go
client.Disconnect(250) // Only waits 250ms
```

### 18. No Health Checks
Services don't expose health endpoints for orchestration systems (Kubernetes, etc.).

---

## 📊 Architecture Recommendations

### 1. Implement Dependency Injection
Instead of passing `db`, `writer`, `ctx` to controllers, create a service container:

```go
type Services struct {
DB *gorm.DB
KafkaWriter *kafka.Writer
AppState *appcontext.AppState
}

func (s *Services) TrackerAddController() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Use s.DB, s.KafkaWriter
}
}
```

### 2. Add Observability
- **Structured logging** with request IDs
- **Metrics** (Prometheus) for:
- Kafka message throughput
- Beacon processing latency
- Database query performance
- **Distributed tracing** (OpenTelemetry)

### 3. Implement Circuit Breakers
For external API calls in `apiclient` package to handle failures gracefully.

### 4. Add Message Validation
Validate Kafka messages before processing:
```go
func (adv *BeaconAdvertisement) Validate() error {
if adv.ID == "" {
return errors.New("beacon ID is required")
}
if adv.RSSI < -100 || adv.RSSI > 0 {
return errors.New("invalid RSSI value")
}
return nil
}
```

---

## 🎯 Refactoring Priority Order

| Priority | Category | Actions |
|----------|----------|---------|
| 1 | 🔴 Security | Remove hardcoded credentials from config |
| 2 | 🔴 Stability | Fix unsafe map/array access |
| 3 | 🔴 Testing | Add unit tests (aim for 70%+ coverage) |
| 4 | 🟡 Error Handling | Implement structured error handling |
| 5 | 🟡 Logging | Standardize to structured logging throughout |
| 6 | 🟡 Code Quality | Extract duplicated code to shared packages |
| 7 | 🟢 Architecture | Implement dependency injection gradually |
| 8 | 🔵 Performance | Optimize hot paths (beacon processing) |

---

## 📈 Metrics Summary

| Category | Count | Status |
|----------|-------|--------|
| 🔴 Critical Issues | 4 | Fix Immediately |
| 🟡 High Priority | 4 | Fix Soon |
| 🟢 Medium Priority | 4 | Plan Refactor |
| 🔵 Low Priority | 6 | Technical Debt |

**Total Issues Identified**: 18

---

## System Architecture Overview

The system consists of 4 microservices:

1. **Bridge** ([cmd/bridge/main.go](cmd/bridge/main.go)) - MQTT to Kafka bridge
2. **Decoder** ([cmd/decoder/main.go](cmd/decoder/main.go)) - BLE beacon decoder
3. **Location** ([cmd/location/main.go](cmd/location/main.go)) - Location calculation service
4. **Server** ([cmd/server/main.go](cmd/server/main.go)) - HTTP API & WebSocket server

### Communication Flow
```
MQTT Gateway → Bridge (Kafka) → Decoder (Kafka) → Location (Kafka) → Server (Kafka)
↓ ↑
External API ←───────────────────────────────────────────────
```

### Technology Stack
- **Language**: Go 1.24.0
- **Message Broker**: Apache Kafka
- **Database**: PostgreSQL with GORM
- **Cache**: Redis (valkey)
- **MQTT**: Eclipse Paho
- **HTTP**: Gorilla Mux + WebSocket
- **Deployment**: Docker Compose

---

## Conclusion

The codebase demonstrates solid understanding of microservices architecture with good separation of concerns. The concurrent access patterns using `sync.RWMutex` are well-implemented. However, the system needs significant hardening before production deployment, particularly in areas of security, testing, and error handling.

Focus on addressing critical security issues first, then build out test coverage to ensure reliability as you refactor other areas of the codebase.

+ 0
- 8
api/README.md Zobrazit soubor

@@ -1,8 +0,0 @@
# `/api`

OpenAPI/Swagger specs, JSON schema files, protocol definition files.

Examples:

* https://github.com/kubernetes/kubernetes/tree/master/api
* https://github.com/moby/moby/tree/master/api

+ 0
- 50
api/openapi.yml Zobrazit soubor

@@ -1,50 +0,0 @@
openapi: 3.0.0
info:
title: Beacon Parser API
version: 1.0.0
paths:
/configs/beacons:
get:
summary: Retrieve beacon parsing configurations
responses:
'200':
description: A list of beacon protocol definitions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/BeaconConfig'

components:
schemas:
BeaconConfig:
type: object
properties:
name:
type: string
example: "Ingics"
min:
type: integer
max:
type: integer
pattern:
type: array
items:
type: string
example: ["0xFF", "0x59"]
configs:
type: object
additionalProperties:
$ref: '#/components/schemas/FieldMapping'
FieldMapping:
type: object
properties:
offset:
type: integer
length:
type: integer
order:
type: string
enum: [littleendian, bigendian]

+ 0
- 3
assets/README.md Zobrazit soubor

@@ -1,3 +0,0 @@
# `/assets`

Other assets to go along with your repository (images, logos, etc).

+ 4
- 0
cmd/location/main.go Zobrazit soubor

@@ -98,6 +98,10 @@ func getLikelyLocations(appState *appcontext.AppState, writer *kafka.Writer) {

mSize := len(beacon.BeaconMetrics)

if mSize == 0 {
continue
}

if (int64(time.Now().Unix()) - (beacon.BeaconMetrics[mSize-1].Timestamp)) > settings.LastSeenThreshold {
slog.Warn("beacon is too old")
continue


+ 0
- 9
examples/README.md Zobrazit soubor

@@ -1,9 +0,0 @@
# `/examples`

Examples for your applications and/or public libraries.

Examples:

* https://github.com/nats-io/nats.go/tree/master/examples
* https://github.com/docker-slim/docker-slim/tree/master/examples
* https://github.com/hashicorp/packer/tree/master/examples

+ 0
- 3
githooks/README.md Zobrazit soubor

@@ -1,3 +0,0 @@
# `/githooks`

Git hooks.

+ 7
- 14
go.mod Zobrazit soubor

@@ -5,37 +5,30 @@ go 1.24.0
toolchain go1.24.9

require (
github.com/boltdb/bolt v1.3.1
github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
github.com/redis/go-redis/v9 v9.16.0
github.com/mitchellh/mapstructure v1.5.0
github.com/segmentio/kafka-go v0.4.49
github.com/yosssi/gmq v0.0.1
gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.31.1
)

require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/eclipse/paho.golang v0.23.0 // indirect
github.com/eclipse/paho.mqtt.golang v1.5.1 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/stretchr/testify v1.11.1 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.29.0 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/gorm v1.31.1 // indirect
)

+ 1
- 28
go.sum Zobrazit soubor

@@ -1,18 +1,6 @@
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/eclipse/paho.golang v0.23.0 h1:KHgl2wz6EJo7cMBmkuhpt7C576vP+kpPv7jjvSyR6Mk=
github.com/eclipse/paho.golang v0.23.0/go.mod h1:nQRhTkoZv8EAiNs5UU0/WdQIx2NrnWUpL9nsGJTQN04=
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
@@ -37,8 +25,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -47,38 +33,25 @@ github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/segmentio/kafka-go v0.4.49 h1:GJiNX1d/g+kG6ljyJEoi9++PUMdXGAxb7JGPiDCuNmk=
github.com/segmentio/kafka-go v0.4.49/go.mod h1:Y1gn60kzLEEaW28YshXyk2+VCUKbJ3Qr6DrnT3i4+9E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/yosssi/gmq v0.0.1 h1:GhlDVaAQoi3Mvjul/qJXXGfL4JBeE0GQwbWp3eIsja8=
github.com/yosssi/gmq v0.0.1/go.mod h1:mReykazh0U1JabvuWh1PEbzzJftqOQWsjr0Lwg5jL1Y=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=


+ 0
- 3
init/README.md Zobrazit soubor

@@ -1,3 +0,0 @@
# `/init`

System init (systemd, upstart, sysv) and process manager/supervisor (runit, supervisord) configs.

+ 0
- 106
internal/pkg/bridge/mqtthandler/mqtthandler.go Zobrazit soubor

@@ -1,106 +0,0 @@
package mqtthandler

import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"

"github.com/AFASystems/presence/internal/pkg/model"
"github.com/segmentio/kafka-go"
)

func MqttHandler(writer *kafka.Writer, topicName []byte, message []byte) {
hostname := strings.Split(string(topicName), "/")[1]
msgStr := string(message)

if strings.HasPrefix(msgStr, "[") {
var readings []model.RawReading
err := json.Unmarshal(message, &readings)
if err != nil {
log.Printf("Error parsing JSON: %v", err)
return
}

for _, reading := range readings {
if reading.Type == "Gateway" {
continue
}
adv := model.BeaconAdvertisement{
Hostname: hostname,
MAC: reading.MAC,
RSSI: int64(reading.RSSI),
Data: reading.RawData,
HSButtonCounter: parseButtonState(reading.RawData),
}

encodedMsg, err := json.Marshal(adv)
if err != nil {
fmt.Println("Error in marshaling: ", err)
break
}

msg := kafka.Message{
Value: encodedMsg,
}
err = writer.WriteMessages(context.Background(), msg)
if err != nil {
fmt.Println("Error in writing to Kafka: ", err)
time.Sleep(1 * time.Second)
break
}
}
} else {
s := strings.Split(string(message), ",")
if len(s) < 6 {
log.Printf("Messaggio CSV non valido: %s", msgStr)
return
}

rawdata := s[4]
buttonCounter := parseButtonState(rawdata)
if buttonCounter > 0 {
adv := model.BeaconAdvertisement{}
i, _ := strconv.ParseInt(s[3], 10, 64)
adv.Hostname = hostname
adv.BeaconType = "hb_button"
adv.MAC = s[1]
adv.RSSI = i
adv.Data = rawdata
adv.HSButtonCounter = buttonCounter

read_line := strings.TrimRight(string(s[5]), "\r\n")
it, err33 := strconv.Atoi(read_line)
if err33 != nil {
fmt.Println(it)
fmt.Println(err33)
os.Exit(2)
}
}
}
}

func parseButtonState(raw string) int64 {
raw = strings.ToUpper(raw)

if strings.HasPrefix(raw, "0201060303E1FF12") && len(raw) >= 38 {
buttonField := raw[34:38]
if buttonValue, err := strconv.ParseInt(buttonField, 16, 64); err == nil {
return buttonValue
}
}

if strings.HasPrefix(raw, "02010612FF590") && len(raw) >= 24 {
counterField := raw[22:24]
buttonState, err := strconv.ParseInt(counterField, 16, 64)
if err == nil {
return buttonState
}
}

return 0
}

+ 0
- 29
internal/pkg/common/appcontext/context.go Zobrazit soubor

@@ -104,23 +104,6 @@ func (m *AppState) GetBeacon(id string) (model.Beacon, bool) {
return beacon, exists
}

// GetHTTPResult returns a beacon from HTTP results by ID (thread-safe)
func (m *AppState) GetHTTPResult(id string) (model.HTTPResult, bool) {
m.httpResults.Lock.RLock()
defer m.httpResults.Lock.RUnlock()

beacon, exists := m.httpResults.Results[id]
return beacon, exists
}

// UpdateHTTPResult updates a beacon in the list (thread-safe)
func (m *AppState) UpdateHTTPResult(id string, beacon model.HTTPResult) {
m.httpResults.Lock.Lock()
defer m.httpResults.Lock.Unlock()

m.httpResults.Results[id] = beacon
}

// UpdateBeacon updates a beacon in the list (thread-safe)
func (m *AppState) UpdateBeacon(id string, beacon model.Beacon) {
m.beacons.Lock.Lock()
@@ -158,18 +141,6 @@ func (m *AppState) GetAllBeacons() map[string]model.Beacon {
return beacons
}

// GetAllHttpResults returns a copy of all beacons
func (m *AppState) GetAllHttpResults() map[string]model.HTTPResult {
m.httpResults.Lock.RLock()
defer m.httpResults.Lock.RUnlock()

beacons := make(map[string]model.HTTPResult)
for id, beacon := range m.httpResults.Results {
beacons[id] = beacon
}
return beacons
}

// GetBeaconCount returns the number of tracked beacons
func (m *AppState) GetBeaconCount() int {
m.beacons.Lock.RLock()


+ 0
- 11
internal/pkg/common/utils/distance.go Zobrazit soubor

@@ -25,14 +25,3 @@ func twosComp(inp string) int64 {
i, _ := strconv.ParseInt("0x"+inp, 0, 64)
return i - 256
}

// ValidateRSSI validates if RSSI value is within reasonable bounds
func ValidateRSSI(rssi int64) bool {
return rssi >= -120 && rssi <= 0
}

// ValidateTXPower validates if TX power is within reasonable bounds
func ValidateTXPower(txPower string) bool {
power := twosComp(txPower)
return power >= -128 && power <= 127
}

+ 1
- 1
internal/pkg/controller/trackers_controller.go Zobrazit soubor

@@ -100,7 +100,7 @@ func TrackerDelete(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.
}

apiUpdate := model.ApiUpdate{
Method: "Delete",
Method: "DELETE",
ID: tracker.ID,
}



+ 0
- 22
internal/pkg/model/type_methods.go Zobrazit soubor

@@ -23,25 +23,3 @@ func (b BeaconEvent) ToJSON() ([]byte, error) {
}
return eData, nil
}

func convertStructToMap(obj any) (map[string]any, error) {
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}

var result map[string]any
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}

return result, nil
}

func (loc HTTPLocation) RedisHashable() (map[string]any, error) {
return convertStructToMap(loc)
}

func (be BeaconEvent) RedisHashable() (map[string]any, error) {
return convertStructToMap(be)
}

+ 0
- 44
internal/pkg/redis/redis.go Zobrazit soubor

@@ -1,44 +0,0 @@
package presenseredis

import (
"context"
"encoding/json"
"fmt"

"github.com/redis/go-redis/v9"
)

// Get Map from Redis
//
// Deprecated: there is only one map now and we know the type
func LoadRedisMap[K comparable, V any, M map[K]V](client *redis.Client, ctx context.Context, key string) M {
redisValue, err := client.Get(ctx, key).Result()
resMap := make(M)

if err == redis.Nil {
fmt.Printf("No list found for key %s, starting empty\n", key)
} else if err != nil {
fmt.Printf("Error in connecting to Redis: %v, key: %s returning empty map\n", err, key)
} else {
if err := json.Unmarshal([]byte(redisValue), &resMap); err != nil {
fmt.Printf("Error in unmarshalling JSON for key: %s\n", key)
}
}

return resMap
}

// Set Map in Redis
//
// Deprecated: hashmaps are used now
func SaveRedisMap(client *redis.Client, ctx context.Context, key string, data interface{}) {
eData, err := json.Marshal(data)
if err != nil {
fmt.Println("Error in marshalling, key: ", key)
}

err = client.Set(ctx, key, eData, 0).Err()
if err != nil {
fmt.Println("Error in persisting in Redis, key: ", key)
}
}

+ 0
- 17
internal/pkg/service/beacon_service.go Zobrazit soubor

@@ -8,7 +8,6 @@ import (
"strings"
"time"

"github.com/AFASystems/presence/internal/pkg/common/appcontext"
"github.com/AFASystems/presence/internal/pkg/model"
"github.com/segmentio/kafka-go"
"gorm.io/gorm"
@@ -72,22 +71,6 @@ func LocationToBeaconService(msg model.HTTPLocation, db *gorm.DB, writer *kafka.
}
}

func EventToBeaconService(msg model.BeaconEvent, appState *appcontext.AppState, ctx context.Context) error {
id := msg.ID
beacon, ok := appState.GetHTTPResult(id)
if !ok {
appState.UpdateHTTPResult(id, model.HTTPResult{ID: id, BeaconType: msg.Type, Battery: int64(msg.Battery), Event: msg.Event})
} else {
beacon.ID = id
beacon.BeaconType = msg.Type
beacon.Battery = int64(msg.Battery)
beacon.Event = msg.Event
appState.UpdateHTTPResult(id, beacon)
}

return nil
}

func formatMac(MAC string) string {
var res strings.Builder
for i := 0; i < len(MAC); i += 2 {


+ 0
- 1467
logging.md
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


+ 0
- 676
refactor.md Zobrazit soubor

@@ -1,676 +0,0 @@
# Refactoring Plan for AFASystems Presence Detection System

**Date:** 2026-01-16
**Total Codebase:** ~3,391 lines of Go code across 4 services

## Executive Summary

After analyzing the codebase across the 4 main services (`bridge`, `decoder`, `location`, `server`), I've identified significant code duplication, inconsistent patterns, and maintenance challenges. This document outlines a structured refactoring approach to improve maintainability, reduce duplication, and establish clear architectural patterns.

---

## Critical Issues Identified

### 1. **Massive Code Duplication** (Priority: HIGH)

#### Problem: Identical Boilerplate in All Services
All 4 services (`bridge/main.go:118-131`, `decoder/main.go:36-44`, `location/main.go:31-39`, `server/main.go:45-53`) contain **identical** code for:
- Log file creation
- Multi-writer setup (stderr + file)
- Logger initialization with JSON handler
- Context setup with signal handling

**Impact:** Any change to logging or signal handling requires updating 4 files.

**Duplication Factor:** ~60 lines × 4 services = 240 lines of duplicated code

#### Problem: Kafka Consumer Pattern Duplication
Each service manually creates channels, adds to waitgroups, and starts consumers in the same pattern:
```go
chRaw := make(chan model.BeaconAdvertisement, 2000)
wg.Add(1)
go kafkaclient.Consume(rawReader, chRaw, ctx, &wg)
```

This pattern appears in `bridge/main.go:147-154`, `decoder/main.go:57-62`, `location/main.go:55-60`, `server/main.go:110-115`.

---

### 2. **Dead Code** (Priority: MEDIUM)

#### Problem: Commented-out Code in bridge/main.go:76-103
83 lines of commented CSV parsing code remain in the codebase. This:
- Reduces readability
- Creates confusion about what functionality is active
- Should be removed or moved to version control history

#### Problem: Unused Variables
In `bridge/main.go:38`:
```go
var wg sync.WaitGroup
```
This package-level variable is used but would be better as a struct field in a service object.

---

### 3. **Inconsistent Error Handling** (Priority: HIGH)

#### Problem: Mixed Error Handling Patterns
Across services, there are at least 3 different error handling patterns:

1. **Silent continuation** (`bridge/main.go:35-37`):
```go
if err != nil {
log.Printf("Error parsing JSON: %v", err)
return // or continue
}
```

2. **Panic on error** (`bridge/main.go:169-171`):
```go
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
```

3. **Fatal termination** (`server/main.go:60-62`):
```go
if err != nil {
log.Fatalf("Failed to open database connection: %v\n", err)
}
```

**Impact:** Inconsistent behavior makes debugging difficult and error handling unpredictable.

---

### 4. **Monolithic main() Functions** (Priority: HIGH)

#### Problem: Single Large Function Does Everything
All main functions are doing too much:
- **bridge/main.go:118-224** (106 lines): Setup, MQTT connection, event loop, Kafka handling, shutdown
- **server/main.go:41-219** (178 lines): DB setup, Kafka setup, HTTP server, WebSocket, event loop, shutdown
- **decoder/main.go:27-91** (64 lines): Kafka setup, parser registry, event loop, processing
- **location/main.go:26-90** (64 lines): Kafka setup, ticker management, event loop, location algorithm

**Impact:** Hard to test, hard to reason about, high cyclomatic complexity.

---

### 5. **Lack of Abstraction for Common Patterns** (Priority: MEDIUM)

#### Problem: No Service Lifecycle Management
Each service manually:
1. Creates logger
2. Sets up signal context
3. Creates Kafka readers/writers
4. Starts consumers
5. Runs event loop
6. Handles shutdown
7. Closes Kafka connections

This is a perfect candidate for an abstraction.

---

### 6. **Hardcoded Configuration** (Priority: MEDIUM)

#### Problem: Hardcoded Paths and Values
- `server/main.go:75`: Hardcoded config file path `"/app/cmd/server/config.json"`
- `bridge/main.go:227`: Hardcoded MQTT topic `"publish_out/#"`
- `server/main.go:238`: Hardcoded ping ticker calculation `(60 * 9) / 10 * time.Second`
- `server/main.go:147`: Hardcoded beacon ticker `2 * time.Second`

**Impact:** Difficult to configure without code changes.

---

### 7. **Missing TODO Resolution** (Priority: LOW)

#### Outstanding TODO
`internal/pkg/model/parser.go:74`:
```go
// TODO: change this to be dynamic, maybe event is interface with no predefined properties
```

This should be addressed to make the parser more flexible.

---

### 8. **Inefficient Memory Usage** (Priority: LOW)

#### Problem: Unbounded Map Growth Potential
In `location/main.go:113-119`:
```go
locList := make(map[string]float64)
for _, metric := range beacon.BeaconMetrics {
res := seenW + (rssiW * (1.0 - (float64(metric.RSSI) / -100.0)))
locList[metric.Location] += res
}
```

If `BeaconMetrics` grows unbounded, this could become a performance issue. However, current implementation limits this via `BeaconMetricSize` setting.

---

## Refactoring Recommendations

### Phase 1: Create Common Infrastructure (Immediate)

#### 1.1 Create Service Lifecycle Framework

**File:** `internal/pkg/server/service.go`
```go
package server

import (
"context"
"io"
"log"
"log/slog"
"os"
"os/signal"
"sync"
"syscall"
)

type Service struct {
name string
cfg Config
logger *slog.Logger
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
kafkaMgr *KafkaManager
}

func NewService(name string, cfg Config) (*Service, error) {
// Initialize logger
// Setup signal handling
// Create Kafka manager
}

func (s *Service) Logger() *slog.Logger {
return s.logger
}

func (s *Service) Context() context.Context {
return s.ctx
}

func (s *Service) WaitGroup() *sync.WaitGroup {
return &s.wg
}

func (s *Service) Start() {
// Start event loop
}

func (s *Service) Shutdown() {
// Handle graceful shutdown
}
```

**Benefits:**
- Single place for lifecycle management
- Consistent startup/shutdown across all services
- Easier testing with mock dependencies

#### 1.2 Extract Logger Initialization

**File:** `internal/pkg/server/logger.go`
```go
package server

import (
"io"
"log"
"log/slog"
"os"
)

func InitLogger(logPath string) (*slog.Logger, io.Closer, error) {
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, nil, err
}

w := io.MultiWriter(os.Stderr, logFile)
logger := slog.New(slog.NewJSONHandler(w, nil))
slog.SetDefault(logger)

return logger, logFile, nil
}
```

**Benefits:**
- Reusable across all services
- Consistent logging format
- Easier to change logging strategy

#### 1.3 Create Kafka Manager

**File:** `internal/pkg/server/kafka.go`
```go
package server

import (
"context"
"sync"

"github.com/AFASystems/presence/internal/pkg/kafkaclient"
"github.com/AFASystems/presence/internal/pkg/model"
"github.com/segmentio/kafka-go"
)

type KafkaManager struct {
readers []*kafka.Reader
writers []*kafka.Writer
lock sync.RWMutex
}

func (km *KafkaManager) CreateReader(url, topic, groupID string) *kafka.Reader
func (km *KafkaManager) CreateWriter(url, topic string) *kafka.Writer
func (km *KafkaManager) StartConsumer[T any](reader *kafka.Reader, ch chan<- T, ctx context.Context)
func (km *KafkaManager) Close()
```

**Benefits:**
- Centralized Kafka lifecycle management
- Type-safe consumer creation
- Automatic cleanup on shutdown

---

### Phase 2: Refactor Individual Services (Short-term)

#### 2.1 Bridge Service Refactoring

**Current Issues:**
- Large monolithic main (106 lines)
- MQTT handler mixed with Kafka logic
- Commented dead code

**Refactored Structure:**
```
cmd/bridge/
├── main.go (50 lines - just setup)
├── service.go (BridgeService struct)
├── mqtthandler/
│ ├── handler.go (MQTT message handling)
│ └── parser.go (Parse MQTT messages)
└── kafkaevents/
└── handlers.go (Kafka event handlers)
```

**Actions:**
1. Remove dead code (lines 76-103)
2. Extract MQTT handling to separate package
3. Create BridgeService struct with lifecycle methods
4. Use common Service framework from Phase 1

#### 2.2 Decoder Service Refactoring

**Current Issues:**
- Processing logic mixed with event loop
- Parser registry embedded in main

**Refactored Structure:**
```
cmd/decoder/
├── main.go (30 lines - just setup)
├── service.go (DecoderService struct)
├── processor/
│ ├── beacon.go (Beacon decoding logic)
│ └── registry.go (Parser registry management)
└── kafkaevents/
└── handlers.go (Kafka event handlers)
```

**Actions:**
1. Extract `decodeBeacon` logic to processor package
2. Create Processor interface for different beacon types
3. Separate parser registry into its own file

#### 2.3 Location Service Refactoring

**Current Issues:**
- Location algorithm embedded in event loop
- No abstraction for different algorithms

**Refactored Structure:**
```
cmd/location/
├── main.go (30 lines - just setup)
├── service.go (LocationService struct)
├── algorithms/
│ ├── interface.go (LocationAlgorithm interface)
│ ├── filter.go (Current filter algorithm)
│ └── ai.go (Future AI algorithm)
└── beacon/
└── tracker.go (Beacon tracking logic)
```

**Actions:**
1. Define LocationAlgorithm interface
2. Move filter algorithm to separate file
3. Add factory pattern for algorithm selection
4. Extract beacon tracking logic

#### 2.4 Server Service Refactoring

**Current Issues:**
- Largest main function (178 lines)
- Mixed concerns: HTTP, WebSocket, Kafka, Database
- Deeply nested handler setup

**Refactored Structure:**
```
cmd/server/
├── main.go (40 lines - just setup)
├── service.go (ServerService struct)
├── http/
│ ├── server.go (HTTP server setup)
│ ├── routes.go (Route registration)
│ └── middleware.go (CORS, logging, etc.)
├── websocket/
│ ├── handler.go (WebSocket upgrade)
│ ├── writer.go (WebSocket write logic)
│ └── reader.go (WebSocket read logic)
└── kafkaevents/
└── handlers.go (Kafka event handlers)
```

**Actions:**
1. Extract HTTP server to separate package
2. Move WebSocket logic to dedicated package
3. Create route registration table
4. Separate Kafka event handlers

---

### Phase 3: Standardize Error Handling (Medium-term)

#### 3.1 Define Error Handling Policy

**File:** `internal/pkg/errors/errors.go`
```go
package errors

import (
"fmt"
"log/slog"
)

// Wrap wraps an error with context
func Wrap(err error, message string) error {
return fmt.Errorf("%s: %w", message, err)
}

// LogAndReturn logs an error and returns it
func LogAndReturn(err error, message string) error {
slog.Error(message, "error", err)
return fmt.Errorf("%s: %w", message, err)
}

// Must panics if err is not nil (for initialization only)
func Must(err error, message string) {
if err != nil {
panic(fmt.Sprintf("%s: %v", message, err))
}
}
```

**Policy:**
- Use `LogAndReturn` for recoverable errors in event loops
- Use `Must` for initialization failures that prevent startup
- Use `Wrap` to add context to errors before returning
- Never use silent log-and-continue without explicit comments

---

### Phase 4: Configuration Management (Medium-term)

#### 4.1 Centralize Configuration

**File:** `internal/pkg/config/bridge.go` (one per service)
```go
package config

type BridgeConfig struct {
// Kafka settings
KafkaURL string

// MQTT settings
MQTTUrl string
MQTTPort int
MQTTTopics []string
MQTTClientID string

// Logging
LogPath string

// Channels
ChannelBuffer int
}

func LoadBridge() (*BridgeConfig, error) {
cfg := Load() // Load base config

return &BridgeConfig{
KafkaURL: cfg.KafkaURL,
MQTTUrl: cfg.MQTTHost,
MQTTPort: 1883,
MQTTTopics: []string{"publish_out/#"},
MQTTClientID: "go_mqtt_client",
LogPath: "server.log",
ChannelBuffer: 200,
}, nil
}
```

**Benefits:**
- No more hardcoded values
- Easy to add environment variable overrides
- Clear configuration schema per service
- Easier testing with different configs

---

### Phase 5: Testing Infrastructure (Long-term)

#### 5.1 Add Interface Definitions

Create interfaces for all external dependencies:
- `MQTTClient` interface
- `KafkaReader` interface
- `KafkaWriter` interface
- `Database` interface

**Benefits:**
- Easy to mock for testing
- Clear contracts between components
- Better documentation

#### 5.2 Add Unit Tests

Target coverage: 70%+

**Priority:**
1. Business logic (location algorithms, beacon parsing)
2. Service lifecycle (startup, shutdown)
3. Error handling paths
4. Kafka message processing

---

## Specific Code Improvements

### Remove Dead Code

**File:** `cmd/bridge/main.go:76-103`
- **Action:** Delete the 83 lines of commented CSV code
- **Reason:** Dead code, maintained in git history if needed

### Fix Package-Level Variables

**File:** `cmd/bridge/main.go:25`
- **Current:** `var wg sync.WaitGroup`
- **Action:** Move to BridgeService struct field
- **Reason:** Avoid global state, enable multiple service instances

### Resolve TODO

**File:** `internal/pkg/model/parser.go:74`
- **Current:** Hardcoded beacon event structure
- **Action:** Make BeaconEvent use flexible map or interface
- **Reason:** Support different beacon types without struct changes

### Improve Channel Buffering

**Current:** Random channel buffer sizes (200, 500, 2000)
- **Action:** Define constant or configuration value
- **File:** `internal/pkg/config/constants.go`
```go
const (
DefaultChannelBuffer = 200
LargeChannelBuffer = 2000
)
```

### Add Context Timeouts

**Current:** Some operations have no timeout
**Examples:**
- `bridge/main.go:69`: Kafka write has no timeout
- `bridge/main.go:158`: MQTT connection has no explicit timeout

**Action:** Add timeouts to all I/O operations
```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err = writer.WriteMessages(ctx, msg)
```

---

## Implementation Priority

### Week 1: Foundation
1. ✅ Create service lifecycle framework
2. ✅ Extract logger initialization
3. ✅ Remove dead code from bridge

### Week 2-3: Service Refactoring
1. ✅ Refactor bridge service
2. ✅ Refactor decoder service
3. ✅ Refactor location service
4. ✅ Refactor server service

### Week 4: Error Handling & Config
1. ✅ Standardize error handling
2. ✅ Centralize configuration
3. ✅ Add configuration validation

### Week 5+: Testing & Documentation
1. ✅ Add unit tests for core logic
2. ✅ Add integration tests
3. ✅ Update documentation
4. ✅ Create architecture diagrams

---

## Success Metrics

### Code Quality
- **Before:** 240 lines of duplicated code
- **After:** < 50 lines of shared infrastructure
- **Reduction:** 80% reduction in duplication

### Maintainability
- **Before:** Changes require updating 4 files
- **After:** Changes to shared code update once
- **Impact:** Faster development, fewer bugs

### Testing
- **Before:** No unit tests (based on provided files)
- **After:** 70%+ code coverage
- **Impact:** Catches regressions early

### File Sizes
- **Before:** main.go files 106-178 lines
- **After:** main.go files < 50 lines
- **Impact:** Easier to understand, better separation of concerns

---

## Migration Strategy

### Incremental Refactoring
1. **DO NOT** rewrite everything at once
2. Extract common code without changing behavior
3. Add tests before refactoring
4. Run existing tests after each change
5. Use feature flags for major changes

### Backward Compatibility
- Keep Kafka topic names unchanged
- Keep API endpoints unchanged
- Keep database schema unchanged
- Allow old and new code to coexist during migration

### Testing During Migration
1. Run existing services in parallel
2. Compare outputs
3. Load test with production-like traffic
4. Monitor for differences
5. Gradual rollout

---

## Additional Recommendations

### Documentation
1. Add godoc comments to all exported functions
2. Create architecture diagrams showing data flow
3. Document Kafka message formats
4. Add runbook for common operations

### Monitoring
1. Add Prometheus metrics
2. Add structured logging with correlation IDs
3. Add health check endpoints
4. Add performance tracing

### Development Workflow
1. Add pre-commit hooks
2. Add linting (golangci-lint)
3. Add formatting checks (gofmt, goimports)
4. Add dependency scanning

---

## Conclusion

The current codebase suffers from significant duplication and lacks clear architectural boundaries. By implementing this refactoring plan incrementally, you can:

1. **Reduce duplication by 80%** through shared infrastructure
2. **Improve maintainability** through consistent patterns
3. **Enable testing** through proper abstractions
4. **Reduce bugs** through standardized error handling
5. **Accelerate development** through clearer structure

The key is to refactor **incrementally** while maintaining backward compatibility and adding tests at each step.

---

## Next Steps

1. **Review this document** with your team
2. **Prioritize phases** based on your pain points
3. **Create tracking issues** for each phase
4. **Start with Phase 1** (common infrastructure)
5. **Measure success** using the metrics above

**Recommended First Step:** Begin with Phase 1.1 (Service Lifecycle Framework) as it provides the foundation for all other refactoring work.


+ 0
- 3
third_party/README.md Zobrazit soubor

@@ -1,3 +0,0 @@
# `/third_party`

External helper tools, forked code and other 3rd party utilities (e.g., Swagger UI).

+ 0
- 9
tools/README.md Zobrazit soubor

@@ -1,9 +0,0 @@
# `/tools`

Supporting tools for this project. Note that these tools can import code from the `/pkg` and `/internal` directories.

Examples:

* https://github.com/istio/istio/tree/master/tools
* https://github.com/openshift/origin/tree/master/tools
* https://github.com/dapr/dapr/tree/master/tools

+ 0
- 3
web/README.md Zobrazit soubor

@@ -1,3 +0,0 @@
# `/web`

Web application specific components: static web assets, server side templates and SPAs.

+ 0
- 0
web/app/.keep Zobrazit soubor


+ 0
- 0
web/static/.keep Zobrazit soubor


+ 0
- 0
web/template/.keep Zobrazit soubor


+ 0
- 8
website/README.md Zobrazit soubor

@@ -1,8 +0,0 @@
# `/website`

This is the place to put your project's website data if you are not using GitHub pages.

Examples:

* https://github.com/hashicorp/vault/tree/master/website
* https://github.com/perkeep/perkeep/tree/master/website

Načítá se…
Zrušit
Uložit