Edit files

This commit is contained in:
2025-11-24 20:33:15 +00:00
parent 843e93a274
commit b1190e2e50
14 changed files with 846 additions and 1207 deletions

143
app/api/export/csv/route.ts Normal file
View File

@@ -0,0 +1,143 @@
import { NextRequest, NextResponse } from "next/server";
import { locationDb, userDb } from "@/lib/db";
import { auth } from "@/lib/auth";
import { calculateDistance, RateLimitedGeocoder } from "@/lib/geo-utils";
/**
* GET /api/export/csv
*
* Export location data as CSV for Lexware digital logbook
*
* Query parameters:
* - username: Filter by device tracker ID
* - startTime: Custom range start (ISO string)
* - endTime: Custom range end (ISO string)
* - includeGeocoding: Whether to include addresses (default: false for preview)
*/
export async function GET(request: NextRequest) {
try {
// Check authentication
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Get user's allowed device IDs
const userId = (session.user as any).id;
const role = (session.user as any).role;
const sessionUsername = session.user.name || '';
const userDeviceIds = userDb.getAllowedDeviceIds(userId, role, sessionUsername);
if (userDeviceIds.length === 0) {
return new NextResponse("Keine Geräte verfügbar", { status: 403 });
}
const searchParams = request.nextUrl.searchParams;
const username = searchParams.get('username') || undefined;
const startTime = searchParams.get('startTime') || undefined;
const endTime = searchParams.get('endTime') || undefined;
const includeGeocoding = searchParams.get('includeGeocoding') === 'true';
// Fetch locations from database
let locations = locationDb.findMany({
user_id: 0,
username,
startTime,
endTime,
limit: 10000, // High limit for exports
});
// Filter by user's allowed devices
locations = locations.filter(loc => loc.username && userDeviceIds.includes(loc.username));
if (locations.length === 0) {
return new NextResponse("Keine Daten im gewählten Zeitraum", { status: 404 });
}
// Sort chronologically (oldest first) for proper distance calculation
locations.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
// Initialize geocoder if needed
const geocoder = includeGeocoding ? new RateLimitedGeocoder() : null;
// Build CSV
const csvRows: string[] = [];
// Header
csvRows.push('Datum,Uhrzeit,Latitude,Longitude,Adresse,Distanz (km),Geschwindigkeit (km/h),Gerät');
// Process each location
for (let i = 0; i < locations.length; i++) {
const loc = locations[i];
const lat = Number(loc.latitude);
const lon = Number(loc.longitude);
// Calculate distance from previous point
let distance = 0;
if (i > 0) {
const prevLoc = locations[i - 1];
distance = calculateDistance(
Number(prevLoc.latitude),
Number(prevLoc.longitude),
lat,
lon
);
}
// Geocode if requested
let address = `${lat.toFixed(6)}, ${lon.toFixed(6)}`;
if (geocoder) {
address = await geocoder.geocode(lat, lon);
}
// Format timestamp (German format)
const date = new Date(loc.timestamp);
const dateStr = date.toLocaleDateString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
const timeStr = date.toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
// Speed (may be null)
const speed = loc.speed != null ? Number(loc.speed).toFixed(1) : '';
// Device name
const deviceName = loc.username || 'Unbekannt';
// Build CSV row - properly escape address field
const escapedAddress = address.includes(',') ? `"${address}"` : address;
const distanceStr = distance.toFixed(3).replace('.', ','); // German decimal separator
csvRows.push(
`${dateStr},${timeStr},${lat.toFixed(6)},${lon.toFixed(6)},${escapedAddress},${distanceStr},${speed},${deviceName}`
);
}
const csv = csvRows.join('\n');
// Return CSV with proper headers
const filename = `fahrtenbuch_${startTime ? new Date(startTime).toISOString().split('T')[0] : 'alle'}_${endTime ? new Date(endTime).toISOString().split('T')[0] : 'alle'}.csv`;
return new NextResponse(csv, {
headers: {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': `attachment; filename="${filename}"`,
},
});
} catch (error) {
console.error("Error exporting CSV:", error);
return NextResponse.json(
{
error: "Failed to export CSV",
details: error instanceof Error ? error.message : "Unknown error"
},
{ status: 500 }
);
}
}