feat: Version 1.10.0 — DB-Zugriff auf PHP-Bridge (DB4js_all.php) umgestellt

- lib/db.ts entfernt, mysql2-Abhängigkeit gestrichen
- lib/phpdb.ts: HTTP-Client für alle DB-Operationen via DB4js_all.php
- Alle API-Routen und Server Actions auf phpdb.ts umgestellt
- compose.yml / docker-compose.prod.yml: MySQL/phpMyAdmin-Container entfernt
- app/api/DB4js_all.php/route.ts: Proxy für Statistik-AJAX-Calls
- Statistik-Grafik liest ab 2026 live aus logbuch statt StatistikJahre
- PHP 7.3-Kompatibilität: str_contains → strpos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 08:48:15 +02:00
parent c3f0b8f1e0
commit a75303f857
18 changed files with 291 additions and 532 deletions
+8 -26
View File
@@ -2,7 +2,9 @@
import { redirect } from 'next/navigation';
import { getSession } from '@/lib/session';
import { query } from '@/lib/db';
import * as phpdb from '@/lib/phpdb';
export type { BeoUser } from '@/lib/phpdb';
export interface ObjektRow {
ID: number;
@@ -13,28 +15,13 @@ export interface ObjektRow {
export async function listObjekte(): Promise<ObjektRow[]> {
const session = await getSession();
if (!session || !session.role?.includes('admin')) redirect('/');
const rows = await query('SELECT ID, Name, LastUsed FROM objekte ORDER BY Name ASC', []);
return rows as ObjektRow[];
return phpdb.listObjekteAdmin();
}
export interface BeoUser {
id: number;
kürzel: string | null;
name: string;
vorname: string | null;
role: string | null;
hasPw: boolean;
}
export async function listUsers(): Promise<BeoUser[]> {
export async function listUsers(): Promise<phpdb.BeoUser[]> {
const session = await getSession();
if (!session || !session.role?.includes('admin')) redirect('/');
const rows = await query(
'SELECT id, `kürzel`, name, vorname, role, (pw IS NOT NULL) AS hasPw FROM beos ORDER BY name, vorname',
[]
) as (Omit<BeoUser, 'hasPw'> & { hasPw: number })[];
return rows.map(r => ({ ...r, hasPw: r.hasPw === 1 }));
return phpdb.listUsers();
}
export async function resetPassword(
@@ -46,16 +33,11 @@ export async function resetPassword(
return { error: 'Keine Berechtigung.' };
}
const idRaw = formData.get('id');
const id = Number(idRaw);
const id = Number(formData.get('id'));
if (!id || isNaN(id)) {
return { error: 'Ungültige Benutzer-ID.' };
}
await query(
'UPDATE beos SET pw = NULL, MustChangePassword = 1 WHERE id = ?',
[id]
);
await phpdb.resetBeoPassword(id);
return { success: 'Passwort wurde zurückgesetzt. Der Benutzer muss sich mit dem Standard-Passwort anmelden und es dann ändern.' };
}
+24
View File
@@ -0,0 +1,24 @@
import { NextResponse } from 'next/server';
const DB_URL = (process.env.PHP_DB_URL ?? 'http://localhost:8080/DB4js_all.php')
.replace(/\?.*$/, '');
async function proxy(req: Request) {
const search = new URL(req.url).search;
const target = DB_URL + search;
const isReadOnly = req.method === 'GET' || req.method === 'HEAD';
const upstream = await fetch(target, {
method: req.method,
headers: { 'content-type': req.headers.get('content-type') ?? 'application/x-www-form-urlencoded' },
body: isReadOnly ? undefined : await req.arrayBuffer(),
cache: 'no-store',
});
const body = await upstream.arrayBuffer();
return new NextResponse(Buffer.from(body), {
status: upstream.status,
headers: { 'content-type': upstream.headers.get('content-type') ?? 'application/json' },
});
}
export const GET = proxy;
export const POST = proxy;
+2 -4
View File
@@ -1,14 +1,12 @@
import { NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { getSession } from '@/lib/session';
import * as phpdb from '@/lib/phpdb';
export async function GET() {
const session = await getSession();
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
try {
const rows = await query(
'SELECT id AS ID, `kürzel` AS Kuerzel, CONCAT(IFNULL(vorname, \'\'), IF(vorname IS NOT NULL, \' \', \'\'), name) AS Name FROM beos WHERE `kürzel` IS NOT NULL AND FIND_IN_SET(\'guide\', role) > 0 ORDER BY name ASC'
) as { ID: number; Kuerzel: string; Name: string }[];
const rows = await phpdb.getBeos();
return NextResponse.json(rows);
} catch (error) {
console.error('GET /api/beos:', error);
+3 -21
View File
@@ -1,15 +1,8 @@
import { NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { getSession } from '@/lib/session';
import * as phpdb from '@/lib/phpdb';
export interface FahrkostenRow {
ID: number;
Kuerzel: string;
Name: string;
Anzahl: number;
}
const EXCLUDED = "'PrF','Beob','BEOS','TD','ToT'";
export type { FahrkostenRow } from '@/lib/phpdb';
export async function GET(req: Request) {
const session = await getSession();
@@ -22,18 +15,7 @@ export async function GET(req: Request) {
}
try {
const rows = await query(
'SELECT b.id AS ID, b.`kürzel` AS Kuerzel,' +
' CONCAT(IFNULL(b.vorname, \'\'), IF(b.vorname IS NOT NULL, \' \', \'\'), b.name) AS Name,' +
' COUNT(DISTINCT l.ID) AS Anzahl' +
' FROM beos b' +
' JOIN logbuch_beos lb ON lb.BeoID = b.id' +
' JOIN logbuch l ON l.ID = lb.LogbuchID' +
' WHERE l.Beginn >= ? AND l.ArtFuehrung NOT IN (' + EXCLUDED + ')' +
' GROUP BY b.id, b.`kürzel`, b.name, b.vorname' +
' ORDER BY b.name ASC',
[ab + ' 00:00:00']
) as FahrkostenRow[];
const rows = await phpdb.getFahrkosten(ab);
return NextResponse.json(rows);
} catch (error) {
console.error('GET /api/fahrkosten:', error);
+20 -71
View File
@@ -1,8 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { query, getPool } from '@/lib/db';
import { getSession } from '@/lib/session';
import { triggerBackup } from '@/lib/backup';
import type { SelectedObjekt } from '@/types/logbuch';
import * as phpdb from '@/lib/phpdb';
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession();
@@ -12,66 +11,25 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
const logbuchId = parseInt(id);
try {
const existingRows = await query('SELECT ID FROM logbuch WHERE ID = ?', [logbuchId]) as { ID: number }[];
if (existingRows.length === 0) {
return NextResponse.json({ error: 'Eintrag nicht gefunden' }, { status: 404 });
}
const isAdmin = session.role?.includes('admin');
const beoRows = await query('SELECT COUNT(*) AS cnt FROM logbuch_beos WHERE LogbuchID = ? AND BeoID = ?', [logbuchId, session.beoId]) as { cnt: number }[];
const isBeo = (beoRows[0]?.cnt ?? 0) > 0;
if (!isAdmin && !isBeo) {
return NextResponse.json({ error: 'Keine Berechtigung zum Ändern dieses Eintrags' }, { status: 403 });
}
const body = await request.json();
const { Kuppel, ArtFuehrung, SonderName, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
await getPool().execute(
'UPDATE logbuch SET Kuppel=?, ArtFuehrung=?, SonderName=?, Beginn=?, Ende=?, Besucher=?,' +
' Bemerkungen=?, WetterTemp=?, WetterFeuchte=?, WetterDruck=? WHERE ID=?',
[
Kuppel, ArtFuehrung, SonderName || null, Beginn, Ende,
Besucher ?? 0,
Bemerkungen?.slice(0, 500) || null,
Wetter?.temp ?? null,
Wetter?.feuchte ?? null,
Wetter?.druck ?? null,
logbuchId,
]
);
await query('DELETE FROM logbuch_beos WHERE LogbuchID = ?', [logbuchId]);
await query('DELETE FROM logbuch_objekte WHERE LogbuchID = ?', [logbuchId]);
for (const beoId of (beoIds as number[]) || []) {
await query('INSERT INTO logbuch_beos (LogbuchID, BeoID) VALUES (?, ?)', [logbuchId, beoId]);
}
for (const obj of (objekte as SelectedObjekt[]) || []) {
let objektId = obj.ID;
if (!objektId) {
const existing = await query('SELECT ID, Name FROM objekte WHERE LOWER(Name) = LOWER(?)', [obj.Name]) as { ID: number; Name: string }[];
if (existing[0]) {
objektId = existing[0].ID;
} else {
const [ins] = await getPool().execute(
'INSERT INTO objekte (Name) VALUES (?)', [obj.Name]
) as [{ insertId: number }, unknown];
objektId = ins.insertId;
}
}
await query('UPDATE objekte SET LastUsed = NOW() WHERE ID = ?', [objektId]);
await query(
'INSERT INTO logbuch_objekte (LogbuchID, ObjektID) VALUES (?, ?)',
[logbuchId, objektId]
);
}
await phpdb.updateLogbuch(logbuchId, session.beoId, session.role ?? '', {
Kuppel, ArtFuehrung, SonderName, Beginn, Ende,
Besucher: Besucher ?? 0,
beoIds: beoIds ?? [],
objekte: objekte ?? [],
Bemerkungen: Bemerkungen ?? null,
Wetter: Wetter ?? null,
});
triggerBackup();
return NextResponse.json({ ok: true });
} catch (error) {
} catch (error: unknown) {
if (error instanceof Error) {
if (error.message.includes('404')) return NextResponse.json({ error: 'Eintrag nicht gefunden' }, { status: 404 });
if (error.message.includes('403')) return NextResponse.json({ error: 'Keine Berechtigung' }, { status: 403 });
}
console.error('PUT /api/logbuch/[id]:', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
}
@@ -85,22 +43,13 @@ export async function DELETE(_request: NextRequest, { params }: { params: Promis
const logbuchId = parseInt(id);
try {
const existingRows = await query('SELECT ID FROM logbuch WHERE ID = ?', [logbuchId]) as { ID: number }[];
if (existingRows.length === 0) {
return NextResponse.json({ error: 'Eintrag nicht gefunden' }, { status: 404 });
}
const isAdmin = session.role?.includes('admin');
const beoRows = await query('SELECT COUNT(*) AS cnt FROM logbuch_beos WHERE LogbuchID = ? AND BeoID = ?', [logbuchId, session.beoId]) as { cnt: number }[];
const isBeo = (beoRows[0]?.cnt ?? 0) > 0;
if (!isAdmin && !isBeo) {
return NextResponse.json({ error: 'Keine Berechtigung zum Löschen dieses Eintrags' }, { status: 403 });
}
await query('DELETE FROM logbuch WHERE ID = ?', [logbuchId]);
await phpdb.deleteLogbuch(logbuchId, session.beoId, session.role ?? '');
return NextResponse.json({ ok: true });
} catch (error) {
} catch (error: unknown) {
if (error instanceof Error) {
if (error.message.includes('404')) return NextResponse.json({ error: 'Eintrag nicht gefunden' }, { status: 404 });
if (error.message.includes('403')) return NextResponse.json({ error: 'Keine Berechtigung' }, { status: 403 });
}
console.error('DELETE /api/logbuch/[id]:', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
}
+18 -114
View File
@@ -1,90 +1,23 @@
import { NextRequest, NextResponse } from 'next/server';
import { query, getPool } from '@/lib/db';
import { getSession } from '@/lib/session';
import { triggerBackup } from '@/lib/backup';
import type { SelectedObjekt } from '@/types/logbuch';
const LIST_SQL =
'SELECT' +
' l.ID, l.Kuppel, l.ArtFuehrung,' +
" DATE_FORMAT(l.Beginn, '%Y-%m-%dT%H:%i') AS Beginn," +
" DATE_FORMAT(l.Ende, '%Y-%m-%dT%H:%i') AS Ende," +
' l.Besucher, l.Bemerkungen, l.SonderName,' +
' l.WetterTemp, l.WetterFeuchte, l.WetterDruck,' +
' l.created_by, l.created_at,' +
' creator.kuerzel AS created_by_kuerzel,' +
" GROUP_CONCAT(DISTINCT bk.kuerzel ORDER BY bk.kuerzel SEPARATOR ', ') AS BEOs," +
" GROUP_CONCAT(DISTINCT o.Name ORDER BY o.Name SEPARATOR ', ') AS Objekte" +
' FROM logbuch l' +
' LEFT JOIN (SELECT id, `kürzel` AS kuerzel FROM beos) creator ON creator.id = l.created_by' +
' LEFT JOIN logbuch_beos lb ON lb.LogbuchID = l.ID' +
' LEFT JOIN (SELECT id, `kürzel` AS kuerzel FROM beos) bk ON bk.id = lb.BeoID' +
' LEFT JOIN logbuch_objekte lo ON lo.LogbuchID = l.ID' +
' LEFT JOIN objekte o ON o.ID = lo.ObjektID' +
' WHERE l.Kuppel = ?' +
' GROUP BY l.ID';
import * as phpdb from '@/lib/phpdb';
export async function GET(request: NextRequest) {
const session = await getSession();
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
const { searchParams } = new URL(request.url);
const kuppel = searchParams.get('kuppel') || 'West';
const limit = Math.min(parseInt(searchParams.get('limit') || '10') || 10, 500);
const limit = Math.min(parseInt(searchParams.get('limit') || '10') || 10, 500);
const offset = Math.max(0, parseInt(searchParams.get('offset') || '0') || 0);
const month = searchParams.get('month') || '';
const order = searchParams.get('order') === 'asc' ? 'ASC' : 'DESC';
const month = searchParams.get('month') || '';
const order = searchParams.get('order') === 'asc' ? 'asc' : 'desc';
const search = (searchParams.get('search') || '').trim();
if (search) {
const pattern = '%' + search + '%';
const searchParams2 = [kuppel, pattern, pattern, pattern];
const countSQL =
'SELECT COUNT(*) AS total FROM (' +
'SELECT l.ID FROM logbuch l' +
' LEFT JOIN logbuch_beos lb ON lb.LogbuchID = l.ID' +
' LEFT JOIN (SELECT id, `kürzel` AS kuerzel FROM beos) bk ON bk.id = lb.BeoID' +
' LEFT JOIN logbuch_objekte lo ON lo.LogbuchID = l.ID' +
' LEFT JOIN objekte o ON o.ID = lo.ObjektID' +
' WHERE l.Kuppel = ?' +
' GROUP BY l.ID' +
" HAVING (MAX(l.Bemerkungen) LIKE ? OR GROUP_CONCAT(DISTINCT bk.kuerzel ORDER BY bk.kuerzel SEPARATOR ', ') LIKE ? OR GROUP_CONCAT(DISTINCT o.Name ORDER BY o.Name SEPARATOR ', ') LIKE ?)" +
') AS sub';
const listSQL = LIST_SQL +
' HAVING (MAX(l.Bemerkungen) LIKE ? OR BEOs LIKE ? OR Objekte LIKE ?)' +
` ORDER BY l.Beginn DESC LIMIT ${limit} OFFSET ${offset}`;
try {
const [countRows, entries] = await Promise.all([
query(countSQL, searchParams2) as Promise<{ total: number }[]>,
query(listSQL, searchParams2),
]);
return NextResponse.json({ entries, total: (countRows as unknown as { total: number }[])[0]?.total ?? 0 });
} catch (error) {
console.error('GET /api/logbuch (search):', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
}
}
let listWhere = 'WHERE l.Kuppel = ?';
let countWhere = 'WHERE Kuppel = ?';
let params: (string | number | null)[] = [kuppel];
if (month && /^\d{4}-\d{2}$/.test(month)) {
const [y, m] = month.split('-').map(Number);
const start = `${y}-${String(m).padStart(2, '0')}-01`;
const nextM = m === 12 ? 1 : m + 1;
const nextY = m === 12 ? y + 1 : y;
const end = `${nextY}-${String(nextM).padStart(2, '0')}-01`;
listWhere += ' AND l.Beginn >= ? AND l.Beginn < ?';
countWhere += ' AND Beginn >= ? AND Beginn < ?';
params = [kuppel, start, end];
}
try {
const [countRows, entries] = await Promise.all([
query('SELECT COUNT(*) AS total FROM logbuch ' + countWhere, params) as Promise<{ total: number }[]>,
query(LIST_SQL.replace('WHERE l.Kuppel = ?', listWhere) + ` ORDER BY l.Beginn ${order} LIMIT ${limit} OFFSET ${offset}`, params),
]);
return NextResponse.json({ entries, total: (countRows as unknown as { total: number }[])[0]?.total ?? 0 });
const result = await phpdb.listLogbuch({ kuppel, limit, offset, month, search, order });
return NextResponse.json(result);
} catch (error) {
console.error('GET /api/logbuch:', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
@@ -99,47 +32,18 @@ export async function POST(request: NextRequest) {
const body = await request.json();
const { Kuppel, ArtFuehrung, SonderName, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
const pool = getPool();
const [result] = await pool.execute(
'INSERT INTO logbuch (Kuppel, ArtFuehrung, SonderName, Beginn, Ende, Besucher, Bemerkungen, WetterTemp, WetterFeuchte, WetterDruck, created_by)' +
' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[
Kuppel, ArtFuehrung, SonderName || null, Beginn, Ende,
Besucher ?? 0,
Bemerkungen?.slice(0, 500) || null,
Wetter?.temp ?? null,
Wetter?.feuchte ?? null,
Wetter?.druck ?? null,
session.beoId,
]
) as [{ insertId: number }, unknown];
const logbuchId = result.insertId;
for (const beoId of (beoIds as number[]) || []) {
await query('INSERT INTO logbuch_beos (LogbuchID, BeoID) VALUES (?, ?)', [logbuchId, beoId]);
}
for (const obj of (objekte as SelectedObjekt[]) || []) {
let objektId = obj.ID;
if (!objektId) {
const existing = await query('SELECT ID, Name FROM objekte WHERE LOWER(Name) = LOWER(?)', [obj.Name]) as { ID: number; Name: string }[];
if (existing[0]) {
objektId = existing[0].ID;
} else {
const [ins] = await pool.execute('INSERT INTO objekte (Name) VALUES (?)', [obj.Name]) as [{ insertId: number }, unknown];
objektId = ins.insertId;
}
}
await query('UPDATE objekte SET LastUsed = NOW() WHERE ID = ?', [objektId]);
await query(
'INSERT INTO logbuch_objekte (LogbuchID, ObjektID) VALUES (?, ?)',
[logbuchId, objektId]
);
}
const result = await phpdb.createLogbuch({
Kuppel, ArtFuehrung, SonderName, Beginn, Ende,
Besucher: Besucher ?? 0,
beoIds: beoIds ?? [],
objekte: objekte ?? [],
Bemerkungen: Bemerkungen ?? null,
Wetter: Wetter ?? null,
created_by: session.beoId,
});
triggerBackup();
return NextResponse.json({ id: logbuchId }, { status: 201 });
return NextResponse.json(result, { status: 201 });
} catch (error) {
console.error('POST /api/logbuch:', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
+4 -4
View File
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { getSession } from '@/lib/session';
import * as phpdb from '@/lib/phpdb';
export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession();
@@ -13,8 +13,8 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
const { name } = await req.json();
const trimmed = (name as string)?.trim();
if (!trimmed) return NextResponse.json({ error: 'Name darf nicht leer sein' }, { status: 400 });
await query('UPDATE objekte SET Name = ? WHERE ID = ?', [trimmed, numId]);
return NextResponse.json({ ID: numId, Name: trimmed });
const result = await phpdb.updateObjekt(numId, trimmed);
return NextResponse.json(result);
} catch (error) {
console.error('PUT /api/objekte/[id]:', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
@@ -29,7 +29,7 @@ export async function DELETE(_req: NextRequest, { params }: { params: Promise<{
const { id } = await params;
const numId = Number(id);
if (isNaN(numId)) return NextResponse.json({ error: 'Ungültige ID' }, { status: 400 });
await query('DELETE FROM objekte WHERE ID = ?', [numId]);
await phpdb.deleteObjekt(numId);
return NextResponse.json({ ok: true });
} catch (error) {
console.error('DELETE /api/objekte/[id]:', error);
+4 -4
View File
@@ -1,12 +1,12 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { getSession } from '@/lib/session';
import * as phpdb from '@/lib/phpdb';
export async function GET() {
const session = await getSession();
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
try {
const rows = await query('SELECT ID, Name FROM objekte ORDER BY LastUsed DESC LIMIT 100');
const rows = await phpdb.getObjekte();
return NextResponse.json(rows);
} catch (error) {
console.error('GET /api/objekte:', error);
@@ -22,8 +22,8 @@ export async function POST(req: NextRequest) {
const { name } = await req.json();
const trimmed = (name as string)?.trim();
if (!trimmed) return NextResponse.json({ error: 'Name darf nicht leer sein' }, { status: 400 });
const result = await query('INSERT INTO objekte (Name) VALUES (?)', [trimmed]) as { insertId: number };
return NextResponse.json({ ID: result.insertId, Name: trimmed }, { status: 201 });
const result = await phpdb.createObjekt(trimmed);
return NextResponse.json(result, { status: 201 });
} catch (error) {
console.error('POST /api/objekte:', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
+3 -61
View File
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { getSession } from '@/lib/session';
import * as phpdb from '@/lib/phpdb';
export async function GET(request: NextRequest) {
const session = await getSession();
@@ -10,66 +10,8 @@ export async function GET(request: NextRequest) {
const year = parseInt(searchParams.get('year') || String(new Date().getFullYear()), 10);
try {
const monthlyRows = await query(
'SELECT' +
' MONTH(Beginn) AS monat,' +
" COUNT(CASE WHEN ArtFuehrung IN ('RF','SF','SonF','PrF') THEN 1 END) AS tageFuehrungen," +
" COUNT(CASE WHEN ArtFuehrung = 'Beob' THEN 1 END) AS tageBeob," +
" COUNT(CASE WHEN ArtFuehrung = 'TD' THEN 1 END) AS tageTD," +
" COUNT(CASE WHEN ArtFuehrung = 'Sonst' THEN 1 END) AS tageSonst," +
" COUNT(CASE WHEN ArtFuehrung = 'BEOS' THEN 1 END) AS tageBEOS," +
" COUNT(CASE WHEN ArtFuehrung = 'ToT' THEN 1 END) AS tagesToT," +
" COUNT(CASE WHEN ArtFuehrung IN ('RF','SF','SonF','PrF','Beob','TD','Sonst','BEOS','ToT') THEN 1 END) AS tageGesamt," +
" SUM(CASE WHEN ArtFuehrung = 'RF' THEN Besucher ELSE 0 END) AS besucherRF," +
" SUM(CASE WHEN ArtFuehrung = 'SF' THEN Besucher ELSE 0 END) AS besucherSF," +
" SUM(CASE WHEN ArtFuehrung = 'SonF' THEN Besucher ELSE 0 END) AS besucherSonF," +
" SUM(CASE WHEN ArtFuehrung = 'PrF' THEN Besucher ELSE 0 END) AS besucherPrF," +
" SUM(CASE WHEN ArtFuehrung = 'ToT' THEN Besucher ELSE 0 END) AS besucherToT," +
" SUM(CASE WHEN ArtFuehrung IN ('RF','SF','SonF','PrF','ToT') THEN Besucher ELSE 0 END) AS besucherGesamt" +
' FROM logbuch' +
' WHERE YEAR(Beginn) = ?' +
' GROUP BY MONTH(Beginn)' +
' ORDER BY monat',
[year]
) as {
monat: number;
tageFuehrungen: number; tageBeob: number; tageTD: number; tageSonst: number; tageBEOS: number; tagesToT: number; tageGesamt: number;
besucherRF: number; besucherSF: number; besucherSonF: number; besucherPrF: number;
besucherToT: number; besucherGesamt: number;
}[];
const cumulativeRows = await query(
"SELECT SUM(CASE WHEN ArtFuehrung IN ('RF','SF','SonF','PrF','ToT') THEN Besucher ELSE 0 END) AS total" +
' FROM logbuch WHERE YEAR(Beginn) = ?',
[year]
) as { total: number | null }[];
const tageRows = await query(
"SELECT COUNT(*) AS tage FROM logbuch WHERE YEAR(Beginn) = ? AND ArtFuehrung IN ('RF','SF','SonF','PrF','Beob','TD','Sonst','BEOS','ToT')",
[year]
) as { tage: number }[];
return NextResponse.json({
monthly: monthlyRows.map((r) => ({
monat: Number(r.monat),
tageFuehrungen: Number(r.tageFuehrungen),
tageBeob: Number(r.tageBeob),
tageTD: Number(r.tageTD),
tageSonst: Number(r.tageSonst),
tageBEOS: Number(r.tageBEOS),
tagesToT: Number(r.tagesToT),
tageGesamt: Number(r.tageGesamt),
besucherRF: Number(r.besucherRF),
besucherSF: Number(r.besucherSF),
besucherSonF: Number(r.besucherSonF),
besucherPrF: Number(r.besucherPrF),
besucherToT: Number(r.besucherToT),
besucherGesamt: Number(r.besucherGesamt),
})),
cumulative: Number(cumulativeRows[0]?.total ?? 0),
tage: Number(tageRows[0]?.tage ?? 0),
year,
});
const result = await phpdb.getStatistik(year);
return NextResponse.json(result);
} catch (error) {
console.error('GET /api/statistik:', error);
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
+2 -5
View File
@@ -3,7 +3,7 @@
import { redirect } from 'next/navigation';
import { getSession, createSession } from '@/lib/session';
import { hashPassword } from '@/lib/auth';
import { query } from '@/lib/db';
import { updateBeoPassword } from '@/lib/phpdb';
export async function changePassword(
_prevState: { error: string } | undefined,
@@ -28,10 +28,7 @@ export async function changePassword(
}
const hashed = await hashPassword(newPassword);
await query(
'UPDATE beos SET pw = ?, MustChangePassword = 0 WHERE id = ?',
[hashed, session.beoId]
);
await updateBeoPassword(session.beoId, hashed);
await createSession({
kuerzel: session.kuerzel,