using System; using System.Collections.Generic; using System.Data.Odbc; using System.Linq; using AS400API.Auth; using AS400API.Infrastructure; using Dapper; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; namespace AS400API.Endpoints; public static class As400Endpoints { public static RouteGroupBuilder MapAs400Endpoints(this RouteGroupBuilder group) { group.MapGet("/v1/as400/query", async (string sql, OdbcConnection conn, ILoggerFactory loggerFactory) => { var logger = loggerFactory.CreateLogger("As400QueryEndpoint"); try { await conn.OpenAsync(); if (string.IsNullOrWhiteSpace(sql)) { logger.LogWarning("Missing sql query parameter when executing AS/400 query"); return Results.BadRequest(new { error = "Query parameter 'sql' is required." }); } logger.LogInformation("Executing AS/400 query with SQL length {Length}", sql?.Length ?? 0); var rows = (await conn.QueryAsync(sql!)).ToList(); var formatted = rows.ToCamelCaseDictionaries().ToList(); logger.LogInformation("AS/400 query returned {RowCount} rows", formatted.Count); return Results.Ok(formatted); } catch (Exception ex) { logger.LogError(ex, "AS/400 query failed"); return Results.Problem($"Query failed: {ex.Message}"); } }) .RequireAuthorization(AuthPolicies.RequireAdmin) .WithSummary("Execute a read-only SQL select against AS/400 (admin only)") .Produces(200) .ProducesProblem(500); group.MapGet("/v1/as400/databases", async (OdbcConnection conn, ILoggerFactory loggerFactory) => { var logger = loggerFactory.CreateLogger("As400DatabasesEndpoint"); try { await conn.OpenAsync(); const string sqlQuery = "SELECT SCHEMA_NAME AS LIBRARY_NAME FROM QSYS2.SYSSCHEMAS ORDER BY LIBRARY_NAME"; var schemas = (await conn.QueryAsync(sqlQuery)).ToList(); var formatted = schemas.ToCamelCaseDictionaries().ToList(); logger.LogInformation("Fetched {Count} AS/400 schemas", formatted.Count); return Results.Ok(formatted); } catch (Exception ex) { logger.LogError(ex, "Failed to list AS/400 schemas"); return Results.Problem($"Failed to list schemas: {ex.Message}"); } }) .RequireAuthorization(AuthPolicies.RequireOperator) .WithSummary("List available libraries/schemas for AS/400 accounts"); group.MapGet("/v1/as400/tables", async (string libraryName, OdbcConnection conn, ILoggerFactory loggerFactory) => { var logger = loggerFactory.CreateLogger("As400TablesEndpoint"); try { await conn.OpenAsync(); if (string.IsNullOrWhiteSpace(libraryName)) { logger.LogWarning("Missing libraryName query parameter when listing tables"); return Results.BadRequest(new { error = "Query parameter 'libraryName' is required." }); } const string sqlQuery = """ SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM QSYS2.SYSTABLES WHERE TABLE_SCHEMA = @libraryName ORDER BY TABLE_NAME """; var tables = (await conn.QueryAsync(sqlQuery, new { libraryName })).ToList(); logger.LogInformation("Fetched {Count} tables for AS/400 library {Library}", tables.Count, libraryName); var formatted = tables.ToCamelCaseDictionaries().ToList(); return Results.Ok(formatted); } catch (Exception ex) { logger.LogError(ex, "Failed to list tables for AS/400 library {Library}", libraryName); return Results.Problem($"Failed to list tables: {ex.Message}"); } }) .RequireAuthorization(AuthPolicies.RequireOperator) .WithSummary("List tables for a specific AS/400 library (schema)"); group.MapGet("/v1/as400/table-structure", async (string libraryName, string tableName, OdbcConnection conn, ILoggerFactory loggerFactory) => { var logger = loggerFactory.CreateLogger("As400TableStructureEndpoint"); try { await conn.OpenAsync(); if (string.IsNullOrWhiteSpace(libraryName) || string.IsNullOrWhiteSpace(tableName)) { logger.LogWarning("Missing libraryName or tableName query parameter when requesting table structure"); return Results.BadRequest(new { error = "Query parameters 'libraryName' and 'tableName' are required." }); } const string sqlQuery = """ SELECT COLUMN_NAME, SYSTEM_COLUMN_NAME, DATA_TYPE, LENGTH, NUMERIC_SCALE, IS_NULLABLE FROM QSYS2.SYSCOLUMNS2 WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION """; await using var command = conn.CreateCommand(); command.CommandText = sqlQuery; var libraryParameter = command.CreateParameter(); libraryParameter.Value = libraryName.ToUpper(); command.Parameters.Add(libraryParameter); var tableParameter = command.CreateParameter(); tableParameter.Value = tableName.ToUpper(); command.Parameters.Add(tableParameter); var columns = new List>(); await using (var reader = await command.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { var row = new Dictionary(StringComparer.OrdinalIgnoreCase); for (var i = 0; i < reader.FieldCount; i++) { row[reader.GetName(i)] = reader.GetNormalizedValue(i)!; } columns.Add(row); } } // var tables = (await conn.QueryAsync(sqlQuery, new { libraryName })).ToList(); // var formatted = tables.ToList(); logger.LogInformation("Fetched {Count} columns for {Library}/{Table}", columns.Count, libraryName, tableName); var formatted = columns.Select(dict => dict.ToCamelCaseDictionary()).ToList(); return Results.Ok(formatted); } catch (Exception ex) { logger.LogError(ex, "Failed to list columns for {Library}/{Table}", libraryName, tableName); return Results.Problem($"Failed to list columns: {ex.Message}"); } }) // .RequireAuthorization(AuthPolicies.RequireOperator) .WithSummary("Describe the structure of a table (columns) for an AS/400 library"); return group; } }