- CreateApiKey() gibt jetzt string key_id zurück statt long rowid - SetScannedKeyAdopted() erwartet string statt long - DeleteApiKey() direkter Vergleich statt Subquery - DB-Explorer: PageSize Default auf 1000 erhöht
116 lines
3.7 KiB
C#
116 lines
3.7 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) && p > 0)
|
|
page = p;
|
|
if (ctx.Request.Query.TryGetValue("pageSize", out var psStr) && int.TryParse(psStr, out var ps) && ps > 0 && ps <= 10000)
|
|
pageSize = ps;
|
|
|
|
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; }
|
|
}
|
|
}
|