Finish first cut of doc access (#55)
- Update paths for build.fs - Remove unused/unneeded deps
This commit is contained in:
		
							parent
							
								
									14b0a58d98
								
							
						
					
					
						commit
						05394b4461
					
				
							
								
								
									
										6
									
								
								build.fs
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								build.fs
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ let execContext = Context.FakeExecutionContext.Create false "build.fsx" [] | |||||||
| Context.setExecutionContext (Context.RuntimeContext.Fake execContext) | Context.setExecutionContext (Context.RuntimeContext.Fake execContext) | ||||||
| 
 | 
 | ||||||
| /// The root path to the projects within this solution | /// The root path to the projects within this solution | ||||||
| let projPath = "src/PrayerTracker" | let projPath = "src" | ||||||
| 
 | 
 | ||||||
| Target.create "Clean" (fun _ -> | Target.create "Clean" (fun _ -> | ||||||
|     !! "src/**/bin" |     !! "src/**/bin" | ||||||
| @ -16,7 +16,7 @@ Target.create "Clean" (fun _ -> | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| Target.create "Test" (fun _ -> | Target.create "Test" (fun _ -> | ||||||
|     let testPath = $"{projPath}.Tests" |     let testPath = $"{projPath}/Tests" | ||||||
|     DotNet.build (fun opts -> { opts with NoLogo = true }) $"{testPath}/PrayerTracker.Tests.fsproj" |     DotNet.build (fun opts -> { opts with NoLogo = true }) $"{testPath}/PrayerTracker.Tests.fsproj" | ||||||
|     Testing.Expecto.run |     Testing.Expecto.run | ||||||
|         (fun opts -> { opts with WorkingDirectory = $"{testPath}/bin/Release/net9.0" }) |         (fun opts -> { opts with WorkingDirectory = $"{testPath}/bin/Release/net9.0" }) | ||||||
| @ -25,7 +25,7 @@ Target.create "Test" (fun _ -> | |||||||
| Target.create "Publish" (fun _ -> | Target.create "Publish" (fun _ -> | ||||||
|     DotNet.publish |     DotNet.publish | ||||||
|         (fun opts -> { opts with Runtime = Some "linux-x64"; SelfContained = Some false; NoLogo = true }) |         (fun opts -> { opts with Runtime = Some "linux-x64"; SelfContained = Some false; NoLogo = true }) | ||||||
|         $"{projPath}/PrayerTracker.fsproj") |         $"{projPath}/PrayerTracker/PrayerTracker.fsproj") | ||||||
| 
 | 
 | ||||||
| Target.create "All" ignore | Target.create "All" ignore | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -90,7 +90,7 @@ module Connection = | |||||||
|                     member _.Deserialize<'T>(it: string) = |                     member _.Deserialize<'T>(it: string) = | ||||||
|                         JsonSerializer.Deserialize<'T>(it, Json.options) } |                         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 name FROM sqlite_master WHERE type = 'table'" [] _.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 | ||||||
| @ -115,26 +115,6 @@ module Connection = | |||||||
| 
 | 
 | ||||||
| open Microsoft.Data.Sqlite | open Microsoft.Data.Sqlite | ||||||
| 
 | 
 | ||||||
| /// Helper functions for the PostgreSQL data implementation |  | ||||||
| [<AutoOpen>] |  | ||||||
| module private Helpers = |  | ||||||
| 
 |  | ||||||
|     /// 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<Instant> "entered_date" |  | ||||||
|           UpdatedDate = row.fieldValue<Instant> "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") } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| open Npgsql |  | ||||||
| 
 |  | ||||||
| /// Functions to retrieve small group information | /// Functions to retrieve small group information | ||||||
| module SmallGroups = | module SmallGroups = | ||||||
| 
 | 
 | ||||||
| @ -317,8 +297,9 @@ module PrayerRequests = | |||||||
|     /// Central place to append sort criteria for prayer request queries |     /// Central place to append sort criteria for prayer request queries | ||||||
|     let private orderBy sort = |     let private orderBy sort = | ||||||
|         match sort with |         match sort with | ||||||
|         | SortByDate -> "updated_date DESC, entered_date DESC, requestor" |         | SortByDate -> [ Field.Named "updatedDate DESC"; Field.Named "enteredDate DESC"; Field.Named "requestor" ] | ||||||
|         | SortByRequestor -> "requestor, updated_date DESC, entered_date DESC" |         | SortByRequestor -> [ Field.Named "requestor"; Field.Named "updatedDate DESC"; Field.Named "enteredDate DESC" ] | ||||||
|  |         |> fun fields -> Query.orderBy fields SQLite | ||||||
| 
 | 
 | ||||||
|     /// Paginate a prayer request query |     /// Paginate a prayer request query | ||||||
|     let private paginate (pageNbr: int) pageSize = |     let private paginate (pageNbr: int) pageSize = | ||||||
| @ -328,13 +309,11 @@ module PrayerRequests = | |||||||
|             "" |             "" | ||||||
| 
 | 
 | ||||||
|     /// Count the number of prayer requests for a church |     /// Count the number of prayer requests for a church | ||||||
|     let countByChurch (churchId: ChurchId) = |     let countByChurch churchId = | ||||||
|         BitBadger.Documents.Postgres.Custom.scalar |         backgroundTask { | ||||||
|             "SELECT COUNT(id) AS req_count |             let! groupIds = SmallGroups.groupIdsByChurch churchId | ||||||
|                FROM pt.prayer_request |             return! Count.byFields Table.Request All [ Field.In "smallGroupId" groupIds ] | ||||||
|               WHERE small_group_id IN (SELECT id FROM pt.small_group WHERE church_id = @churchId)" |         } | ||||||
|             [ "@churchId", Sql.uuid churchId.Value ] |  | ||||||
|             (fun row -> row.int "req_count") |  | ||||||
| 
 | 
 | ||||||
|     /// Count the number of prayer requests for a small group |     /// Count the number of prayer requests for a small group | ||||||
|     let countByGroup (groupId: SmallGroupId) = |     let countByGroup (groupId: SmallGroupId) = | ||||||
| @ -347,52 +326,50 @@ module PrayerRequests = | |||||||
|     let forGroup (opts: PrayerRequestOptions) = |     let forGroup (opts: PrayerRequestOptions) = | ||||||
|         let theDate = defaultArg opts.ListDate (opts.SmallGroup.LocalDateNow opts.Clock) |         let theDate = defaultArg opts.ListDate (opts.SmallGroup.LocalDateNow opts.Clock) | ||||||
| 
 | 
 | ||||||
|         let where, parameters = |         let sql, parameters = | ||||||
|             if opts.ActiveOnly then |             if opts.ActiveOnly then | ||||||
|                 let asOf = |                 let expDate = | ||||||
|                     NpgsqlParameter( |                     (theDate.AtStartOfDayInZone(opts.SmallGroup.TimeZone) | ||||||
|                         "@asOf", |  | ||||||
|                         (theDate.AtStartOfDayInZone(opts.SmallGroup.TimeZone) |  | ||||||
|                          - Duration.FromDays opts.SmallGroup.Preferences.DaysToExpire) |                          - Duration.FromDays opts.SmallGroup.Preferences.DaysToExpire) | ||||||
|                             .ToInstant() |                         .ToInstant() | ||||||
|                     ) |                 $"""AND (   data->>'updatedDate'  > :updatedDate | ||||||
| 
 |                          OR data->>'expiration'   = :expManual | ||||||
|                 "   AND (   updated_date > @asOf |                          OR data->>'requestType' IN (:typLongTerm, :typExpecting)) | ||||||
|                          OR expiration   = @manual |                     AND data->>'expiration' <> :expForced""", | ||||||
|                          OR request_type = @longTerm |                 [ SqliteParameter(":updatedDate", expDate) | ||||||
|                          OR request_type = @expecting) |                   SqliteParameter(":expManual", string Manual) | ||||||
|                     AND expiration <> @forced", |                   SqliteParameter(":typLongTerm", string LongTermRequest) | ||||||
|                 [ "@asOf", Sql.parameter asOf |                   SqliteParameter(":typExpecting", string Expecting) | ||||||
|                   "@manual", Sql.string (string Manual) |                   SqliteParameter(":expForced", string Forced) ] | ||||||
|                   "@longTerm", Sql.string (string LongTermRequest) |  | ||||||
|                   "@expecting", Sql.string (string Expecting) |  | ||||||
|                   "@forced", Sql.string (string Forced) ] |  | ||||||
|             else |             else | ||||||
|                 "", [] |                 "", [] | ||||||
| 
 | 
 | ||||||
|         BitBadger.Documents.Postgres.Custom.list |         Custom.list | ||||||
|             $"SELECT * |             $"SELECT data FROM {Table.Request} | ||||||
|                 FROM pt.prayer_request |                WHERE data->>'smallGroupId = :groupId {sql} | ||||||
|                WHERE small_group_id = @groupId {where} |  | ||||||
|                ORDER BY {orderBy opts.SmallGroup.Preferences.RequestSort} |                ORDER BY {orderBy opts.SmallGroup.Preferences.RequestSort} | ||||||
|                {paginate opts.PageNumber opts.SmallGroup.Preferences.PageSize}" |                {paginate opts.PageNumber opts.SmallGroup.Preferences.PageSize}" | ||||||
|             (("@groupId", Sql.uuid opts.SmallGroup.Id.Value) :: parameters) |             (SqliteParameter(":groupId", string opts.SmallGroup.Id) :: parameters) | ||||||
|             mapToPrayerRequest |             fromData<PrayerRequest> | ||||||
| 
 | 
 | ||||||
|     /// Save a prayer request |     /// Save a prayer request | ||||||
|     let save req = save<PrayerRequest> Table.Request req |     let save req = save<PrayerRequest> Table.Request req | ||||||
| 
 | 
 | ||||||
|     /// Search prayer requests for the given term |     /// Search prayer requests for the given term | ||||||
|     let searchForGroup group searchTerm pageNbr = |     let searchForGroup group searchTerm pageNbr = | ||||||
|         BitBadger.Documents.Postgres.Custom.list |         Custom.list | ||||||
|             $"SELECT * FROM pt.prayer_request WHERE small_group_id = @groupId AND request_text ILIKE @search |             $"SELECT data FROM {Table.Request} | ||||||
|  |                WHERE data->>'smallGroupId' = :groupId | ||||||
|  |                  AND data->>'requestText' LIKE :search | ||||||
|                   UNION |                   UNION | ||||||
|               SELECT * FROM pt.prayer_request WHERE small_group_id = @groupId AND COALESCE(requestor, '') ILIKE @search |               SELECT data FROM {Table.Request} | ||||||
|  |                WHERE data->>'smallGroupId' = :groupId | ||||||
|  |                  AND COALESCE(data->>'requestor', '') LIKE :search | ||||||
|               ORDER BY {orderBy group.Preferences.RequestSort} |               ORDER BY {orderBy group.Preferences.RequestSort} | ||||||
|               {paginate pageNbr group.Preferences.PageSize}" |               {paginate pageNbr group.Preferences.PageSize}" | ||||||
|             [ "@groupId", Sql.uuid group.Id.Value |             [ SqliteParameter(":groupId", string group.Id) | ||||||
|               "@search", Sql.string $"%%%s{searchTerm}%%" ] |               SqliteParameter(":search", $"%%%s{searchTerm}%%") ] | ||||||
|             mapToPrayerRequest |             fromData<PrayerRequest> | ||||||
| 
 | 
 | ||||||
|     /// Retrieve a prayer request by its ID |     /// Retrieve a prayer request by its ID | ||||||
|     let tryById reqId = |     let tryById reqId = | ||||||
|  | |||||||
| @ -1,192 +0,0 @@ | |||||||
| namespace PrayerTracker.Data |  | ||||||
| 
 |  | ||||||
| open System.Threading |  | ||||||
| open System.Threading.Tasks |  | ||||||
| open Microsoft.Extensions.Caching.Distributed |  | ||||||
| open NodaTime |  | ||||||
| open Npgsql |  | ||||||
| 
 |  | ||||||
| /// Helper types and functions for the cache |  | ||||||
| [<AutoOpen>] |  | ||||||
| module private CacheHelpers = |  | ||||||
|      |  | ||||||
|     open System |  | ||||||
|      |  | ||||||
|     /// The cache entry |  | ||||||
|     type Entry = |  | ||||||
|         {   /// The ID of the cache entry |  | ||||||
|             Id : string |  | ||||||
|              |  | ||||||
|             /// The value to be cached |  | ||||||
|             Payload : byte[] |  | ||||||
|              |  | ||||||
|             /// When this entry will expire |  | ||||||
|             ExpireAt : Instant |  | ||||||
|              |  | ||||||
|             /// The duration by which the expiration should be pushed out when being refreshed |  | ||||||
|             SlidingExpiration : Duration option |  | ||||||
|              |  | ||||||
|             /// The must-expire-by date/time for the cache entry |  | ||||||
|             AbsoluteExpiration : Instant option |  | ||||||
|         } |  | ||||||
|      |  | ||||||
|     /// Run a task synchronously |  | ||||||
|     let sync<'T> (it : Task<'T>) = it |> (Async.AwaitTask >> Async.RunSynchronously) |  | ||||||
|      |  | ||||||
|     /// Get the current instant |  | ||||||
|     let getNow () = SystemClock.Instance.GetCurrentInstant () |  | ||||||
|      |  | ||||||
|     /// Create a parameter for the expire-at time |  | ||||||
|     let expireParam (it : Instant) = |  | ||||||
|         "@expireAt", Sql.parameter (NpgsqlParameter ("@expireAt", it)) |  | ||||||
|      |  | ||||||
|     /// Create a parameter for a possibly-missing NodaTime type |  | ||||||
|     let optParam<'T> name (it : 'T option) = |  | ||||||
|         let p = NpgsqlParameter ($"@%s{name}", if Option.isSome it then box it.Value else DBNull.Value) |  | ||||||
|         p.ParameterName, Sql.parameter p |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| open BitBadger.Documents.Postgres |  | ||||||
| 
 |  | ||||||
| /// A distributed cache implementation in PostgreSQL used to handle sessions for myWebLog |  | ||||||
| type DistributedCache () = |  | ||||||
|      |  | ||||||
|     // ~~~ INITIALIZATION ~~~ |  | ||||||
|      |  | ||||||
|     do |  | ||||||
|         task { |  | ||||||
|             let! exists = |  | ||||||
|                 Custom.scalar |  | ||||||
|                     $"SELECT EXISTS |  | ||||||
|                           (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'session') |  | ||||||
|                         AS does_exist" |  | ||||||
|                     [] (fun row -> row.bool "does_exist") |  | ||||||
|             if not exists then |  | ||||||
|                 do! Custom.nonQuery |  | ||||||
|                         "CREATE TABLE session ( |  | ||||||
|                             id                  TEXT        NOT NULL PRIMARY KEY, |  | ||||||
|                             payload             BYTEA       NOT NULL, |  | ||||||
|                             expire_at           TIMESTAMPTZ NOT NULL, |  | ||||||
|                             sliding_expiration  INTERVAL, |  | ||||||
|                             absolute_expiration TIMESTAMPTZ); |  | ||||||
|                         CREATE INDEX idx_session_expiration ON session (expire_at)" [] |  | ||||||
|         } |> sync |  | ||||||
|      |  | ||||||
|     // ~~~ SUPPORT FUNCTIONS ~~~ |  | ||||||
|      |  | ||||||
|     /// Get an entry, updating it for sliding expiration |  | ||||||
|     let getEntry key = backgroundTask { |  | ||||||
|         let idParam = "@id", Sql.string key |  | ||||||
|         let! tryEntry = |  | ||||||
|             Custom.single "SELECT * FROM session WHERE id = @id" [ idParam ] |  | ||||||
|                           (fun row -> |  | ||||||
|                               {   Id                 = row.string                     "id" |  | ||||||
|                                   Payload            = row.bytea                      "payload" |  | ||||||
|                                   ExpireAt           = row.fieldValue<Instant>        "expire_at" |  | ||||||
|                                   SlidingExpiration  = row.fieldValueOrNone<Duration> "sliding_expiration" |  | ||||||
|                                   AbsoluteExpiration = row.fieldValueOrNone<Instant>  "absolute_expiration"   }) |  | ||||||
|         match tryEntry with |  | ||||||
|         | Some entry -> |  | ||||||
|             let now      = getNow () |  | ||||||
|             let slideExp = defaultArg entry.SlidingExpiration  Duration.MinValue |  | ||||||
|             let absExp   = defaultArg entry.AbsoluteExpiration Instant.MinValue |  | ||||||
|             let needsRefresh, item = |  | ||||||
|                 if entry.ExpireAt = absExp then false, entry |  | ||||||
|                 elif slideExp = Duration.MinValue && absExp = Instant.MinValue then false, entry |  | ||||||
|                 elif absExp > Instant.MinValue && entry.ExpireAt.Plus slideExp > absExp then |  | ||||||
|                     true, { entry with ExpireAt = absExp } |  | ||||||
|                 else true, { entry with ExpireAt = now.Plus slideExp } |  | ||||||
|             if needsRefresh then |  | ||||||
|                 do! Custom.nonQuery "UPDATE session SET expire_at = @expireAt WHERE id = @id" |  | ||||||
|                                     [ expireParam item.ExpireAt; idParam ] |  | ||||||
|             return if item.ExpireAt > now then Some entry else None |  | ||||||
|         | None -> return None |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     /// The last time expired entries were purged (runs every 30 minutes) |  | ||||||
|     let mutable lastPurge = Instant.MinValue |  | ||||||
|      |  | ||||||
|     /// Purge expired entries every 30 minutes |  | ||||||
|     let purge () = backgroundTask { |  | ||||||
|         let now = getNow () |  | ||||||
|         if lastPurge.Plus (Duration.FromMinutes 30L) < now then |  | ||||||
|             do! Custom.nonQuery "DELETE FROM session WHERE expire_at < @expireAt" [ expireParam now ] |  | ||||||
|             lastPurge <- now |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     /// Remove a cache entry |  | ||||||
|     let removeEntry key = |  | ||||||
|         Custom.nonQuery "DELETE FROM session WHERE id = @id" [ "@id", Sql.string key ] |  | ||||||
|      |  | ||||||
|     /// Save an entry |  | ||||||
|     let saveEntry (opts : DistributedCacheEntryOptions) key payload = |  | ||||||
|         let now = getNow () |  | ||||||
|         let expireAt, slideExp, absExp = |  | ||||||
|             if opts.SlidingExpiration.HasValue then |  | ||||||
|                 let slide = Duration.FromTimeSpan opts.SlidingExpiration.Value |  | ||||||
|                 now.Plus slide, Some slide, None |  | ||||||
|             elif opts.AbsoluteExpiration.HasValue then |  | ||||||
|                 let exp = Instant.FromDateTimeOffset opts.AbsoluteExpiration.Value |  | ||||||
|                 exp, None, Some exp |  | ||||||
|             elif opts.AbsoluteExpirationRelativeToNow.HasValue then |  | ||||||
|                 let exp = now.Plus (Duration.FromTimeSpan opts.AbsoluteExpirationRelativeToNow.Value) |  | ||||||
|                 exp, None, Some exp |  | ||||||
|             else |  | ||||||
|                 // Default to 2 hour sliding expiration |  | ||||||
|                 let slide = Duration.FromHours 2 |  | ||||||
|                 now.Plus slide, Some slide, None |  | ||||||
|         Custom.nonQuery |  | ||||||
|             "INSERT INTO session ( |  | ||||||
|                 id, payload, expire_at, sliding_expiration, absolute_expiration |  | ||||||
|             ) VALUES ( |  | ||||||
|                 @id, @payload, @expireAt, @slideExp, @absExp |  | ||||||
|             ) ON CONFLICT (id) DO UPDATE |  | ||||||
|             SET payload             = EXCLUDED.payload, |  | ||||||
|                 expire_at           = EXCLUDED.expire_at, |  | ||||||
|                 sliding_expiration  = EXCLUDED.sliding_expiration, |  | ||||||
|                 absolute_expiration = EXCLUDED.absolute_expiration" |  | ||||||
|             [   "@id",      Sql.string key |  | ||||||
|                 "@payload", Sql.bytea payload |  | ||||||
|                 expireParam expireAt |  | ||||||
|                 optParam "slideExp" slideExp |  | ||||||
|                 optParam "absExp"   absExp ] |  | ||||||
|          |  | ||||||
|     // ~~~ IMPLEMENTATION FUNCTIONS ~~~ |  | ||||||
|      |  | ||||||
|     /// Retrieve the data for a cache entry |  | ||||||
|     let get key (_ : CancellationToken) = backgroundTask { |  | ||||||
|         match! getEntry key with |  | ||||||
|         | Some entry -> |  | ||||||
|             do! purge () |  | ||||||
|             return entry.Payload |  | ||||||
|         | None -> return null |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     /// Refresh an entry |  | ||||||
|     let refresh key (cancelToken : CancellationToken) = backgroundTask { |  | ||||||
|         let! _ = get key cancelToken |  | ||||||
|         () |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     /// Remove an entry |  | ||||||
|     let remove key (_ : CancellationToken) = backgroundTask { |  | ||||||
|         do! removeEntry key |  | ||||||
|         do! purge () |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     /// Set an entry |  | ||||||
|     let set key value options (_ : CancellationToken) = backgroundTask { |  | ||||||
|         do! saveEntry options key value |  | ||||||
|         do! purge () |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     interface IDistributedCache with |  | ||||||
|         member this.Get key = get key CancellationToken.None |> sync |  | ||||||
|         member this.GetAsync (key, token) = get key token |  | ||||||
|         member this.Refresh key = refresh key CancellationToken.None |> sync |  | ||||||
|         member this.RefreshAsync (key, token) = refresh key token |  | ||||||
|         member this.Remove key = remove key CancellationToken.None |> sync |  | ||||||
|         member this.RemoveAsync (key, token) = remove key token |  | ||||||
|         member this.Set (key, value, options) = set key value options CancellationToken.None |> sync |  | ||||||
|         member this.SetAsync (key, value, options, token) = set key value options token |  | ||||||
| 
 |  | ||||||
| @ -3,17 +3,13 @@ | |||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <Compile Include="Entities.fs" /> |     <Compile Include="Entities.fs" /> | ||||||
|     <Compile Include="Access.fs" /> |     <Compile Include="Access.fs" /> | ||||||
|     <Compile Include="DistributedCache.fs" /> |  | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="BitBadger.Documents.Postgres" Version="3.1.0" /> |  | ||||||
|     <PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.1" /> |     <PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.1" /> | ||||||
|     <PackageReference Include="Giraffe" Version="7.0.2" /> |     <PackageReference Include="Giraffe" Version="7.0.2" /> | ||||||
|     <PackageReference Include="NodaTime" Version="3.2.1" /> |     <PackageReference Include="NodaTime" Version="3.2.1" /> | ||||||
|     <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.NodaTime" Version="8.0.3" /> |  | ||||||
|     <PackageReference Update="FSharp.Core" Version="9.0.101" /> |     <PackageReference Update="FSharp.Core" Version="9.0.101" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -40,10 +40,9 @@ module Configure = | |||||||
|     open BitBadger.Documents.Sqlite |     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.DependencyInjection |     open Microsoft.Extensions.DependencyInjection | ||||||
|  |     open NeoSmart.Caching.Sqlite | ||||||
|     open NodaTime |     open NodaTime | ||||||
|     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) | ||||||
| @ -85,7 +84,8 @@ module Configure = | |||||||
|         if (emailCfg.GetChildren >> Seq.isEmpty >> not) () then |         if (emailCfg.GetChildren >> Seq.isEmpty >> not) () then | ||||||
|             ConfigurationBinder.Bind(emailCfg, Email.smtpOptions) |             ConfigurationBinder.Bind(emailCfg, Email.smtpOptions) | ||||||
| 
 | 
 | ||||||
|         let _ = svc.AddSingleton<IDistributedCache, DistributedCache>() |         let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SessionDB")) "./data/session.db" | ||||||
|  |         let _ = svc.AddSqliteCache(fun o -> o.CachePath <- cachePath) | ||||||
|         let _ = svc.AddSession() |         let _ = svc.AddSession() | ||||||
|         let _ = svc.AddAntiforgery() |         let _ = svc.AddAntiforgery() | ||||||
|         let _ = svc.AddRouting() |         let _ = svc.AddRouting() | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ let private findStats churchId = task { | |||||||
|     let! groups   = SmallGroups.countByChurch    churchId |     let! groups   = SmallGroups.countByChurch    churchId | ||||||
|     let! requests = PrayerRequests.countByChurch churchId |     let! requests = PrayerRequests.countByChurch churchId | ||||||
|     let! users    = Users.countByChurch          churchId |     let! users    = Users.countByChurch          churchId | ||||||
|     return shortGuid churchId.Value, { SmallGroups = int groups; PrayerRequests = requests; Users = int users } |     return shortGuid churchId.Value, { SmallGroups = int groups; PrayerRequests = int requests; Users = int users } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // POST /church/[church-id]/delete | // POST /church/[church-id]/delete | ||||||
|  | |||||||
| @ -1,29 +1,25 @@ | |||||||
| [<AutoOpen>] | [<AutoOpen>] | ||||||
| module PrayerTracker.Extensions | module PrayerTracker.Extensions | ||||||
| 
 | 
 | ||||||
|  | open BitBadger.Documents | ||||||
| open Microsoft.AspNetCore.Http | open Microsoft.AspNetCore.Http | ||||||
| open Newtonsoft.Json |  | ||||||
| open NodaTime | open NodaTime | ||||||
| open NodaTime.Serialization.JsonNet |  | ||||||
| open PrayerTracker.Data | open PrayerTracker.Data | ||||||
| open PrayerTracker.Entities | open PrayerTracker.Entities | ||||||
| open PrayerTracker.ViewModels | open PrayerTracker.ViewModels | ||||||
| 
 | 
 | ||||||
| /// JSON.NET serializer settings for NodaTime |  | ||||||
| let private jsonSettings = JsonSerializerSettings().ConfigureForNodaTime DateTimeZoneProviders.Tzdb |  | ||||||
| 
 |  | ||||||
| /// Extensions on the .NET session object | /// Extensions on the .NET session object | ||||||
| type ISession with | type ISession with | ||||||
| 
 | 
 | ||||||
|     /// Set an object in the session |     /// Set an object in the session | ||||||
|     member this.SetObject<'T> key (value: 'T) = |     member this.SetObject<'T> key (value: 'T) = | ||||||
|         this.SetString(key, JsonConvert.SerializeObject(value, jsonSettings)) |         this.SetString(key, (Configuration.serializer ()).Serialize value) | ||||||
| 
 | 
 | ||||||
|     /// Get an object from the session |     /// Get an object from the session | ||||||
|     member this.TryGetObject<'T> key = |     member this.TryGetObject<'T> key = | ||||||
|         match this.GetString key with |         match this.GetString key with | ||||||
|         | null -> None |         | null -> None | ||||||
|         | v -> Some (JsonConvert.DeserializeObject<'T>(v, jsonSettings)) |         | v -> Some ((Configuration.serializer ()).Deserialize<'T> v) | ||||||
| 
 | 
 | ||||||
|     /// The currently logged on small group |     /// The currently logged on small group | ||||||
|     member this.CurrentGroup |     member this.CurrentGroup | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ | |||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <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="NeoSmart.Caching.Sqlite.AspNetCore" Version="9.0.0" /> | ||||||
|     <PackageReference Update="FSharp.Core" Version="9.0.101" /> |     <PackageReference Update="FSharp.Core" Version="9.0.101" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,11 +19,6 @@ | |||||||
|     <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.10.0" /> |     <PackageReference Include="MailKit" Version="4.10.0" /> | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="2.3.0" /> |  | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" /> |  | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.3.0" /> |  | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.3.0" /> |  | ||||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> |  | ||||||
|     <PackageReference Update="FSharp.Core" Version="9.0.101" /> |     <PackageReference Update="FSharp.Core" Version="9.0.101" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user