From 2b5ec692f2a2ce095a53b8d61d958fec0644e725 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Fri, 31 Jan 2025 07:02:10 -0500 Subject: [PATCH] Config conns for migration (#55) - Update deps --- src/PrayerTracker.Data/Access.fs | 37 ++- .../PrayerTracker.Data.fsproj | 2 +- src/PrayerTracker.MigrateV9/Program.fs | 155 +++++----- .../PrayerTracker.Tests.fsproj | 4 +- src/PrayerTracker.UI/PrayerTracker.UI.fsproj | 12 +- src/PrayerTracker/App.fs | 287 ++++++++++-------- src/PrayerTracker/PrayerTracker.fsproj | 2 +- 7 files changed, 271 insertions(+), 228 deletions(-) diff --git a/src/PrayerTracker.Data/Access.fs b/src/PrayerTracker.Data/Access.fs index 47d908d..912bb01 100644 --- a/src/PrayerTracker.Data/Access.fs +++ b/src/PrayerTracker.Data/Access.fs @@ -67,15 +67,24 @@ module Json = opts.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull opts - +open BitBadger.Documents open BitBadger.Documents.Sqlite /// Establish the required data environment [] -module Environment = - +module Connection = + + open System.Text.Json + /// Ensure tables and indexes are defined let setUp () = backgroundTask { + Configuration.useIdField "id" + Configuration.useSerializer + { new IDocumentSerializer with + member _.Serialize<'T>(it : 'T) = JsonSerializer.Serialize(it, Json.options) + member _.Deserialize<'T>(it : string) = JsonSerializer.Deserialize<'T>(it, Json.options) + } + let! tables = Custom.list "SELECT table_name FROM sqlite_master" [] _.GetString(0) if not (List.contains Table.Church tables) then do! Definition.ensureTable Table.Church @@ -310,17 +319,17 @@ module SmallGroups = Count.byFields Table.Group All [ Field.Equal "churchId" churchId ] /// Delete a small group by its ID - let deleteById (groupId : SmallGroupId) = backgroundTask { - let idParam = [ [ "@groupId", Sql.uuid groupId.Value ] ] - let! _ = - BitBadger.Documents.Postgres.Configuration.dataSource () - |> Sql.fromDataSource - |> Sql.executeTransactionAsync - [ "DELETE FROM pt.prayer_request WHERE small_group_id = @groupId", idParam - "DELETE FROM pt.user_small_group WHERE small_group_id = @groupId", idParam - "DELETE FROM pt.list_preference WHERE small_group_id = @groupId", idParam - "DELETE FROM pt.small_group WHERE id = @groupId", idParam ] - () + let deleteById (groupId: SmallGroupId) = backgroundTask { + use conn = Configuration.dbConn () + use txn = conn.BeginTransaction() + + let! users = Find.byFields Table.User All [ Field.InArray "smallGroups" Table.User [ groupId ] ] + for user in users do + do! Patch.byId Table.User user.Id {| SmallGroups = user.SmallGroups |> List.except [ groupId ] |} + do! conn.deleteByFields Table.Request All [ Field.Equal "smallGroupId" groupId ] + do! conn.deleteById Table.Group groupId + + do! txn.CommitAsync() } /// Get information for all small groups diff --git a/src/PrayerTracker.Data/PrayerTracker.Data.fsproj b/src/PrayerTracker.Data/PrayerTracker.Data.fsproj index 627d40c..7b3baa8 100644 --- a/src/PrayerTracker.Data/PrayerTracker.Data.fsproj +++ b/src/PrayerTracker.Data/PrayerTracker.Data.fsproj @@ -14,7 +14,7 @@ - + diff --git a/src/PrayerTracker.MigrateV9/Program.fs b/src/PrayerTracker.MigrateV9/Program.fs index 35fd6a3..0a25360 100644 --- a/src/PrayerTracker.MigrateV9/Program.fs +++ b/src/PrayerTracker.MigrateV9/Program.fs @@ -1,112 +1,103 @@  open NodaTime -open Npgsql.FSharp -open PrayerTracker.Data open PrayerTracker.Entities module PgMappings = /// Map a row to a Church instance - let mapToChurch (row : RowReader) = - { Id = ChurchId (row.uuid "id") - Name = row.string "church_name" - City = row.string "city" - State = row.string "state" - HasVpsInterface = row.bool "has_vps_interface" - InterfaceAddress = row.stringOrNone "interface_address" - } - - /// Map a row to a ListPreferences instance - let mapToListPreferences (row : RowReader) = - { SmallGroupId = SmallGroupId (row.uuid "small_group_id") - DaysToKeepNew = row.int "days_to_keep_new" - DaysToExpire = row.int "days_to_expire" - LongTermUpdateWeeks = row.int "long_term_update_weeks" - EmailFromName = row.string "email_from_name" - EmailFromAddress = row.string "email_from_address" - Fonts = row.string "fonts" - HeadingColor = row.string "heading_color" - LineColor = row.string "line_color" - HeadingFontSize = row.int "heading_font_size" - TextFontSize = row.int "text_font_size" - GroupPassword = row.string "group_password" - IsPublic = row.bool "is_public" - PageSize = row.int "page_size" - TimeZoneId = TimeZoneId (row.string "time_zone_id") - RequestSort = RequestSort.Parse (row.string "request_sort") - DefaultEmailType = EmailFormat.Parse (row.string "default_email_type") - AsOfDateDisplay = AsOfDateDisplay.Parse (row.string "as_of_date_display") - } + let mapToChurch (row: RowReader) = + { Id = ChurchId (row.uuid "id") + Name = row.string "church_name" + City = row.string "city" + State = row.string "state" + HasVpsInterface = row.bool "has_vps_interface" + InterfaceAddress = row.stringOrNone "interface_address" } /// Map a row to a Member instance - let mapToMember (row : RowReader) = - { Id = MemberId (row.uuid "id") - SmallGroupId = SmallGroupId (row.uuid "small_group_id") - Name = row.string "member_name" - Email = row.string "email" - Format = row.stringOrNone "email_format" |> Option.map EmailFormat.Parse - } + let mapToMember (row: RowReader) = + { Id = MemberId (row.uuid "id") + SmallGroupId = SmallGroupId (row.uuid "small_group_id") + Name = row.string "member_name" + Email = row.string "email" + Format = row.stringOrNone "email_format" |> Option.map EmailFormat.Parse } /// Map a row to a Prayer Request instance - let mapToPrayerRequest (row : RowReader) = - { Id = PrayerRequestId (row.uuid "id") - UserId = UserId (row.uuid "user_id") - SmallGroupId = SmallGroupId (row.uuid "small_group_id") - EnteredDate = row.fieldValue "entered_date" - UpdatedDate = row.fieldValue "updated_date" - Requestor = row.stringOrNone "requestor" - Text = row.string "request_text" - NotifyChaplain = row.bool "notify_chaplain" - RequestType = PrayerRequestType.Parse (row.string "request_type") - Expiration = Expiration.Parse (row.string "expiration") - } + let mapToPrayerRequest (row: RowReader) = + { Id = PrayerRequestId (row.uuid "id") + UserId = UserId (row.uuid "user_id") + SmallGroupId = SmallGroupId (row.uuid "small_group_id") + EnteredDate = row.fieldValue "entered_date" + UpdatedDate = row.fieldValue "updated_date" + Requestor = row.stringOrNone "requestor" + Text = row.string "request_text" + NotifyChaplain = row.bool "notify_chaplain" + RequestType = PrayerRequestType.Parse (row.string "request_type") + Expiration = Expiration.Parse (row.string "expiration") } /// Map a row to a Small Group instance - let mapToSmallGroup (row : RowReader) = - { Id = SmallGroupId (row.uuid "id") - ChurchId = ChurchId (row.uuid "church_id") - Name = row.string "group_name" - Preferences = ListPreferences.Empty - } - - /// Map a row to a Small Group instance with populated list preferences - let mapToSmallGroupWithPreferences (row : RowReader) = - { mapToSmallGroup row with - Preferences = mapToListPreferences row - } + let mapToSmallGroup (row: RowReader) = + { Id = SmallGroupId (row.uuid "id") + ChurchId = ChurchId (row.uuid "church_id") + Name = row.string "group_name" + Preferences = + { SmallGroupId = SmallGroupId (row.uuid "small_group_id") + DaysToKeepNew = row.int "days_to_keep_new" + DaysToExpire = row.int "days_to_expire" + LongTermUpdateWeeks = row.int "long_term_update_weeks" + EmailFromName = row.string "email_from_name" + EmailFromAddress = row.string "email_from_address" + Fonts = row.string "fonts" + HeadingColor = row.string "heading_color" + LineColor = row.string "line_color" + HeadingFontSize = row.int "heading_font_size" + TextFontSize = row.int "text_font_size" + GroupPassword = row.string "group_password" + IsPublic = row.bool "is_public" + PageSize = row.int "page_size" + TimeZoneId = TimeZoneId (row.string "time_zone_id") + RequestSort = RequestSort.Parse (row.string "request_sort") + DefaultEmailType = EmailFormat.Parse (row.string "default_email_type") + AsOfDateDisplay = AsOfDateDisplay.Parse (row.string "as_of_date_display") } } /// Map a row to a User instance - let mapToUser (row : RowReader) = - { Id = UserId (row.uuid "id") - FirstName = row.string "first_name" - LastName = row.string "last_name" - Email = row.string "email" - IsAdmin = row.bool "is_admin" - PasswordHash = row.string "password_hash" - LastSeen = row.fieldValueOrNone "last_seen" - SmallGroups = [] - } + let mapToUser (row: RowReader) = + { Id = UserId (row.uuid "id") + FirstName = row.string "first_name" + LastName = row.string "last_name" + Email = row.string "email" + IsAdmin = row.bool "is_admin" + PasswordHash = row.string "password_hash" + LastSeen = row.fieldValueOrNone "last_seen" + SmallGroups = [] } -// TODO: Configure PostgreSQL and SQLite connections + +open System +open BitBadger.Documents.Sqlite +open Npgsql +open Npgsql.FSharp +open PrayerTracker.Data task { - - let source = BitBadger.Documents.Postgres.Configuration.dataSource () + + Configuration.useConnectionString (Environment.GetEnvironmentVariable "PT_SQLITE_CONN") + do! Connection.setUp () + + use source = NpgsqlDataSourceBuilder(Environment.GetEnvironmentVariable "PT_PG_CONN").Build() let! churches = Sql.fromDataSource source |> Sql.query "SELECT * FROM pt.church" |> Sql.executeAsync PgMappings.mapToChurch for church in churches do - do! BitBadger.Documents.Sqlite.Document.insert Table.Church church + do! Churches.save church printfn "Migrated %d churches" churches.Length let! groups = Sql.fromDataSource source |> Sql.query "SELECT sg.*, lp.* FROM pt.small_group sg INNER JOIN pt.list_preference lp ON lp.small_group_id = sg.id" - |> Sql.executeAsync PgMappings.mapToSmallGroupWithPreferences + |> Sql.executeAsync PgMappings.mapToSmallGroup for group in groups do - do! BitBadger.Documents.Sqlite.Document.insert Table.Group group + do! SmallGroups.save group printfn "Migrated %d groups" groups.Length let! members = @@ -114,7 +105,7 @@ task { |> Sql.query "SELECT * from pt.member" |> Sql.executeAsync PgMappings.mapToMember for mbr in members do - do! BitBadger.Documents.Sqlite.Document.insert Table.Member mbr + do! Members.save mbr printfn "Migrated %d members" members.Length let! requests = @@ -122,7 +113,7 @@ task { |> Sql.query "SELECT * from pt.prayer_request" |> Sql.executeAsync PgMappings.mapToPrayerRequest for request in requests do - do! BitBadger.Documents.Sqlite.Document.insert Table.Request request + do! PrayerRequests.save request printfn "Migrated %d requests" requests.Length let! users = @@ -135,7 +126,7 @@ task { |> Sql.query "SELECT small_group_id FROM pt.user_small_group WHERE user_id = :user_id" |> Sql.parameters [ ":user_id", Sql.uuid user.Id.Value ] |> Sql.executeAsync (fun row -> (row.uuid >> SmallGroupId) "small_group_id") - do! BitBadger.Documents.Sqlite.Document.insert Table.User { user with SmallGroups = groups } + do! Users.save { user with SmallGroups = groups } printfn "Migrated %d users" users.Length } |> Async.AwaitTask |> Async.RunSynchronously diff --git a/src/PrayerTracker.Tests/PrayerTracker.Tests.fsproj b/src/PrayerTracker.Tests/PrayerTracker.Tests.fsproj index 4c0ab10..f6e3caf 100644 --- a/src/PrayerTracker.Tests/PrayerTracker.Tests.fsproj +++ b/src/PrayerTracker.Tests/PrayerTracker.Tests.fsproj @@ -15,8 +15,8 @@ - - + + diff --git a/src/PrayerTracker.UI/PrayerTracker.UI.fsproj b/src/PrayerTracker.UI/PrayerTracker.UI.fsproj index 89d2f33..99635fc 100644 --- a/src/PrayerTracker.UI/PrayerTracker.UI.fsproj +++ b/src/PrayerTracker.UI/PrayerTracker.UI.fsproj @@ -18,13 +18,13 @@ - - - - - + + + + + - + diff --git a/src/PrayerTracker/App.fs b/src/PrayerTracker/App.fs index 6910569..3d8aef1 100644 --- a/src/PrayerTracker/App.fs +++ b/src/PrayerTracker/App.fs @@ -3,12 +3,13 @@ namespace PrayerTracker open Microsoft.AspNetCore.Http /// Middleware to add the starting ticks for the request -type RequestStartMiddleware (next: RequestDelegate) = - - member this.InvokeAsync(ctx: HttpContext) = task { - ctx.Items[Key.startTime] <- ctx.Now - return! next.Invoke ctx - } +type RequestStartMiddleware(next: RequestDelegate) = + + member this.InvokeAsync(ctx: HttpContext) = + task { + ctx.Items[Key.startTime] <- ctx.Now + return! next.Invoke ctx + } open System @@ -19,23 +20,24 @@ open Microsoft.Extensions.Configuration /// Module to hold configuration for the web app [] module Configure = - + /// Set up the configuration for the app let configuration (ctx: WebHostBuilderContext) (cfg: IConfigurationBuilder) = - cfg.SetBasePath(ctx.HostingEnvironment.ContentRootPath) + cfg + .SetBasePath(ctx.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", optional = true, reloadOnChange = true) .AddJsonFile($"appsettings.{ctx.HostingEnvironment.EnvironmentName}.json", optional = true) .AddEnvironmentVariables() |> ignore open Microsoft.AspNetCore.Server.Kestrel.Core - + /// Configure Kestrel from appsettings.json let kestrel (ctx: WebHostBuilderContext) (opts: KestrelServerOptions) = (ctx.Configuration.GetSection >> opts.Configure >> ignore) "Kestrel" open System.Globalization - open BitBadger.Documents.Postgres + open BitBadger.Documents.Sqlite open Microsoft.AspNetCore.Authentication.Cookies open Microsoft.AspNetCore.Localization open Microsoft.Extensions.Caching.Distributed @@ -43,131 +45,153 @@ module Configure = open NodaTime open Npgsql open PrayerTracker.Data - + /// Configure ASP.NET Core's service collection (dependency injection container) let services (svc: IServiceCollection) = let _ = svc.AddOptions() let _ = svc.AddLocalization(fun options -> options.ResourcesPath <- "Resources") + let _ = svc.Configure(fun (opts: RequestLocalizationOptions) -> let supportedCultures = - [| CultureInfo "en-US"; CultureInfo "en-GB"; CultureInfo "en-AU"; CultureInfo "en" - CultureInfo "es-MX"; CultureInfo "es-ES"; CultureInfo "es" |] + [| CultureInfo "en-US" + CultureInfo "en-GB" + CultureInfo "en-AU" + CultureInfo "en" + CultureInfo "es-MX" + CultureInfo "es-ES" + CultureInfo "es" |] + opts.DefaultRequestCulture <- RequestCulture("en-US", "en-US") - opts.SupportedCultures <- supportedCultures - opts.SupportedUICultures <- supportedCultures) + opts.SupportedCultures <- supportedCultures + opts.SupportedUICultures <- supportedCultures) + let _ = - svc.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + svc + .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(fun opts -> - opts.ExpireTimeSpan <- TimeSpan.FromMinutes 120. + opts.ExpireTimeSpan <- TimeSpan.FromMinutes 120. opts.SlidingExpiration <- true - opts.AccessDeniedPath <- "/error/403") + opts.AccessDeniedPath <- "/error/403") + let _ = svc.AddAuthorization() let cfg = svc.BuildServiceProvider().GetService() - let dsb = NpgsqlDataSourceBuilder(cfg.GetConnectionString "PrayerTracker") - let _ = dsb.UseNodaTime() - dsb.Build() |> Configuration.useDataSource + Configuration.useConnectionString (cfg.GetConnectionString "PrayerTracker") + Connection.setUp () |> Async.AwaitTask |> Async.RunSynchronously let emailCfg = cfg.GetSection "Email" - if (emailCfg.GetChildren >> Seq.isEmpty >> not) () then ConfigurationBinder.Bind(emailCfg, Email.smtpOptions) + + if (emailCfg.GetChildren >> Seq.isEmpty >> not) () then + ConfigurationBinder.Bind(emailCfg, Email.smtpOptions) let _ = svc.AddSingleton() let _ = svc.AddSession() let _ = svc.AddAntiforgery() let _ = svc.AddRouting() let _ = svc.AddSingleton SystemClock.Instance - + () - + open Giraffe - - let noWeb : HttpHandler = fun next ctx -> - redirectTo true $"""/{string ctx.Request.RouteValues["path"]}""" next ctx - + + /// Endpoint to redirect URLs starting with /web to their non-web equivalent + let noWeb: HttpHandler = + fun next ctx -> redirectTo true $"""/{string ctx.Request.RouteValues["path"]}""" next ctx + open Giraffe.EndpointRouting - + /// Routes for PrayerTracker - let routes = [ - route "/web/{**path}" noWeb - GET_HEAD [ - subRoute "/church" [ - route "es" Handlers.Church.maintain - routef "/%O/edit" Handlers.Church.edit ] - route "/class/logon" (redirectTo true "/small-group/log-on") - routef "/error/%s" Handlers.Home.error - subRoute "/help" [ - route "" Handlers.Help.index - subRoute "/requests" [ - route "/edit" Handlers.Help.Requests.edit - route "/maintain" Handlers.Help.Requests.maintain - route "/view" Handlers.Help.Requests.view ] - subRoute "/small-group" [ - route "/announcement" Handlers.Help.SmallGroup.announcement - route "/members" Handlers.Help.SmallGroup.members - route "/preferences" Handlers.Help.SmallGroup.preferences ] - subRoute "/user" [ - route "/log-on" Handlers.Help.User.logOn - route "/password" Handlers.Help.User.password ] ] - routef "/language/%s" Handlers.Home.language - subRoute "/legal" [ - route "/privacy-policy" Handlers.Home.privacyPolicy - route "/terms-of-service" Handlers.Home.tos ] - route "/log-off" Handlers.Home.logOff - subRoute "/prayer-request" [ - route "s" (Handlers.PrayerRequest.maintain true) - routef "s/email/%s" Handlers.PrayerRequest.email - route "s/inactive" (Handlers.PrayerRequest.maintain false) - route "s/lists" Handlers.PrayerRequest.lists - routef "s/%O/list" Handlers.PrayerRequest.list - route "s/maintain" (redirectTo true "/prayer-requests") - routef "s/print/%s" Handlers.PrayerRequest.print - route "s/view" (Handlers.PrayerRequest.view None) - routef "s/view/%s" (Some >> Handlers.PrayerRequest.view) - routef "/%O/edit" Handlers.PrayerRequest.edit - routef "/%O/expire" Handlers.PrayerRequest.expire - routef "/%O/restore" Handlers.PrayerRequest.restore ] - subRoute "/small-group" [ - route "" Handlers.SmallGroup.overview - route "s" Handlers.SmallGroup.maintain - route "/announcement" Handlers.SmallGroup.announcement - routef "/%O/edit" Handlers.SmallGroup.edit - route "/log-on" (Handlers.SmallGroup.logOn None) - routef "/log-on/%O" (Some >> Handlers.SmallGroup.logOn) - route "/logon" (redirectTo true "/small-group/log-on") - routef "/member/%O/edit" Handlers.SmallGroup.editMember - route "/members" Handlers.SmallGroup.members - route "/preferences" Handlers.SmallGroup.preferences ] - route "/unauthorized" Handlers.Home.unauthorized - subRoute "/user" [ - route "s" Handlers.User.maintain - routef "/%O/edit" Handlers.User.edit - routef "/%O/small-groups" Handlers.User.smallGroups - route "/log-on" Handlers.User.logOn - route "/logon" (redirectTo true "/user/log-on") - route "/password" Handlers.User.password ] - route "/" Handlers.Home.homePage ] - POST [ - subRoute "/church" [ - routef "/%O/delete" Handlers.Church.delete - route "/save" Handlers.Church.save ] - subRoute "/prayer-request" [ - routef "/%O/delete" Handlers.PrayerRequest.delete - route "/save" Handlers.PrayerRequest.save ] - subRoute "/small-group" [ - route "/announcement/send" Handlers.SmallGroup.sendAnnouncement - routef "/%O/delete" Handlers.SmallGroup.delete - route "/log-on/submit" Handlers.SmallGroup.logOnSubmit - routef "/member/%O/delete" Handlers.SmallGroup.deleteMember - route "/member/save" Handlers.SmallGroup.saveMember - route "/preferences/save" Handlers.SmallGroup.savePreferences - route "/save" Handlers.SmallGroup.save ] - subRoute "/user" [ - routef "/%O/delete" Handlers.User.delete - route "/edit/save" Handlers.User.save - route "/log-on" Handlers.User.doLogOn - route "/password/change" Handlers.User.changePassword - route "/small-groups/save" Handlers.User.saveGroups ] ] ] + let routes = + [ route "/web/{**path}" noWeb + GET_HEAD + [ subRoute "/church" [ route "es" Handlers.Church.maintain; routef "/%O/edit" Handlers.Church.edit ] + route "/class/logon" (redirectTo true "/small-group/log-on") + routef "/error/%s" Handlers.Home.error + subRoute + "/help" + [ route "" Handlers.Help.index + subRoute + "/requests" + [ route "/edit" Handlers.Help.Requests.edit + route "/maintain" Handlers.Help.Requests.maintain + route "/view" Handlers.Help.Requests.view ] + subRoute + "/small-group" + [ route "/announcement" Handlers.Help.SmallGroup.announcement + route "/members" Handlers.Help.SmallGroup.members + route "/preferences" Handlers.Help.SmallGroup.preferences ] + subRoute + "/user" + [ route "/log-on" Handlers.Help.User.logOn + route "/password" Handlers.Help.User.password ] ] + routef "/language/%s" Handlers.Home.language + subRoute + "/legal" + [ route "/privacy-policy" Handlers.Home.privacyPolicy + route "/terms-of-service" Handlers.Home.tos ] + route "/log-off" Handlers.Home.logOff + subRoute + "/prayer-request" + [ route "s" (Handlers.PrayerRequest.maintain true) + routef "s/email/%s" Handlers.PrayerRequest.email + route "s/inactive" (Handlers.PrayerRequest.maintain false) + route "s/lists" Handlers.PrayerRequest.lists + routef "s/%O/list" Handlers.PrayerRequest.list + route "s/maintain" (redirectTo true "/prayer-requests") + routef "s/print/%s" Handlers.PrayerRequest.print + route "s/view" (Handlers.PrayerRequest.view None) + routef "s/view/%s" (Some >> Handlers.PrayerRequest.view) + routef "/%O/edit" Handlers.PrayerRequest.edit + routef "/%O/expire" Handlers.PrayerRequest.expire + routef "/%O/restore" Handlers.PrayerRequest.restore ] + subRoute + "/small-group" + [ route "" Handlers.SmallGroup.overview + route "s" Handlers.SmallGroup.maintain + route "/announcement" Handlers.SmallGroup.announcement + routef "/%O/edit" Handlers.SmallGroup.edit + route "/log-on" (Handlers.SmallGroup.logOn None) + routef "/log-on/%O" (Some >> Handlers.SmallGroup.logOn) + route "/logon" (redirectTo true "/small-group/log-on") + routef "/member/%O/edit" Handlers.SmallGroup.editMember + route "/members" Handlers.SmallGroup.members + route "/preferences" Handlers.SmallGroup.preferences ] + route "/unauthorized" Handlers.Home.unauthorized + subRoute + "/user" + [ route "s" Handlers.User.maintain + routef "/%O/edit" Handlers.User.edit + routef "/%O/small-groups" Handlers.User.smallGroups + route "/log-on" Handlers.User.logOn + route "/logon" (redirectTo true "/user/log-on") + route "/password" Handlers.User.password ] + route "/" Handlers.Home.homePage ] + POST + [ subRoute + "/church" + [ routef "/%O/delete" Handlers.Church.delete + route "/save" Handlers.Church.save ] + subRoute + "/prayer-request" + [ routef "/%O/delete" Handlers.PrayerRequest.delete + route "/save" Handlers.PrayerRequest.save ] + subRoute + "/small-group" + [ route "/announcement/send" Handlers.SmallGroup.sendAnnouncement + routef "/%O/delete" Handlers.SmallGroup.delete + route "/log-on/submit" Handlers.SmallGroup.logOnSubmit + routef "/member/%O/delete" Handlers.SmallGroup.deleteMember + route "/member/save" Handlers.SmallGroup.saveMember + route "/preferences/save" Handlers.SmallGroup.savePreferences + route "/save" Handlers.SmallGroup.save ] + subRoute + "/user" + [ routef "/%O/delete" Handlers.User.delete + route "/edit/save" Handlers.User.save + route "/log-on" Handlers.User.doLogOn + route "/password/change" Handlers.User.changePassword + route "/small-groups/save" Handlers.User.saveGroups ] ] ] open Microsoft.Extensions.Logging @@ -175,53 +199,67 @@ module Configure = let errorHandler (ex: exn) (logger: ILogger) = logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.") clearResponse >=> setStatusCode 500 >=> text ex.Message - + open Microsoft.Extensions.Hosting - + /// Configure logging let logging (log: ILoggingBuilder) = let env = log.Services.BuildServiceProvider().GetService() - if env.IsDevelopment() then log else log.AddFilter(fun l -> l > LogLevel.Information) - |> function l -> l.AddConsole().AddDebug() + + if env.IsDevelopment() then + log + else + log.AddFilter(fun l -> l > LogLevel.Information) + |> function + | l -> l.AddConsole().AddDebug() |> ignore - + open BitBadger.AspNetCore.CanonicalDomains open Microsoft.Extensions.Localization open Microsoft.Extensions.Options - + /// Configure the application let app (app: IApplicationBuilder) = let env = app.ApplicationServices.GetRequiredService() + if env.IsDevelopment() then app.UseDeveloperExceptionPage() else app.UseGiraffeErrorHandler errorHandler |> ignore - + let _ = app.UseForwardedHeaders() let _ = app.UseCanonicalDomains() let _ = app.UseStatusCodePagesWithReExecute "/error/{0}" let _ = app.UseStaticFiles() - let _ = app.UseCookiePolicy(CookiePolicyOptions(MinimumSameSitePolicy = SameSiteMode.Strict)) + + let _ = + app.UseCookiePolicy(CookiePolicyOptions(MinimumSameSitePolicy = SameSiteMode.Strict)) + let _ = app.UseMiddleware() let _ = app.UseRouting() let _ = app.UseSession() - let _ = app.UseRequestLocalization( - app.ApplicationServices.GetService>().Value) + + let _ = + app.UseRequestLocalization(app.ApplicationServices.GetService>().Value) + let _ = app.UseAuthentication() let _ = app.UseAuthorization() let _ = app.UseEndpoints(fun e -> e.MapGiraffeEndpoints routes) - app.ApplicationServices.GetRequiredService() |> Views.I18N.setUpFactories + + app.ApplicationServices.GetRequiredService() + |> Views.I18N.setUpFactories /// The web application module App = - + open System.IO [] let main args = let contentRoot = Directory.GetCurrentDirectory() + let app = WebHostBuilder() .UseContentRoot(contentRoot) @@ -232,5 +270,10 @@ module App = .ConfigureLogging(Configure.logging) .Configure(System.Action Configure.app) .Build() - if args.Length > 0 then printfn $"Unrecognized option {args[0]}" else app.Run() + + if args.Length > 0 then + printfn $"Unrecognized option {args[0]}" + else + app.Run() + 0 diff --git a/src/PrayerTracker/PrayerTracker.fsproj b/src/PrayerTracker/PrayerTracker.fsproj index 6c52fe7..4e6939f 100644 --- a/src/PrayerTracker/PrayerTracker.fsproj +++ b/src/PrayerTracker/PrayerTracker.fsproj @@ -28,7 +28,7 @@ - +