diff --git a/README.md b/README.md
index 0a7856b..b74368f 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,12 @@ Eine moderne Location-Tracking Anwendung basierend auf Next.js 14 mit MQTT/OwnTr
- 📊 **Dashboard** - Übersicht über Geräte, Statistiken und Datenbankstatus
- ⏱️ **System Status** - Live-Uptime, Memory Usage, Runtime Info
- 📱 **Device Management** - Geräte hinzufügen, bearbeiten, löschen
+- 🎯 **Geofencing** - Kreisförmige Geofences mit Enter/Exit-Benachrichtigungen:
+ - Geofences erstellen, bearbeiten und löschen
+ - Radius 50m bis 50km konfigurierbar
+ - Visualisierung auf der Karte mit Toggle-Button
+ - Event-Historie mit Enter/Exit-Ereignissen
+ - E-Mail und MQTT-Benachrichtigungen bei Grenzüberschreitung
- 💾 **Datenbank-Wartung**:
- 🧹 Cleanup alter Daten (7, 15, 30, 90 Tage)
- ⚡ Datenbank-Optimierung (VACUUM)
@@ -51,17 +57,20 @@ Eine moderne Location-Tracking Anwendung basierend auf Next.js 14 mit MQTT/OwnTr
## 🛠 Tech Stack
-- **Framework:** Next.js 14 (App Router)
+- **Framework:** Next.js 16.0.7 (App Router)
+- **Runtime:** React 19.2.1
- **Sprache:** TypeScript 5.9
- **Styling:** Tailwind CSS v4
- **Karten:** Leaflet 1.9.4 + React-Leaflet 5.0
- **Authentifizierung:** NextAuth.js v5 (beta)
- **Datenbank:** SQLite (better-sqlite3)
+- **MQTT:** MQTT.js v5 + Eclipse Mosquitto
+- **E-Mail:** Nodemailer + React Email
- **Passwort-Hashing:** bcryptjs
- **Datenquelle:** MQTT Broker + lokale SQLite-Cache
### Dual-Database Architektur
-- **database.sqlite** - User, Geräte (kritische Daten)
+- **database.sqlite** - User, Geräte, MQTT-Credentials, Geofences (kritische Daten)
- **locations.sqlite** - Location-Tracking (hohe Schreibrate, isoliert)
---
@@ -91,7 +100,7 @@ npm run db:init
```
Dies erstellt:
-- `data/database.sqlite` (User + Devices)
+- `data/database.sqlite` (User + Devices + MQTT + Geofences)
- `data/locations.sqlite` (Location-Tracking)
- Standard Admin-User: `admin` / `admin123`
- Standard Devices (ID 10, 11)
@@ -128,6 +137,11 @@ npm run db:init:app
npm run db:init:locations
```
+**Nur Geofence-Tabellen:**
+```bash
+npm run db:init:geofence
+```
+
### Datenbank zurücksetzen
**Admin-User neu anlegen:**
@@ -207,6 +221,52 @@ created_at TEXT DEFAULT (datetime('now'))
updated_at TEXT DEFAULT (datetime('now'))
```
+**Geofence Tabelle:**
+```sql
+id TEXT PRIMARY KEY
+name TEXT NOT NULL
+description TEXT
+shape_type TEXT NOT NULL DEFAULT 'circle' -- Only 'circle' for MVP
+center_latitude REAL NOT NULL
+center_longitude REAL NOT NULL
+radius_meters INTEGER NOT NULL
+owner_id TEXT NOT NULL -- FK zu User.id
+device_id TEXT NOT NULL -- FK zu Device.id
+is_active INTEGER DEFAULT 1 -- 0 oder 1
+color TEXT DEFAULT '#3b82f6'
+created_at TEXT DEFAULT (datetime('now'))
+updated_at TEXT DEFAULT (datetime('now'))
+```
+
+**GeofenceEvent Tabelle:**
+```sql
+id INTEGER PRIMARY KEY AUTOINCREMENT
+geofence_id TEXT NOT NULL -- FK zu Geofence.id
+device_id TEXT NOT NULL -- FK zu Device.id
+location_id INTEGER NOT NULL
+event_type TEXT NOT NULL -- 'enter' oder 'exit'
+latitude REAL NOT NULL
+longitude REAL NOT NULL
+distance_from_center REAL
+notification_sent INTEGER DEFAULT 0 -- 0=not sent, 1=sent, 2=failed
+notification_error TEXT
+timestamp TEXT NOT NULL
+created_at TEXT DEFAULT (datetime('now'))
+```
+
+**GeofenceStatus Tabelle:**
+```sql
+id INTEGER PRIMARY KEY AUTOINCREMENT
+device_id TEXT NOT NULL -- FK zu Device.id
+geofence_id TEXT NOT NULL -- FK zu Geofence.id
+is_inside INTEGER NOT NULL DEFAULT 0 -- 0 oder 1
+last_enter_time TEXT
+last_exit_time TEXT
+last_checked_at TEXT
+created_at TEXT DEFAULT (datetime('now'))
+updated_at TEXT DEFAULT (datetime('now'))
+```
+
**Indexes:**
- `idx_user_username` ON User(username)
- `idx_user_parent` ON User(parent_user_id)
@@ -215,6 +275,17 @@ updated_at TEXT DEFAULT (datetime('now'))
- `idx_mqtt_credentials_device` ON mqtt_credentials(device_id)
- `idx_mqtt_credentials_username` ON mqtt_credentials(mqtt_username)
- `idx_mqtt_acl_device` ON mqtt_acl_rules(device_id)
+- `idx_geofence_owner` ON Geofence(owner_id)
+- `idx_geofence_device` ON Geofence(device_id)
+- `idx_geofence_active` ON Geofence(is_active)
+- `idx_geofence_event_geofence` ON GeofenceEvent(geofence_id)
+- `idx_geofence_event_device` ON GeofenceEvent(device_id)
+- `idx_geofence_event_timestamp` ON GeofenceEvent(timestamp DESC)
+- `idx_geofence_event_notification` ON GeofenceEvent(notification_sent)
+- `idx_geofence_event_composite` ON GeofenceEvent(device_id, geofence_id, timestamp DESC)
+- `idx_geofence_status_device` ON GeofenceStatus(device_id)
+- `idx_geofence_status_geofence` ON GeofenceStatus(geofence_id)
+- `idx_geofence_status_inside` ON GeofenceStatus(is_inside)
---
@@ -328,6 +399,8 @@ flowchart TD
B -->|Subscribe| C[📡 Next.js MQTT Subscriber]
C -->|Store Locations| D[(🗄️ SQLite Cache
locations.sqlite)]
+ C -->|Check Geofences| L[🎯 Geofence Monitor]
+ L -->|Store Events| K[(💼 SQLite DB
database.sqlite)]
E[🖥️ Browser Client] -->|GET /api/locations
alle 5 Sek| F[📡 Next.js API Route]
@@ -335,10 +408,13 @@ flowchart TD
F -->|JSON Response| E
E -->|Render| G[🗺️ React Leaflet Map]
+ E -->|Render Geofences| M[🔵 Geofence Circles]
H[👤 Admin User] -->|Login| I[🔐 NextAuth.js]
I -->|Authenticated| J[📊 Admin Panel]
- J -->|CRUD Operations| K[(💼 SQLite DB
database.sqlite)]
+ J -->|CRUD Operations| K
+ J -->|Manage Geofences| K
+ K -->|Query Events| J
style A fill:#4CAF50
style B fill:#FF9800
@@ -348,6 +424,8 @@ flowchart TD
style G fill:#8BC34A
style I fill:#E91E63
style K fill:#FFC107
+ style L fill:#9C27B0
+ style M fill:#3F51B5
```
### Komponenten-Übersicht
@@ -361,6 +439,7 @@ graph LR
subgraph "Next.js Application"
C[MQTT Subscriber]
+ I[Geofence Monitor]
D[Frontend
React/Leaflet]
E[API Routes]
F[Auth Layer
NextAuth.js]
@@ -368,12 +447,14 @@ graph LR
subgraph "Data Layer"
G[locations.sqlite
Tracking Data]
- H[database.sqlite
Users & Devices]
+ H[database.sqlite
Users, Devices
MQTT, Geofences]
end
A -->|MQTT| B
B -->|Subscribe| C
- C -->|Write| G
+ C -->|Write Locations| G
+ C -->|Trigger| I
+ I -->|Check & Store Events| H
D -->|HTTP| E
E -->|Read/Write| G
@@ -385,6 +466,7 @@ graph LR
style A fill:#4CAF50,color:#fff
style B fill:#FF9800,color:#fff
style C fill:#2196F3,color:#fff
+ style I fill:#9C27B0,color:#fff
style D fill:#00BCD4,color:#000
style E fill:#00BCD4,color:#000
style F fill:#E91E63,color:#fff
@@ -398,9 +480,15 @@ graph LR
erDiagram
USER ||--o{ USER : "parent_user_id"
USER ||--o{ DEVICE : owns
+ USER ||--o{ GEOFENCE : owns
DEVICE ||--o{ LOCATION : tracks
DEVICE ||--o| MQTT_CREDENTIALS : has
DEVICE ||--o{ MQTT_ACL_RULES : has
+ DEVICE ||--o{ GEOFENCE : monitors
+ GEOFENCE ||--o{ GEOFENCE_EVENT : triggers
+ GEOFENCE ||--o{ GEOFENCE_STATUS : tracks
+ DEVICE ||--o{ GEOFENCE_EVENT : generates
+ DEVICE ||--o{ GEOFENCE_STATUS : has
USER {
string id PK
@@ -450,6 +538,49 @@ erDiagram
datetime updated_at
}
+ GEOFENCE {
+ string id PK
+ string name
+ string description
+ string shape_type
+ float center_latitude
+ float center_longitude
+ int radius_meters
+ string owner_id FK
+ string device_id FK
+ boolean is_active
+ string color
+ datetime created_at
+ datetime updated_at
+ }
+
+ GEOFENCE_EVENT {
+ int id PK
+ string geofence_id FK
+ string device_id FK
+ int location_id
+ string event_type
+ float latitude
+ float longitude
+ float distance_from_center
+ int notification_sent
+ string notification_error
+ datetime timestamp
+ datetime created_at
+ }
+
+ GEOFENCE_STATUS {
+ int id PK
+ string device_id FK
+ string geofence_id FK
+ boolean is_inside
+ datetime last_enter_time
+ datetime last_exit_time
+ datetime last_checked_at
+ datetime created_at
+ datetime updated_at
+ }
+
LOCATION {
int id PK
float latitude