parent
							
								
									d86249c18e
								
							
						
					
					
						commit
						2b5ec692f2
					
				| @ -67,15 +67,24 @@ module Json = | |||||||
|         opts.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull |         opts.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull | ||||||
|         opts |         opts | ||||||
| 
 | 
 | ||||||
| 
 | open BitBadger.Documents | ||||||
| open BitBadger.Documents.Sqlite | open BitBadger.Documents.Sqlite | ||||||
| 
 | 
 | ||||||
| /// Establish the required data environment | /// Establish the required data environment | ||||||
| [<RequireQualifiedAccess>] | [<RequireQualifiedAccess>] | ||||||
| module Environment = | module Connection = | ||||||
| 
 |      | ||||||
|  |     open System.Text.Json | ||||||
|  |      | ||||||
|     /// Ensure tables and indexes are defined |     /// Ensure tables and indexes are defined | ||||||
|     let setUp () = backgroundTask { |     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<string> "SELECT table_name FROM sqlite_master" [] _.GetString(0) |         let! tables = Custom.list<string> "SELECT table_name FROM sqlite_master" [] _.GetString(0) | ||||||
|         if not (List.contains Table.Church tables) then |         if not (List.contains Table.Church tables) then | ||||||
|             do! Definition.ensureTable Table.Church |             do! Definition.ensureTable Table.Church | ||||||
| @ -310,17 +319,17 @@ module SmallGroups = | |||||||
|         Count.byFields Table.Group All [ Field.Equal "churchId" churchId ] |         Count.byFields Table.Group All [ Field.Equal "churchId" churchId ] | ||||||
| 
 | 
 | ||||||
|     /// Delete a small group by its ID |     /// Delete a small group by its ID | ||||||
|     let deleteById (groupId : SmallGroupId) = backgroundTask { |     let deleteById (groupId: SmallGroupId) = backgroundTask { | ||||||
|         let idParam = [ [ "@groupId", Sql.uuid groupId.Value ] ] |         use conn = Configuration.dbConn () | ||||||
|         let! _ = |         use txn  = conn.BeginTransaction() | ||||||
|             BitBadger.Documents.Postgres.Configuration.dataSource () |          | ||||||
|             |> Sql.fromDataSource |         let! users = Find.byFields<User> Table.User All [ Field.InArray "smallGroups" Table.User [ groupId ] ] | ||||||
|             |> Sql.executeTransactionAsync |         for user in users do | ||||||
|                 [   "DELETE FROM pt.prayer_request   WHERE small_group_id = @groupId", idParam |             do! Patch.byId Table.User user.Id {| SmallGroups = user.SmallGroups |> List.except [ groupId ] |} | ||||||
|                     "DELETE FROM pt.user_small_group WHERE small_group_id = @groupId", idParam |         do! conn.deleteByFields Table.Request All [ Field.Equal "smallGroupId" groupId ] | ||||||
|                     "DELETE FROM pt.list_preference  WHERE small_group_id = @groupId", idParam |         do! conn.deleteById Table.Group groupId | ||||||
|                     "DELETE FROM pt.small_group      WHERE id             = @groupId", idParam ] |          | ||||||
|         () |         do! txn.CommitAsync() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Get information for all small groups |     /// Get information for all small groups | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
|     <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" /> |     <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" /> | ||||||
|     <PackageReference Include="Npgsql.FSharp" Version="5.7.0" /> |     <PackageReference Include="Npgsql.FSharp" Version="5.7.0" /> | ||||||
|     <PackageReference Include="Npgsql.NodaTime" Version="8.0.3" /> |     <PackageReference Include="Npgsql.NodaTime" Version="8.0.3" /> | ||||||
|     <PackageReference Update="FSharp.Core" Version="9.0.100" /> |     <PackageReference Update="FSharp.Core" Version="9.0.101" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
| </Project> | </Project> | ||||||
|  | |||||||
| @ -1,112 +1,103 @@ | |||||||
|  |  | ||||||
| open NodaTime | open NodaTime | ||||||
| open Npgsql.FSharp |  | ||||||
| open PrayerTracker.Data |  | ||||||
| open PrayerTracker.Entities | open PrayerTracker.Entities | ||||||
| 
 | 
 | ||||||
| module PgMappings = | module PgMappings = | ||||||
|     /// Map a row to a Church instance |     /// Map a row to a Church instance | ||||||
|     let mapToChurch (row : RowReader) = |     let mapToChurch (row: RowReader) = | ||||||
|         {   Id               = ChurchId         (row.uuid "id") |         { Id               = ChurchId         (row.uuid "id") | ||||||
|             Name             = row.string       "church_name" |           Name             = row.string       "church_name" | ||||||
|             City             = row.string       "city" |           City             = row.string       "city" | ||||||
|             State            = row.string       "state" |           State            = row.string       "state" | ||||||
|             HasVpsInterface  = row.bool         "has_vps_interface" |           HasVpsInterface  = row.bool         "has_vps_interface" | ||||||
|             InterfaceAddress = row.stringOrNone "interface_address" |           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") |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|     /// Map a row to a Member instance |     /// Map a row to a Member instance | ||||||
|     let mapToMember (row : RowReader) = |     let mapToMember (row: RowReader) = | ||||||
|         {   Id           = MemberId         (row.uuid "id") |         { Id           = MemberId         (row.uuid "id") | ||||||
|             SmallGroupId = SmallGroupId     (row.uuid "small_group_id") |           SmallGroupId = SmallGroupId     (row.uuid "small_group_id") | ||||||
|             Name         = row.string       "member_name" |           Name         = row.string       "member_name" | ||||||
|             Email        = row.string       "email" |           Email        = row.string       "email" | ||||||
|             Format       = row.stringOrNone "email_format" |> Option.map EmailFormat.Parse |           Format       = row.stringOrNone "email_format" |> Option.map EmailFormat.Parse } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|     /// Map a row to a Prayer Request instance |     /// Map a row to a Prayer Request instance | ||||||
|     let mapToPrayerRequest (row : RowReader) = |     let mapToPrayerRequest (row: RowReader) = | ||||||
|         {   Id             = PrayerRequestId         (row.uuid "id") |         { Id             = PrayerRequestId         (row.uuid "id") | ||||||
|             UserId         = UserId                  (row.uuid "user_id") |           UserId         = UserId                  (row.uuid "user_id") | ||||||
|             SmallGroupId   = SmallGroupId            (row.uuid "small_group_id") |           SmallGroupId   = SmallGroupId            (row.uuid "small_group_id") | ||||||
|             EnteredDate    = row.fieldValue<Instant> "entered_date" |           EnteredDate    = row.fieldValue<Instant> "entered_date" | ||||||
|             UpdatedDate    = row.fieldValue<Instant> "updated_date" |           UpdatedDate    = row.fieldValue<Instant> "updated_date" | ||||||
|             Requestor      = row.stringOrNone        "requestor" |           Requestor      = row.stringOrNone        "requestor" | ||||||
|             Text           = row.string              "request_text" |           Text           = row.string              "request_text" | ||||||
|             NotifyChaplain = row.bool                "notify_chaplain" |           NotifyChaplain = row.bool                "notify_chaplain" | ||||||
|             RequestType    = PrayerRequestType.Parse (row.string "request_type") |           RequestType    = PrayerRequestType.Parse (row.string "request_type") | ||||||
|             Expiration     = Expiration.Parse        (row.string "expiration") |           Expiration     = Expiration.Parse        (row.string "expiration") } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|     /// Map a row to a Small Group instance |     /// Map a row to a Small Group instance | ||||||
|     let mapToSmallGroup (row : RowReader) = |     let mapToSmallGroup (row: RowReader) = | ||||||
|         {   Id          = SmallGroupId (row.uuid "id") |         { Id          = SmallGroupId (row.uuid "id") | ||||||
|             ChurchId    = ChurchId     (row.uuid "church_id") |           ChurchId    = ChurchId     (row.uuid "church_id") | ||||||
|             Name        = row.string   "group_name" |           Name        = row.string   "group_name" | ||||||
|             Preferences = ListPreferences.Empty |           Preferences = | ||||||
|         } |               { SmallGroupId        = SmallGroupId (row.uuid "small_group_id") | ||||||
| 
 |                 DaysToKeepNew       = row.int      "days_to_keep_new" | ||||||
|     /// Map a row to a Small Group instance with populated list preferences |                 DaysToExpire        = row.int      "days_to_expire" | ||||||
|     let mapToSmallGroupWithPreferences (row : RowReader) = |                 LongTermUpdateWeeks = row.int      "long_term_update_weeks" | ||||||
|         { mapToSmallGroup row with |                 EmailFromName       = row.string   "email_from_name" | ||||||
|             Preferences = mapToListPreferences row |                 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 |     /// Map a row to a User instance | ||||||
|     let mapToUser (row : RowReader) = |     let mapToUser (row: RowReader) = | ||||||
|         {   Id           = UserId     (row.uuid "id") |         { Id           = UserId     (row.uuid "id") | ||||||
|             FirstName    = row.string "first_name" |           FirstName    = row.string "first_name" | ||||||
|             LastName     = row.string "last_name" |           LastName     = row.string "last_name" | ||||||
|             Email        = row.string "email" |           Email        = row.string "email" | ||||||
|             IsAdmin      = row.bool   "is_admin" |           IsAdmin      = row.bool   "is_admin" | ||||||
|             PasswordHash = row.string "password_hash" |           PasswordHash = row.string "password_hash" | ||||||
|             LastSeen     = row.fieldValueOrNone<Instant> "last_seen" |           LastSeen     = row.fieldValueOrNone<Instant> "last_seen" | ||||||
|             SmallGroups  = [] |           SmallGroups  = [] } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
| // TODO: Configure PostgreSQL and SQLite connections | 
 | ||||||
|  | open System | ||||||
|  | open BitBadger.Documents.Sqlite | ||||||
|  | open Npgsql | ||||||
|  | open Npgsql.FSharp | ||||||
|  | open PrayerTracker.Data | ||||||
| 
 | 
 | ||||||
| task { | 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 = |     let! churches = | ||||||
|         Sql.fromDataSource source |         Sql.fromDataSource source | ||||||
|         |> Sql.query "SELECT * FROM pt.church" |         |> Sql.query "SELECT * FROM pt.church" | ||||||
|         |> Sql.executeAsync PgMappings.mapToChurch |         |> Sql.executeAsync PgMappings.mapToChurch | ||||||
|     for church in churches do |     for church in churches do | ||||||
|         do! BitBadger.Documents.Sqlite.Document.insert Table.Church church |         do! Churches.save church | ||||||
|     printfn "Migrated %d churches" churches.Length |     printfn "Migrated %d churches" churches.Length | ||||||
| 
 | 
 | ||||||
|     let! groups = |     let! groups = | ||||||
|         Sql.fromDataSource source |         Sql.fromDataSource source | ||||||
|         |> Sql.query "SELECT sg.*, lp.* FROM pt.small_group sg |         |> Sql.query "SELECT sg.*, lp.* FROM pt.small_group sg | ||||||
|                         INNER JOIN pt.list_preference lp ON lp.small_group_id = sg.id" |                         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 |     for group in groups do | ||||||
|         do! BitBadger.Documents.Sqlite.Document.insert Table.Group group |         do! SmallGroups.save group | ||||||
|     printfn "Migrated %d groups" groups.Length |     printfn "Migrated %d groups" groups.Length | ||||||
| 
 | 
 | ||||||
|     let! members = |     let! members = | ||||||
| @ -114,7 +105,7 @@ task { | |||||||
|         |> Sql.query "SELECT * from pt.member" |         |> Sql.query "SELECT * from pt.member" | ||||||
|         |> Sql.executeAsync PgMappings.mapToMember |         |> Sql.executeAsync PgMappings.mapToMember | ||||||
|     for mbr in members do |     for mbr in members do | ||||||
|         do! BitBadger.Documents.Sqlite.Document.insert Table.Member mbr |         do! Members.save mbr | ||||||
|     printfn "Migrated %d members" members.Length |     printfn "Migrated %d members" members.Length | ||||||
| 
 | 
 | ||||||
|     let! requests = |     let! requests = | ||||||
| @ -122,7 +113,7 @@ task { | |||||||
|         |> Sql.query "SELECT * from pt.prayer_request" |         |> Sql.query "SELECT * from pt.prayer_request" | ||||||
|         |> Sql.executeAsync PgMappings.mapToPrayerRequest |         |> Sql.executeAsync PgMappings.mapToPrayerRequest | ||||||
|     for request in requests do |     for request in requests do | ||||||
|         do! BitBadger.Documents.Sqlite.Document.insert Table.Request request |         do! PrayerRequests.save request | ||||||
|     printfn "Migrated %d requests" requests.Length |     printfn "Migrated %d requests" requests.Length | ||||||
| 
 | 
 | ||||||
|     let! users = |     let! users = | ||||||
| @ -135,7 +126,7 @@ task { | |||||||
|             |> Sql.query "SELECT small_group_id FROM pt.user_small_group WHERE user_id = :user_id" |             |> 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.parameters [ ":user_id", Sql.uuid user.Id.Value ] | ||||||
|             |> Sql.executeAsync (fun row -> (row.uuid >> SmallGroupId) "small_group_id") |             |> 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 |     printfn "Migrated %d users" users.Length | ||||||
| 
 | 
 | ||||||
| } |> Async.AwaitTask |> Async.RunSynchronously | } |> Async.AwaitTask |> Async.RunSynchronously | ||||||
|  | |||||||
| @ -15,8 +15,8 @@ | |||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="Expecto" Version="10.2.1" /> |     <PackageReference Include="Expecto" Version="10.2.1" /> | ||||||
|     <PackageReference Include="NodaTime.Testing" Version="3.2.0" /> |     <PackageReference Include="NodaTime.Testing" Version="3.2.1" /> | ||||||
|     <PackageReference Update="FSharp.Core" Version="9.0.100" /> |     <PackageReference Update="FSharp.Core" Version="9.0.101" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  | |||||||
| @ -18,13 +18,13 @@ | |||||||
|     <PackageReference Include="Giraffe.Fixi" Version="0.5.7" /> |     <PackageReference Include="Giraffe.Fixi" Version="0.5.7" /> | ||||||
|     <PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" /> |     <PackageReference Include="Giraffe.ViewEngine" Version="1.4.0" /> | ||||||
|     <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.4" /> |     <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.4" /> | ||||||
|     <PackageReference Include="MailKit" Version="4.9.0" /> |     <PackageReference Include="MailKit" Version="4.10.0" /> | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="2.2.0" /> |     <PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="2.3.0" /> | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" /> |     <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" /> | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" /> |     <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.3.0" /> | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> |     <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.3.0" /> | ||||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> |     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||||
|     <PackageReference Update="FSharp.Core" Version="9.0.100" /> |     <PackageReference Update="FSharp.Core" Version="9.0.101" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  | |||||||
| @ -3,12 +3,13 @@ namespace PrayerTracker | |||||||
| open Microsoft.AspNetCore.Http | open Microsoft.AspNetCore.Http | ||||||
| 
 | 
 | ||||||
| /// Middleware to add the starting ticks for the request | /// Middleware to add the starting ticks for the request | ||||||
| type RequestStartMiddleware (next: RequestDelegate) = | type RequestStartMiddleware(next: RequestDelegate) = | ||||||
|      | 
 | ||||||
|     member this.InvokeAsync(ctx: HttpContext) = task { |     member this.InvokeAsync(ctx: HttpContext) = | ||||||
|         ctx.Items[Key.startTime] <- ctx.Now |         task { | ||||||
|         return! next.Invoke ctx |             ctx.Items[Key.startTime] <- ctx.Now | ||||||
|     } |             return! next.Invoke ctx | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| open System | open System | ||||||
| @ -19,23 +20,24 @@ open Microsoft.Extensions.Configuration | |||||||
| /// Module to hold configuration for the web app | /// Module to hold configuration for the web app | ||||||
| [<RequireQualifiedAccess>] | [<RequireQualifiedAccess>] | ||||||
| module Configure = | module Configure = | ||||||
|    | 
 | ||||||
|     /// Set up the configuration for the app |     /// Set up the configuration for the app | ||||||
|     let configuration (ctx: WebHostBuilderContext) (cfg: IConfigurationBuilder) = |     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.json", optional = true, reloadOnChange = true) | ||||||
|             .AddJsonFile($"appsettings.{ctx.HostingEnvironment.EnvironmentName}.json", optional = true) |             .AddJsonFile($"appsettings.{ctx.HostingEnvironment.EnvironmentName}.json", optional = true) | ||||||
|             .AddEnvironmentVariables() |             .AddEnvironmentVariables() | ||||||
|         |> ignore |         |> ignore | ||||||
| 
 | 
 | ||||||
|     open Microsoft.AspNetCore.Server.Kestrel.Core |     open Microsoft.AspNetCore.Server.Kestrel.Core | ||||||
|      | 
 | ||||||
|     /// Configure Kestrel from appsettings.json |     /// Configure Kestrel from appsettings.json | ||||||
|     let kestrel (ctx: WebHostBuilderContext) (opts: KestrelServerOptions) = |     let kestrel (ctx: WebHostBuilderContext) (opts: KestrelServerOptions) = | ||||||
|         (ctx.Configuration.GetSection >> opts.Configure >> ignore) "Kestrel" |         (ctx.Configuration.GetSection >> opts.Configure >> ignore) "Kestrel" | ||||||
| 
 | 
 | ||||||
|     open System.Globalization |     open System.Globalization | ||||||
|     open BitBadger.Documents.Postgres |     open BitBadger.Documents.Sqlite | ||||||
|     open Microsoft.AspNetCore.Authentication.Cookies |     open Microsoft.AspNetCore.Authentication.Cookies | ||||||
|     open Microsoft.AspNetCore.Localization |     open Microsoft.AspNetCore.Localization | ||||||
|     open Microsoft.Extensions.Caching.Distributed |     open Microsoft.Extensions.Caching.Distributed | ||||||
| @ -43,131 +45,153 @@ module Configure = | |||||||
|     open NodaTime |     open NodaTime | ||||||
|     open Npgsql |     open Npgsql | ||||||
|     open PrayerTracker.Data |     open PrayerTracker.Data | ||||||
|      | 
 | ||||||
|     /// Configure ASP.NET Core's service collection (dependency injection container) |     /// Configure ASP.NET Core's service collection (dependency injection container) | ||||||
|     let services (svc: IServiceCollection) = |     let services (svc: IServiceCollection) = | ||||||
|         let _ = svc.AddOptions() |         let _ = svc.AddOptions() | ||||||
|         let _ = svc.AddLocalization(fun options -> options.ResourcesPath <- "Resources") |         let _ = svc.AddLocalization(fun options -> options.ResourcesPath <- "Resources") | ||||||
|  | 
 | ||||||
|         let _ = |         let _ = | ||||||
|             svc.Configure<RequestLocalizationOptions>(fun (opts: RequestLocalizationOptions) -> |             svc.Configure<RequestLocalizationOptions>(fun (opts: RequestLocalizationOptions) -> | ||||||
|                 let supportedCultures = |                 let supportedCultures = | ||||||
|                     [| CultureInfo "en-US"; CultureInfo "en-GB"; CultureInfo "en-AU"; CultureInfo "en" |                     [| CultureInfo "en-US" | ||||||
|                        CultureInfo "es-MX"; CultureInfo "es-ES"; CultureInfo "es" |] |                        CultureInfo "en-GB" | ||||||
|  |                        CultureInfo "en-AU" | ||||||
|  |                        CultureInfo "en" | ||||||
|  |                        CultureInfo "es-MX" | ||||||
|  |                        CultureInfo "es-ES" | ||||||
|  |                        CultureInfo "es" |] | ||||||
|  | 
 | ||||||
|                 opts.DefaultRequestCulture <- RequestCulture("en-US", "en-US") |                 opts.DefaultRequestCulture <- RequestCulture("en-US", "en-US") | ||||||
|                 opts.SupportedCultures     <- supportedCultures |                 opts.SupportedCultures <- supportedCultures | ||||||
|                 opts.SupportedUICultures   <- supportedCultures) |                 opts.SupportedUICultures <- supportedCultures) | ||||||
|  | 
 | ||||||
|         let _ = |         let _ = | ||||||
|             svc.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) |             svc | ||||||
|  |                 .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) | ||||||
|                 .AddCookie(fun opts -> |                 .AddCookie(fun opts -> | ||||||
|                     opts.ExpireTimeSpan    <- TimeSpan.FromMinutes 120. |                     opts.ExpireTimeSpan <- TimeSpan.FromMinutes 120. | ||||||
|                     opts.SlidingExpiration <- true |                     opts.SlidingExpiration <- true | ||||||
|                     opts.AccessDeniedPath  <- "/error/403") |                     opts.AccessDeniedPath <- "/error/403") | ||||||
|  | 
 | ||||||
|         let _ = svc.AddAuthorization() |         let _ = svc.AddAuthorization() | ||||||
| 
 | 
 | ||||||
|         let cfg = svc.BuildServiceProvider().GetService<IConfiguration>() |         let cfg = svc.BuildServiceProvider().GetService<IConfiguration>() | ||||||
|         let dsb = NpgsqlDataSourceBuilder(cfg.GetConnectionString "PrayerTracker") |         Configuration.useConnectionString (cfg.GetConnectionString "PrayerTracker") | ||||||
|         let _   = dsb.UseNodaTime() |         Connection.setUp () |> Async.AwaitTask |> Async.RunSynchronously | ||||||
|         dsb.Build() |> Configuration.useDataSource  |  | ||||||
| 
 | 
 | ||||||
|         let emailCfg = cfg.GetSection "Email" |         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<IDistributedCache, DistributedCache>() |         let _ = svc.AddSingleton<IDistributedCache, DistributedCache>() | ||||||
|         let _ = svc.AddSession() |         let _ = svc.AddSession() | ||||||
|         let _ = svc.AddAntiforgery() |         let _ = svc.AddAntiforgery() | ||||||
|         let _ = svc.AddRouting() |         let _ = svc.AddRouting() | ||||||
|         let _ = svc.AddSingleton<IClock> SystemClock.Instance |         let _ = svc.AddSingleton<IClock> SystemClock.Instance | ||||||
|          | 
 | ||||||
|         () |         () | ||||||
|      | 
 | ||||||
|     open Giraffe |     open Giraffe | ||||||
|      | 
 | ||||||
|     let noWeb : HttpHandler = fun next ctx -> |     /// <summary>Endpoint to redirect URLs starting with <c>/web</c> to their non-web equivalent</summary> | ||||||
|         redirectTo true $"""/{string ctx.Request.RouteValues["path"]}""" next ctx |     let noWeb: HttpHandler = | ||||||
|          |         fun next ctx -> redirectTo true $"""/{string ctx.Request.RouteValues["path"]}""" next ctx | ||||||
|  | 
 | ||||||
|     open Giraffe.EndpointRouting |     open Giraffe.EndpointRouting | ||||||
|      | 
 | ||||||
|     /// Routes for PrayerTracker |     /// Routes for PrayerTracker | ||||||
|     let routes = [ |     let routes = | ||||||
|         route "/web/{**path}" noWeb |         [ route "/web/{**path}" noWeb | ||||||
|         GET_HEAD [ |           GET_HEAD | ||||||
|             subRoute "/church" [ |               [ subRoute "/church" [ route "es" Handlers.Church.maintain; routef "/%O/edit" Handlers.Church.edit ] | ||||||
|                 route  "es"       Handlers.Church.maintain |                 route "/class/logon" (redirectTo true "/small-group/log-on") | ||||||
|                 routef "/%O/edit" Handlers.Church.edit ] |                 routef "/error/%s" Handlers.Home.error | ||||||
|             route    "/class/logon" (redirectTo true "/small-group/log-on") |                 subRoute | ||||||
|             routef   "/error/%s"    Handlers.Home.error |                     "/help" | ||||||
|             subRoute "/help" [ |                     [ route "" Handlers.Help.index | ||||||
|                 route    "" Handlers.Help.index |                       subRoute | ||||||
|                 subRoute "/requests" [ |                           "/requests" | ||||||
|                     route "/edit"     Handlers.Help.Requests.edit |                           [ route "/edit" Handlers.Help.Requests.edit | ||||||
|                     route "/maintain" Handlers.Help.Requests.maintain |                             route "/maintain" Handlers.Help.Requests.maintain | ||||||
|                     route "/view"     Handlers.Help.Requests.view ] |                             route "/view" Handlers.Help.Requests.view ] | ||||||
|                 subRoute "/small-group" [ |                       subRoute | ||||||
|                     route "/announcement" Handlers.Help.SmallGroup.announcement |                           "/small-group" | ||||||
|                     route "/members"      Handlers.Help.SmallGroup.members |                           [ route "/announcement" Handlers.Help.SmallGroup.announcement | ||||||
|                     route "/preferences"  Handlers.Help.SmallGroup.preferences ] |                             route "/members" Handlers.Help.SmallGroup.members | ||||||
|                 subRoute "/user" [ |                             route "/preferences" Handlers.Help.SmallGroup.preferences ] | ||||||
|                     route "/log-on"   Handlers.Help.User.logOn |                       subRoute | ||||||
|                     route "/password" Handlers.Help.User.password ] ] |                           "/user" | ||||||
|             routef   "/language/%s" Handlers.Home.language |                           [ route "/log-on" Handlers.Help.User.logOn | ||||||
|             subRoute "/legal" [ |                             route "/password" Handlers.Help.User.password ] ] | ||||||
|                 route "/privacy-policy"   Handlers.Home.privacyPolicy |                 routef "/language/%s" Handlers.Home.language | ||||||
|                 route "/terms-of-service" Handlers.Home.tos ] |                 subRoute | ||||||
|             route    "/log-off" Handlers.Home.logOff |                     "/legal" | ||||||
|             subRoute "/prayer-request" [ |                     [ route "/privacy-policy" Handlers.Home.privacyPolicy | ||||||
|                 route  "s"           (Handlers.PrayerRequest.maintain true) |                       route "/terms-of-service" Handlers.Home.tos ] | ||||||
|                 routef "s/email/%s"  Handlers.PrayerRequest.email |                 route "/log-off" Handlers.Home.logOff | ||||||
|                 route  "s/inactive"  (Handlers.PrayerRequest.maintain false) |                 subRoute | ||||||
|                 route  "s/lists"     Handlers.PrayerRequest.lists |                     "/prayer-request" | ||||||
|                 routef "s/%O/list"   Handlers.PrayerRequest.list |                     [ route "s" (Handlers.PrayerRequest.maintain true) | ||||||
|                 route  "s/maintain"  (redirectTo true "/prayer-requests") |                       routef "s/email/%s" Handlers.PrayerRequest.email | ||||||
|                 routef "s/print/%s"  Handlers.PrayerRequest.print |                       route "s/inactive" (Handlers.PrayerRequest.maintain false) | ||||||
|                 route  "s/view"      (Handlers.PrayerRequest.view None) |                       route "s/lists" Handlers.PrayerRequest.lists | ||||||
|                 routef "s/view/%s"   (Some >> Handlers.PrayerRequest.view) |                       routef "s/%O/list" Handlers.PrayerRequest.list | ||||||
|                 routef "/%O/edit"    Handlers.PrayerRequest.edit |                       route "s/maintain" (redirectTo true "/prayer-requests") | ||||||
|                 routef "/%O/expire"  Handlers.PrayerRequest.expire |                       routef "s/print/%s" Handlers.PrayerRequest.print | ||||||
|                 routef "/%O/restore" Handlers.PrayerRequest.restore ] |                       route "s/view" (Handlers.PrayerRequest.view None) | ||||||
|             subRoute "/small-group" [ |                       routef "s/view/%s" (Some >> Handlers.PrayerRequest.view) | ||||||
|                 route  ""                Handlers.SmallGroup.overview |                       routef "/%O/edit" Handlers.PrayerRequest.edit | ||||||
|                 route  "s"               Handlers.SmallGroup.maintain |                       routef "/%O/expire" Handlers.PrayerRequest.expire | ||||||
|                 route  "/announcement"   Handlers.SmallGroup.announcement |                       routef "/%O/restore" Handlers.PrayerRequest.restore ] | ||||||
|                 routef "/%O/edit"        Handlers.SmallGroup.edit |                 subRoute | ||||||
|                 route  "/log-on"         (Handlers.SmallGroup.logOn None) |                     "/small-group" | ||||||
|                 routef "/log-on/%O"      (Some >> Handlers.SmallGroup.logOn) |                     [ route "" Handlers.SmallGroup.overview | ||||||
|                 route  "/logon"          (redirectTo true "/small-group/log-on") |                       route "s" Handlers.SmallGroup.maintain | ||||||
|                 routef "/member/%O/edit" Handlers.SmallGroup.editMember |                       route "/announcement" Handlers.SmallGroup.announcement | ||||||
|                 route  "/members"        Handlers.SmallGroup.members |                       routef "/%O/edit" Handlers.SmallGroup.edit | ||||||
|                 route  "/preferences"    Handlers.SmallGroup.preferences ] |                       route "/log-on" (Handlers.SmallGroup.logOn None) | ||||||
|             route    "/unauthorized" Handlers.Home.unauthorized |                       routef "/log-on/%O" (Some >> Handlers.SmallGroup.logOn) | ||||||
|             subRoute "/user" [ |                       route "/logon" (redirectTo true "/small-group/log-on") | ||||||
|                 route  "s"                Handlers.User.maintain |                       routef "/member/%O/edit" Handlers.SmallGroup.editMember | ||||||
|                 routef "/%O/edit"         Handlers.User.edit |                       route "/members" Handlers.SmallGroup.members | ||||||
|                 routef "/%O/small-groups" Handlers.User.smallGroups |                       route "/preferences" Handlers.SmallGroup.preferences ] | ||||||
|                 route  "/log-on"          Handlers.User.logOn |                 route "/unauthorized" Handlers.Home.unauthorized | ||||||
|                 route  "/logon"           (redirectTo true "/user/log-on") |                 subRoute | ||||||
|                 route  "/password"        Handlers.User.password ] |                     "/user" | ||||||
|             route    "/" Handlers.Home.homePage ] |                     [ route "s" Handlers.User.maintain | ||||||
|         POST [ |                       routef "/%O/edit" Handlers.User.edit | ||||||
|             subRoute "/church" [ |                       routef "/%O/small-groups" Handlers.User.smallGroups | ||||||
|                 routef "/%O/delete" Handlers.Church.delete |                       route "/log-on" Handlers.User.logOn | ||||||
|                 route  "/save"      Handlers.Church.save ] |                       route "/logon" (redirectTo true "/user/log-on") | ||||||
|             subRoute "/prayer-request" [ |                       route "/password" Handlers.User.password ] | ||||||
|                 routef "/%O/delete" Handlers.PrayerRequest.delete |                 route "/" Handlers.Home.homePage ] | ||||||
|                 route  "/save"      Handlers.PrayerRequest.save ] |           POST | ||||||
|             subRoute "/small-group" [ |               [ subRoute | ||||||
|                 route  "/announcement/send" Handlers.SmallGroup.sendAnnouncement |                     "/church" | ||||||
|                 routef "/%O/delete"         Handlers.SmallGroup.delete |                     [ routef "/%O/delete" Handlers.Church.delete | ||||||
|                 route  "/log-on/submit"     Handlers.SmallGroup.logOnSubmit |                       route "/save" Handlers.Church.save ] | ||||||
|                 routef "/member/%O/delete"  Handlers.SmallGroup.deleteMember |                 subRoute | ||||||
|                 route  "/member/save"       Handlers.SmallGroup.saveMember |                     "/prayer-request" | ||||||
|                 route  "/preferences/save"  Handlers.SmallGroup.savePreferences |                     [ routef "/%O/delete" Handlers.PrayerRequest.delete | ||||||
|                 route  "/save"              Handlers.SmallGroup.save ] |                       route "/save" Handlers.PrayerRequest.save ] | ||||||
|             subRoute "/user" [ |                 subRoute | ||||||
|                 routef "/%O/delete"         Handlers.User.delete |                     "/small-group" | ||||||
|                 route  "/edit/save"         Handlers.User.save |                     [ route "/announcement/send" Handlers.SmallGroup.sendAnnouncement | ||||||
|                 route  "/log-on"            Handlers.User.doLogOn |                       routef "/%O/delete" Handlers.SmallGroup.delete | ||||||
|                 route  "/password/change"   Handlers.User.changePassword |                       route "/log-on/submit" Handlers.SmallGroup.logOnSubmit | ||||||
|                 route  "/small-groups/save" Handlers.User.saveGroups ] ] ] |                       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 |     open Microsoft.Extensions.Logging | ||||||
| 
 | 
 | ||||||
| @ -175,53 +199,67 @@ module Configure = | |||||||
|     let errorHandler (ex: exn) (logger: ILogger) = |     let errorHandler (ex: exn) (logger: ILogger) = | ||||||
|         logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.") |         logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.") | ||||||
|         clearResponse >=> setStatusCode 500 >=> text ex.Message |         clearResponse >=> setStatusCode 500 >=> text ex.Message | ||||||
|      | 
 | ||||||
|     open Microsoft.Extensions.Hosting |     open Microsoft.Extensions.Hosting | ||||||
|      | 
 | ||||||
|     /// Configure logging |     /// Configure logging | ||||||
|     let logging (log: ILoggingBuilder) = |     let logging (log: ILoggingBuilder) = | ||||||
|         let env = log.Services.BuildServiceProvider().GetService<IWebHostEnvironment>() |         let env = log.Services.BuildServiceProvider().GetService<IWebHostEnvironment>() | ||||||
|         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 |         |> ignore | ||||||
|      | 
 | ||||||
|     open BitBadger.AspNetCore.CanonicalDomains |     open BitBadger.AspNetCore.CanonicalDomains | ||||||
|     open Microsoft.Extensions.Localization |     open Microsoft.Extensions.Localization | ||||||
|     open Microsoft.Extensions.Options |     open Microsoft.Extensions.Options | ||||||
|      | 
 | ||||||
|     /// Configure the application |     /// Configure the application | ||||||
|     let app (app: IApplicationBuilder) = |     let app (app: IApplicationBuilder) = | ||||||
|         let env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>() |         let env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>() | ||||||
|  | 
 | ||||||
|         if env.IsDevelopment() then |         if env.IsDevelopment() then | ||||||
|             app.UseDeveloperExceptionPage() |             app.UseDeveloperExceptionPage() | ||||||
|         else |         else | ||||||
|             app.UseGiraffeErrorHandler errorHandler |             app.UseGiraffeErrorHandler errorHandler | ||||||
|         |> ignore |         |> ignore | ||||||
|          | 
 | ||||||
|         let _ = app.UseForwardedHeaders() |         let _ = app.UseForwardedHeaders() | ||||||
|         let _ = app.UseCanonicalDomains() |         let _ = app.UseCanonicalDomains() | ||||||
|         let _ = app.UseStatusCodePagesWithReExecute "/error/{0}" |         let _ = app.UseStatusCodePagesWithReExecute "/error/{0}" | ||||||
|         let _ = app.UseStaticFiles() |         let _ = app.UseStaticFiles() | ||||||
|         let _ = app.UseCookiePolicy(CookiePolicyOptions(MinimumSameSitePolicy = SameSiteMode.Strict)) | 
 | ||||||
|  |         let _ = | ||||||
|  |             app.UseCookiePolicy(CookiePolicyOptions(MinimumSameSitePolicy = SameSiteMode.Strict)) | ||||||
|  | 
 | ||||||
|         let _ = app.UseMiddleware<RequestStartMiddleware>() |         let _ = app.UseMiddleware<RequestStartMiddleware>() | ||||||
|         let _ = app.UseRouting() |         let _ = app.UseRouting() | ||||||
|         let _ = app.UseSession() |         let _ = app.UseSession() | ||||||
|         let _ = app.UseRequestLocalization( | 
 | ||||||
|             app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>().Value) |         let _ = | ||||||
|  |             app.UseRequestLocalization(app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>().Value) | ||||||
|  | 
 | ||||||
|         let _ = app.UseAuthentication() |         let _ = app.UseAuthentication() | ||||||
|         let _ = app.UseAuthorization() |         let _ = app.UseAuthorization() | ||||||
|         let _ = app.UseEndpoints(fun e -> e.MapGiraffeEndpoints routes) |         let _ = app.UseEndpoints(fun e -> e.MapGiraffeEndpoints routes) | ||||||
|         app.ApplicationServices.GetRequiredService<IStringLocalizerFactory>() |> Views.I18N.setUpFactories  | 
 | ||||||
|  |         app.ApplicationServices.GetRequiredService<IStringLocalizerFactory>() | ||||||
|  |         |> Views.I18N.setUpFactories | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /// The web application | /// The web application | ||||||
| module App = | module App = | ||||||
|      | 
 | ||||||
|     open System.IO |     open System.IO | ||||||
| 
 | 
 | ||||||
|     [<EntryPoint>] |     [<EntryPoint>] | ||||||
|     let main args = |     let main args = | ||||||
|         let contentRoot = Directory.GetCurrentDirectory() |         let contentRoot = Directory.GetCurrentDirectory() | ||||||
|  | 
 | ||||||
|         let app = |         let app = | ||||||
|             WebHostBuilder() |             WebHostBuilder() | ||||||
|                 .UseContentRoot(contentRoot) |                 .UseContentRoot(contentRoot) | ||||||
| @ -232,5 +270,10 @@ module App = | |||||||
|                 .ConfigureLogging(Configure.logging) |                 .ConfigureLogging(Configure.logging) | ||||||
|                 .Configure(System.Action<IApplicationBuilder> Configure.app) |                 .Configure(System.Action<IApplicationBuilder> Configure.app) | ||||||
|                 .Build() |                 .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 |         0 | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ | |||||||
|     <PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.1.0" /> |     <PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.1.0" /> | ||||||
|     <PackageReference Include="Giraffe.Htmx" Version="2.0.4" /> |     <PackageReference Include="Giraffe.Htmx" Version="2.0.4" /> | ||||||
|     <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.1.0" /> |     <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.1.0" /> | ||||||
|     <PackageReference Update="FSharp.Core" Version="9.0.100" /> |     <PackageReference Update="FSharp.Core" Version="9.0.101" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user