Files
location-mqtt-tracker-app/app/api/geofences/route.ts
Joachim Hummel bd6a7ab187 Add Geofence MVP feature implementation
Implemented complete MVP for geofencing functionality with database,
backend logic, MQTT integration, and API endpoints.

**Phase 1: Database & Core Logic**
- scripts/init-geofence-db.js: Database initialization for Geofence tables
- lib/types.ts: TypeScript types for Geofence, GeofenceEvent, GeofenceStatus
- lib/geofence-engine.ts: Core geofencing logic (Haversine distance, state tracking)
- lib/geofence-db.ts: Database layer with CRUD operations
- package.json: Added db:init:geofence script

**Phase 2: MQTT Integration & Email Notifications**
- emails/geofence-enter.tsx: React Email template for enter events
- emails/geofence-exit.tsx: React Email template for exit events
- lib/email-renderer.ts: Added geofence email rendering functions
- lib/geofence-notifications.ts: Notification service for geofence events
- lib/mqtt-subscriber.ts: Integrated automatic geofence checking on location updates

**Phase 3: Minimal API**
- app/api/geofences/route.ts: GET (list) and POST (create) endpoints
- app/api/geofences/[id]/route.ts: DELETE endpoint
- All endpoints with authentication and ownership checks

**MVP Simplifications:**
- No zone limit enforcement (unlimited for all users)
- No notification flags (always send Enter + Exit emails)
- Device assignment required (no NULL device logic)
- Circular geofences only

**Features:**
 Automatic geofence detection on MQTT location updates
 Email notifications for enter/exit events
 State tracking to prevent duplicate events
 REST API for geofence management
 Non-blocking async processing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 18:14:24 +00:00

121 lines
3.4 KiB
TypeScript

import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { geofenceDb } from "@/lib/geofence-db";
import { v4 as uuidv4 } from 'uuid';
import type { CreateGeofenceInput } from "@/lib/types";
// GET /api/geofences - List all geofences for the authenticated user
export async function GET() {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const userId = (session.user as any).id;
// Get all geofences owned by this user
const geofences = geofenceDb.findByOwner(userId);
return NextResponse.json({
success: true,
geofences,
total: geofences.length,
});
} catch (error) {
console.error("[GET /api/geofences] Error:", error);
return NextResponse.json(
{
error: "Failed to fetch geofences",
details: error instanceof Error ? error.message : "Unknown error",
},
{ status: 500 }
);
}
}
// POST /api/geofences - Create a new geofence
export async function POST(request: Request) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const userId = (session.user as any).id;
const body = await request.json();
// Validate required fields
const { name, center_latitude, center_longitude, radius_meters, device_id, description, color } = body;
if (!name || typeof name !== 'string') {
return NextResponse.json(
{ error: "Name is required and must be a string" },
{ status: 400 }
);
}
if (typeof center_latitude !== 'number' || center_latitude < -90 || center_latitude > 90) {
return NextResponse.json(
{ error: "Invalid center_latitude (must be between -90 and 90)" },
{ status: 400 }
);
}
if (typeof center_longitude !== 'number' || center_longitude < -180 || center_longitude > 180) {
return NextResponse.json(
{ error: "Invalid center_longitude (must be between -180 and 180)" },
{ status: 400 }
);
}
if (typeof radius_meters !== 'number' || radius_meters < 50 || radius_meters > 50000) {
return NextResponse.json(
{ error: "Invalid radius_meters (must be between 50 and 50000)" },
{ status: 400 }
);
}
if (!device_id || typeof device_id !== 'string') {
return NextResponse.json(
{ error: "device_id is required" },
{ status: 400 }
);
}
// Create geofence data
const geofenceData: CreateGeofenceInput = {
id: uuidv4(),
name: name.trim(),
description: description?.trim() || undefined,
center_latitude,
center_longitude,
radius_meters,
owner_id: userId,
device_id,
color: color || '#3b82f6',
};
// Create geofence in database
const geofence = geofenceDb.create(geofenceData);
console.log(`[POST /api/geofences] Created geofence ${geofence.name} for user ${userId}`);
return NextResponse.json({
success: true,
geofence,
}, { status: 201 });
} catch (error) {
console.error("[POST /api/geofences] Error:", error);
return NextResponse.json(
{
error: "Failed to create geofence",
details: error instanceof Error ? error.message : "Unknown error",
},
{ status: 500 }
);
}
}