Initial implementation: Logbuch Sternwarte Welzheim
Vollständige Next.js 16 Webanwendung als Logbuch für die Sternwarte Welzheim. 4 Kuppeln (West/Ost/Süd/Pluto), BEO-basierte Authentifizierung mit erzwungenem Passwort-Wechsel beim Erstlogin, MySQL-Backend, Docker-Deployment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
12
app/api/beos/route.ts
Normal file
12
app/api/beos/route.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { query } from '@/lib/db';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const rows = await query('SELECT ID, Kuerzel, Name FROM beos ORDER BY Name ASC');
|
||||
return NextResponse.json(rows);
|
||||
} catch (error) {
|
||||
console.error('GET /api/beos:', error);
|
||||
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
79
app/api/logbuch/[id]/route.ts
Normal file
79
app/api/logbuch/[id]/route.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { query, getPool } from '@/lib/db';
|
||||
import { getSession } from '@/lib/session';
|
||||
import type { SelectedObjekt } from '@/types/logbuch';
|
||||
|
||||
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await getSession();
|
||||
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
|
||||
|
||||
const { id } = await params;
|
||||
const logbuchId = parseInt(id);
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { Kuppel, ArtFuehrung, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
|
||||
|
||||
await getPool().execute(
|
||||
`UPDATE logbuch SET Kuppel=?, ArtFuehrung=?, Beginn=?, Ende=?, Besucher=?,
|
||||
Bemerkungen=?, WetterTemp=?, WetterFeuchte=?, WetterDruck=?
|
||||
WHERE ID=?`,
|
||||
[
|
||||
Kuppel, ArtFuehrung, 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 FROM objekte WHERE Name = ?', [obj.Name]) as { ID: number }[];
|
||||
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, ObjektName) VALUES (?, ?, ?)',
|
||||
[logbuchId, objektId, obj.Name]
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (error) {
|
||||
console.error('PUT /api/logbuch/[id]:', error);
|
||||
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await getSession();
|
||||
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
|
||||
|
||||
const { id } = await params;
|
||||
|
||||
try {
|
||||
await query('DELETE FROM logbuch WHERE ID = ?', [parseInt(id)]);
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (error) {
|
||||
console.error('DELETE /api/logbuch/[id]:', error);
|
||||
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
92
app/api/logbuch/route.ts
Normal file
92
app/api/logbuch/route.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { query } from '@/lib/db';
|
||||
import { getSession } from '@/lib/session';
|
||||
import type { SelectedObjekt } from '@/types/logbuch';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const kuppel = searchParams.get('kuppel') || 'West';
|
||||
const limit = Math.min(parseInt(searchParams.get('limit') || '20'), 100);
|
||||
|
||||
try {
|
||||
const rows = await query(
|
||||
`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.WetterTemp, l.WetterFeuchte, l.WetterDruck,
|
||||
l.created_by, l.created_at,
|
||||
GROUP_CONCAT(DISTINCT b.Kuerzel ORDER BY b.Kuerzel SEPARATOR ', ') AS BEOs,
|
||||
GROUP_CONCAT(DISTINCT lo.ObjektName ORDER BY lo.ObjektName SEPARATOR ', ') AS Objekte
|
||||
FROM logbuch l
|
||||
LEFT JOIN logbuch_beos lb ON lb.LogbuchID = l.ID
|
||||
LEFT JOIN beos b ON b.ID = lb.BeoID
|
||||
LEFT JOIN logbuch_objekte lo ON lo.LogbuchID = l.ID
|
||||
WHERE l.Kuppel = ?
|
||||
GROUP BY l.ID
|
||||
ORDER BY l.Beginn DESC
|
||||
LIMIT ?`,
|
||||
[kuppel, limit]
|
||||
);
|
||||
return NextResponse.json(rows);
|
||||
} catch (error) {
|
||||
console.error('GET /api/logbuch:', error);
|
||||
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const session = await getSession();
|
||||
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { Kuppel, ArtFuehrung, Beginn, Ende, Besucher, beoIds, objekte, Bemerkungen, Wetter } = body;
|
||||
|
||||
const [result] = await (await import('@/lib/db')).getPool().execute(
|
||||
`INSERT INTO logbuch (Kuppel, ArtFuehrung, Beginn, Ende, Besucher, Bemerkungen, WetterTemp, WetterFeuchte, WetterDruck, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
Kuppel, ArtFuehrung, 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 FROM objekte WHERE Name = ?', [obj.Name]) as { ID: number }[];
|
||||
if (existing[0]) {
|
||||
objektId = existing[0].ID;
|
||||
} else {
|
||||
const [ins] = await (await import('@/lib/db')).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, ObjektName) VALUES (?, ?, ?)',
|
||||
[logbuchId, objektId, obj.Name]
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ id: logbuchId }, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('POST /api/logbuch:', error);
|
||||
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
7
app/api/logout/route.ts
Normal file
7
app/api/logout/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { deleteSession } from '@/lib/session';
|
||||
|
||||
export async function POST() {
|
||||
await deleteSession();
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
12
app/api/objekte/route.ts
Normal file
12
app/api/objekte/route.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { query } from '@/lib/db';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const rows = await query('SELECT ID, Name FROM objekte ORDER BY LastUsed DESC LIMIT 100');
|
||||
return NextResponse.json(rows);
|
||||
} catch (error) {
|
||||
console.error('GET /api/objekte:', error);
|
||||
return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
8
app/api/wetter/route.ts
Normal file
8
app/api/wetter/route.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
const temp = Math.round((8 + Math.random() * 15) * 10) / 10;
|
||||
const feuchte = Math.round((40 + Math.random() * 50) * 10) / 10;
|
||||
const druck = Math.round((990 + Math.random() * 30) * 10) / 10;
|
||||
return NextResponse.json({ temp, feuchte, druck });
|
||||
}
|
||||
Reference in New Issue
Block a user