Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Saturn integration #29

Open
wants to merge 4 commits into
base: search-table-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@ source https://www.nuget.org/api/v2/

nuget Giraffe.Razor
nuget Microsoft.AspNetCore.Authentication.Cookies
nuget Microsoft.AspNetCore.Server.IISIntegration
nuget Microsoft.AspNetCore.Server.Kestrel
nuget Microsoft.AspNetCore.StaticFiles
nuget Microsoft.Extensions.Logging.Console
nuget Microsoft.Extensions.Logging.Debug
nuget Microsoft.Extensions.Configuration.Json
nuget Microsoft.Azure.Search
nuget WindowsAzure.Storage
nuget Fable.JsonConverter
nuget Fable.Elmish.Browser
nuget Fable.Elmish.Debugger
nuget Fable.Elmish.React
nuget Fable.Elmish.HMR
nuget Saturn
clitool Microsoft.DotNet.Watcher.Tools
clitool dotnet-fable

Expand All @@ -26,4 +21,4 @@ github CompositionalIT/fshelpers src/FsHelpers/ArmHelper/ArmHelper.fs
github CompositionalIT/fshelpers src/FsHelpers/FSharpCore/String.fs
nuget FSharp.Data
nuget FSharp.Azure.StorageTypeProvider
nuget Fake
nuget Fake
278 changes: 214 additions & 64 deletions paket.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/client/pages/Search.fs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ let viewResults searchResults dispatch =
toTd row.Address.TownCity
td [ Scope "row" ] [ postcodeLink ]
toTd (row.DateOfTransfer.ToShortDateString())
toTd (sprintf "%s" (commaSeparate row.Price)) ]
toTd (sprintf "£%s" (commaSeparate row.Price)) ]
]
]
nav [] [
Expand Down
116 changes: 40 additions & 76 deletions src/server/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,50 @@ module PropertyMapper.App

open System
open System.IO
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Cors.Infrastructure
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Logging
open Microsoft.Extensions.Configuration
open Microsoft.Extensions.DependencyInjection
open Giraffe
open Giraffe.Razor
open PropertyMapper.Routing

// ---------------------------------
// Error handler
// ---------------------------------

let errorHandler (ex : Exception) (logger : ILogger) =
logger.LogError(EventId(), 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:8080").AllowAnyMethod().AllowAnyHeader() |> ignore

let createSearch config =
match config with
| _ when String.IsNullOrWhiteSpace config.AzureSearchServiceName ->
{ new Search.ISearch with
member __.GenericSearch request = Search.InMemory.findGeneric request
member __.PostcodeSearch request = Search.InMemory.findByPostcode request }
| config ->
{ new Search.ISearch with
member __.GenericSearch request = Search.Azure.findGeneric config request
member __.PostcodeSearch request = Search.Azure.findByPostcode config AzureStorage.tryGetGeo request }

let configureApp config (app : IApplicationBuilder) =
app.UseCors(configureCors)
.UseGiraffeErrorHandler(errorHandler)
.UseStaticFiles()
.UseGiraffe(webApp config)

let configureServices (services : IServiceCollection) =
let sp = services.BuildServiceProvider()
let env = sp.GetService<IHostingEnvironment>()
let viewsFolderPath = Path.Combine(env.ContentRootPath, "Views")
services.AddRazorEngine viewsFolderPath |> ignore
services.AddCors() |> ignore

let configureLogging (builder : ILoggingBuilder) =
let filter (l : LogLevel) = l.Equals LogLevel.Error
builder.AddFilter(filter).AddConsole().AddDebug() |> ignore

let appConfig =
lazy
let builder =
ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional = true)
.AddEnvironmentVariables()
.Build()

{ AzureStorage = builder.GetConnectionString "AzureStorage" |> ConnectionString
AzureSearch = builder.GetConnectionString "AzureSearch" |> ConnectionString
AzureSearchServiceName = builder.["AzureSearchName"] }
module Config =
let createSearchEngine() =
let appConfig =
let builder =
ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional = true)
.AddEnvironmentVariables()
.Build()
{ AzureStorage = builder.GetConnectionString "AzureStorage" |> ConnectionString
AzureSearch = builder.GetConnectionString "AzureSearch" |> ConnectionString
AzureSearchServiceName = builder.["AzureSearchName"] }

match appConfig with
| appConfig when String.IsNullOrWhiteSpace appConfig.AzureSearchServiceName ->
{ new Search.ISearchEngine with
member __.GenericSearch request = Search.InMemory.findGeneric request
member __.PostcodeSearch request = Search.InMemory.findByPostcode request }
| appConfig ->
{ new Search.ISearchEngine with
member __.GenericSearch request = Search.Azure.findGeneric appConfig request
member __.PostcodeSearch request = Search.Azure.findByPostcode appConfig AzureStorage.tryGetGeo request }

