Files
gott/Endpoints/DatabaseExplorerEndpoint.cs
Meleki 229e6d9e53 DB Explorer: Standardmäßig zur letzten Seite springen
- Backend: page=-1 wird als 'letzte Seite' interpretiert (COUNT + Berechnung)
- Frontend: Tabellen-Klick ruft page=-1 auf statt page=1
- Aufgelöster page-Wert aus API-Response für korrekte Anzeige (34/34 statt -1/34)
2026-03-15 10:20:59 +01:00

127 lines
4.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Dapper;
using Gott.Extensions;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Data.Sqlite;
namespace Gott.Endpoints;
public static class DatabaseExplorerEndpoint
{
public static void MapDatabaseExplorerEndpoints(this WebApplication app)
{
app.MapGet("/api/admin/db/tables", HandleGetTables);
app.MapGet("/api/admin/db/tables/{name}", HandleGetTableData);
}
private static async Task HandleGetTables(HttpContext ctx, DatabaseService db)
{
if (!ctx.IsAdmin())
{
ctx.Response.StatusCode = 403;
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
return;
}
using var conn = db.GetConnection();
var tables = conn.Query<TableInfo>(
"SELECT name, (SELECT COUNT(*) FROM pragma_table_info(m.name)) as column_count " +
"FROM sqlite_master m WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name");
var result = new List<object>();
foreach (var t in tables)
{
var rowCount = conn.ExecuteScalar<long>($"SELECT COUNT(*) FROM \"{t.Name}\"");
result.Add(new { name = t.Name, columns = t.ColumnCount, rows = rowCount });
}
await ctx.Response.WriteAsJsonAsync(new { tables = result });
}
private static async Task HandleGetTableData(HttpContext ctx, DatabaseService db, string name)
{
if (!ctx.IsAdmin())
{
ctx.Response.StatusCode = 403;
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
return;
}
// Tabellennamen validieren (SQL-Injection verhindern)
using var conn = db.GetConnection();
var exists = conn.ExecuteScalar<int>(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = @name",
new { name });
if (exists == 0)
{
ctx.Response.StatusCode = 404;
await ctx.Response.WriteAsJsonAsync(new { error = "Table not found" });
return;
}
// Pagination
var page = 1;
var pageSize = 1000;
if (ctx.Request.Query.TryGetValue("page", out var pageStr) && int.TryParse(pageStr, out var p))
page = p; // p == -1 → letzte Seite
if (ctx.Request.Query.TryGetValue("pageSize", out var psStr) && int.TryParse(psStr, out var ps) && ps > 0 && ps <= 10000)
pageSize = ps;
// Bei page == -1 → letzte Seite berechnen
if (page == -1)
{
var count = conn.ExecuteScalar<long>($"SELECT COUNT(*) FROM \"{name}\"");
page = Math.Max(1, (int)Math.Ceiling((double)count / pageSize));
}
else if (page < 1)
{
page = 1;
}
var offset = (page - 1) * pageSize;
// Spalten-Info
var columns = conn.Query<ColumnInfo>($"PRAGMA table_info(\"{name}\")");
// Daten
var totalRows = conn.ExecuteScalar<long>($"SELECT COUNT(*) FROM \"{name}\"");
var rows = conn.Query($"SELECT * FROM \"{name}\" LIMIT @limit OFFSET @offset",
new { limit = pageSize, offset });
await ctx.Response.WriteAsJsonAsync(new
{
table = name,
columns,
rows,
pagination = new
{
page,
pageSize,
totalRows,
totalPages = (int)Math.Ceiling((double)totalRows / pageSize)
}
});
}
private class TableInfo
{
public string Name { get; set; }
public int ColumnCount { get; set; }
}
private class ColumnInfo
{
public int Cid { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public int Notnull { get; set; }
public string DfltValue { get; set; }
public int Pk { get; set; }
}
}