diff --git a/Program.fs b/Program.fs index 774dd9f..fa9226d 100644 --- a/Program.fs +++ b/Program.fs @@ -1,19 +1,32 @@ open Giraffe -open Microsoft.AspNetCore.Http +open System +open System.IO open Microsoft.AspNetCore.Builder -open Microsoft.Extensions.DependencyInjection +open Microsoft.AspNetCore.Cors.Infrastructure +open Microsoft.AspNetCore.Hosting open Microsoft.Extensions.Hosting -open Thoth.Json -type QueryFilter = -| Equal of string * string -| GreaterThan of string * float -| LessThan of string * float +open Microsoft.Extensions.Logging +open Microsoft.Extensions.DependencyInjection +open Microsoft.AspNetCore.Http + +#if FABLE_COMPILER +open Thoth.Json +#else +open Thoth.Json.Net +#endif + +// Here you can write your code as usual + +type QueryFilter = + | Equal of string * string + | GreaterThan of string * float + | LessThan of string * float + +type QueryDSL = + { Select: string list + Filters: QueryFilter list + Sort: (string * string) list } // Field and direction -type QueryDSL = { - Select : string list - Filters : QueryFilter list - Sort : (string * string) list // Field and direction -} let parseQuery (body: string) = // Example JSON parsing (using FSharp.Data or similar) // Example query JSON: @@ -35,85 +48,133 @@ let parseQuery (body: string) = // parsed.["sort"].AsArray() // |> Array.map (fun s -> s.[0].AsString(), s.[1].AsString()) // |> List.ofArray - + //{ Select = selectFields; Filters = filters; Sort = sort } query - with - | ex -> failwithf "Failed to parse query: %s" ex.Message + with ex -> + failwithf "Failed to parse query: %s" ex.Message let fetchData (queryDsl: QueryDSL) = // Mock database query (replace with actual DB logic) - let mockData = [ - Map.ofList [ "name", box "Alice"; "age", box 30 ] - Map.ofList [ "name", box "Bob"; "age", box 25 ] - Map.ofList [ "name", box "Charlie"; "age", box 35 ] - ] + let mockData = + [ Map.ofList [ "name", box "Alice"; "age", box 30 ] + Map.ofList [ "name", box "Bob"; "age", box 25 ] + Map.ofList [ "name", box "Charlie"; "age", box 35 ] ] // Filter and transform the data based on the DSL mockData |> List.filter (fun row -> - queryDsl.Filters |> List.forall (function + queryDsl.Filters + |> List.forall (function | Equal(field, num) -> Map.tryFind field row - |> Option.map(fun value -> (string value) = num) + |> Option.map (fun value -> (string value) = num) |> Option.defaultValue false - //row.GetType().GetProperty(field).GetValue(row) = value + //row.GetType().GetProperty(field).GetValue(row) = value //| GreaterThan(field, num) -> row.[field] |> float > num | GreaterThan(field, num) -> - Map.tryFind field row - |> Option.bind (fun value -> - match value with - | :? float as v -> Some (v > num) - | :? int as v -> Some (float v > num) - | _ -> None) + Map.tryFind field row + |> Option.bind (fun value -> + match value with + | :? float as v -> Some(v > num) + | :? int as v -> Some(float v > num) + | _ -> None) |> Option.defaultValue false | LessThan(field, num) -> - Map.tryFind field row - |> Option.bind (fun value -> - match value with - | :? float as v -> Some (v < num) - | :? int as v -> Some (float v < num) - | _ -> None) - |> Option.defaultValue false)) - |> List.map (fun row -> - queryDsl.Select |> List.map (fun field -> field, row.[field]) |> dict) + Map.tryFind field row + |> Option.bind (fun value -> + match value with + | :? float as v -> Some(v < num) + | :? int as v -> Some(float v < num) + | _ -> None) + |> Option.defaultValue false)) + |> List.map (fun row -> queryDsl.Select |> List.map (fun field -> field, row.[field]) |> dict) |> fun result -> result -let queryHandler = fun (next: HttpFunc) (ctx: HttpContext) -> - task { - //let! body = ctx.ReadBodyAsStringAsync() - let! body = ctx.ReadBodyBufferedFromRequestAsync() - return! parseQuery body - |> Result.map(fun queryDsl -> - let data = fetchData queryDsl - json data next ctx - ) - |> Result.defaultWith (fun error -> json {|success = false;message = error|} next ctx) - } -let webApp () = - POST >=> route "query" >=> queryHandler - // [ - //subRoute "/foo" [ GET [ route "/bar" (text "Aloha!") ] ] - // POST - // route "query" queryHandler - // RequestErrors.notFound "Not Found" - - // ] +let queryHandler = + fun (next: HttpFunc) (ctx: HttpContext) -> + task { + //let! body = ctx.ReadBodyAsStringAsync() + let! body = ctx.ReadBodyBufferedFromRequestAsync() + + return! + parseQuery body + |> Result.map (fun queryDsl -> + let data = fetchData queryDsl + json data next ctx) + |> Result.defaultWith (fun error -> json {| success = false; message = error |} next ctx) + } + +let webApp = + choose + [ POST >=> choose [ route "/query" >=> queryHandler ] + setStatusCode 404 >=> text "Not Found" ] +// --------------------------------- +// Error handler +// --------------------------------- + +let errorHandler (ex: Exception) (logger: ILogger) = + logger.LogError(ex, "An unhandled exception has occurred while executing the request.") + clearResponse >=> setStatusCode 500 >=> text ex.Message + +// --------------------------------- +// Config and Main +// --------------------------------- + +let configureCors (builder: CorsPolicyBuilder) = + builder.WithOrigins("http://localhost:5000", "https://localhost:5001").AllowAnyMethod().AllowAnyHeader() + |> ignore let configureApp (app: IApplicationBuilder) = - app.UseGiraffe queryHandler + let env = app.ApplicationServices.GetService() + + (match env.IsDevelopment() with + | true -> app.UseDeveloperExceptionPage() + | false -> app.UseGiraffeErrorHandler(errorHandler).UseHttpsRedirection()) + .UseCors(configureCors) + .UseStaticFiles() + .UseGiraffe(webApp) let configureServices (services: IServiceCollection) = + services.AddCors() |> ignore services.AddGiraffe() |> ignore +let configureLogging (builder: ILoggingBuilder) = + builder.AddConsole().AddDebug() |> ignore + [] let main args = - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(fun webBuilder -> - webBuilder - //.Configure(configureApp) - .ConfigureServices(configureServices) - |> ignore) + let contentRoot = Directory.GetCurrentDirectory() + let webRoot = Path.Combine(contentRoot, "WebRoot") + + Host + .CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(fun webHostBuilder -> + webHostBuilder + //.UseContentRoot(contentRoot) + //.UseWebRoot(webRoot) + .Configure(Action configureApp) + .ConfigureServices(configureServices) + .ConfigureLogging(configureLogging) + |> ignore) .Build() .Run() + 0 +//let configureApp (app: IApplicationBuilder) = +// app.UseGiraffe queryHandler + +//let configureServices (services: IServiceCollection) = +// services.AddGiraffe() |> ignore + +//[] +//let main args = +// Host.CreateDefaultBuilder(args) +// .ConfigureWebHostDefaults(fun webBuilder -> +// webBuilder +//.Configure(configureApp) +// .ConfigureServices(configureServices) +// |> ignore) +// .Build() +// .Run() +// 0 diff --git a/ReportApi.fsproj b/ReportApi.fsproj index 912f842..54b04fe 100644 --- a/ReportApi.fsproj +++ b/ReportApi.fsproj @@ -5,9 +5,12 @@ net9.0 + - + + +