open Saturn.Application
open Saturn.Router

let topRouter =
let searcher = Config.createSearchEngine()
scope {
error_handler (text "404")
forward "/property" (Routing.propertyRouter searcher) }

let app = application {
url "http://localhost:5000"
memory_cache
use_static (Directory.GetCurrentDirectory())
use_gzip
use_cors "*" (fun builder -> builder.WithOrigins("http://localhost:8080").AllowAnyMethod().AllowAnyHeader() |> ignore)
router topRouter }

[<EntryPoint>]
let main _ =
let contentRoot = Directory.GetCurrentDirectory()
let webRoot = Path.Combine(contentRoot, "WebRoot")
let configureApp = appConfig.Value |> createSearch |> configureApp

WebHostBuilder()
.UseKestrel()
.UseContentRoot(contentRoot)
.UseIISIntegration()
.UseWebRoot(webRoot)
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureServices(configureServices)
.ConfigureLogging(configureLogging)
.Build()
.Run()
run app
0
54 changes: 25 additions & 29 deletions src/server/Routing.fs
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
module PropertyMapper.Routing

open PropertyMapper.Search
open PropertyMapper.Contracts
open Giraffe
open Microsoft.AspNetCore.Http

let searchProperties (searcher:Search.ISearch) (postCode:string, distance, page) next (ctx:HttpContext) = task {
let! properties =
searcher.PostcodeSearch
{ Filter = ctx.BindQueryString<PropertyFilter>()
Postcode = postCode.ToUpper()
MaxDistance = distance
Page = page }
return! FableJson.serialize properties next ctx }
module Adapters =
open Microsoft.AspNetCore.Http
open Giraffe
open PropertyMapper.Search
open PropertyMapper.Contracts
let searchProperties (searcher:Search.ISearchEngine) (postCode:string, distance, page) next (ctx:HttpContext) = task {
let! properties =
searcher.PostcodeSearch
{ Filter = ctx.BindQueryString<PropertyFilter>()
Postcode = postCode.ToUpper()
MaxDistance = distance
Page = page }
return! FableJson.serialize properties next ctx }

let genericSearch (searcher:Search.ISearch) (text, page) next (ctx:HttpContext) =
let request =
{ Page = page
Text = if System.String.IsNullOrWhiteSpace text then None else Some text
Filter = ctx.BindQueryString<PropertyFilter>() }
task {
let! properties = searcher.GenericSearch request
let genericSearch (searcher:Search.ISearchEngine) (text, page) next (ctx:HttpContext) = task {
let! properties =
let request =
{ Page = page
Text = if System.String.IsNullOrWhiteSpace text then None else Some text
Filter = ctx.BindQueryString<PropertyFilter>() }
searcher.GenericSearch request
return! FableJson.serialize properties next ctx }
let webApp searcher : HttpHandler =
choose [
GET >=>
choose [
routef "/property/find/%s/%i" (genericSearch searcher)
routef "/property/%s/%i/%i" (searchProperties searcher)
routef "/property/%s/%i" (fun (postcode, distance) -> searchProperties searcher (postcode, distance, 0))
]
setStatusCode 404 >=> text "Not Found" ]

open Saturn.Router
let propertyRouter searcher = scope {
getf "/find/%s/%i" (Adapters.genericSearch searcher)
getf "/%s/%i/%i" (Adapters.searchProperties searcher)
getf "/%s/%i" (fun (postcode, distance) -> Adapters.searchProperties searcher (postcode, distance, 0)) }
2 changes: 1 addition & 1 deletion src/server/Search.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ type FindNearestRequest = { Postcode : string; MaxDistance : int; Page : int; Fi
type FindGenericRequest = { Text : string option; Page : int; Filter : PropertyFilter }
type Geo = { Lat : float; Long : float }

type ISearch =
type ISearchEngine =
abstract GenericSearch : FindGenericRequest -> SearchResponse Task
abstract PostcodeSearch : FindNearestRequest -> SearchResponse Task
9 changes: 2 additions & 7 deletions src/server/paket.references
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
Fable.JsonConverter
Giraffe.Razor
Microsoft.AspNetCore.Authentication.Cookies
Microsoft.AspNetCore.Server.Kestrel
Microsoft.AspNetCore.Server.IISIntegration
Microsoft.AspNetCore.StaticFiles
Microsoft.Azure.Search
Microsoft.DotNet.Watcher.Tools
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.Logging.Console
Microsoft.Extensions.Logging.Debug
WindowsAzure.Storage
WindowsAzure.Storage
Saturn