WIP on NodaTime conversion (#41)

This commit is contained in:
Daniel J. Summers 2022-08-07 22:49:32 -04:00
parent 35db7aaaf5
commit e0d2be41d8
15 changed files with 147 additions and 112 deletions

View File

@ -2,6 +2,7 @@
module PrayerTracker.DataAccess
open System.Linq
open NodaTime
open PrayerTracker.Entities
[<AutoOpen>]
@ -24,7 +25,6 @@ module private Helpers =
if pageNbr > 0 then q.Skip((pageNbr - 1) * pageSize).Take pageSize else q
open System
open Microsoft.EntityFrameworkCore
open Microsoft.FSharpLu
@ -90,12 +90,14 @@ type AppDbContext with
/// Get all (or active) requests for a small group as of now or the specified date
member this.AllRequestsForSmallGroup (grp : SmallGroup) clock listDate activeOnly pageNbr = backgroundTask {
let theDate = match listDate with Some dt -> dt | _ -> grp.LocalDateNow clock
let theDate = match listDate with Some dt -> dt | _ -> SmallGroup.localDateNow clock grp
let query =
this.PrayerRequests.Where(fun req -> req.SmallGroupId = grp.Id)
|> function
| q when activeOnly ->
let asOf = DateTime (theDate.AddDays(-(float grp.Preferences.DaysToExpire)).Date.Ticks, DateTimeKind.Utc)
let asOf =
(theDate.AtStartOfDayInZone(SmallGroup.timeZone grp) - Duration.FromDays grp.Preferences.DaysToExpire)
.ToInstant ()
q.Where(fun req ->
( req.UpdatedDate > asOf
|| req.Expiration = Manual

View File

@ -1,7 +1,5 @@
namespace PrayerTracker.Entities
// fsharplint:disable RecordFieldNames MemberNames
(*-- SUPPORT TYPES --*)
/// How as-of dates should (or should not) be displayed with requests
@ -633,10 +631,10 @@ and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest =
SmallGroupId : SmallGroupId
/// The date/time on which this request was entered
EnteredDate : DateTime
EnteredDate : Instant
/// The date/time this request was last updated
UpdatedDate : DateTime
UpdatedDate : Instant
/// The name of the requestor or subject, or title of announcement
Requestor : string option
@ -664,8 +662,8 @@ with
RequestType = CurrentRequest
UserId = UserId Guid.Empty
SmallGroupId = SmallGroupId Guid.Empty
EnteredDate = DateTime.MinValue
UpdatedDate = DateTime.MinValue
EnteredDate = Instant.MinValue
UpdatedDate = Instant.MinValue
Requestor = None
Text = ""
NotifyChaplain = false
@ -674,20 +672,6 @@ with
Expiration = Automatic
}
/// Is this request expired?
member this.IsExpired (curr : DateTime) expDays =
match this.Expiration, this.RequestType with
| Forced, _ -> true
| Manual, _
| Automatic, LongTermRequest
| Automatic, Expecting -> false
| Automatic, _ -> curr.AddDays(-(float expDays)).Date > this.UpdatedDate.Date // Automatic expiration
/// Is an update required for this long-term request?
member this.UpdateRequired curr expDays updWeeks =
if this.IsExpired curr expDays then false
else curr.AddDays(-(float (updWeeks * 7))).Date > this.UpdatedDate.Date
/// Configure EF for this entity
static member internal ConfigureEF (mb : ModelBuilder) =
mb.Entity<PrayerRequest> (fun it ->
@ -759,19 +743,6 @@ with
Users = ResizeArray<UserSmallGroup> ()
}
/// Get the local date for this group
member this.LocalTimeNow (clock : IClock) =
if isNull clock then nullArg (nameof clock)
let tzId = TimeZoneId.toString this.Preferences.TimeZoneId
let tz =
if DateTimeZoneProviders.Tzdb.Ids.Contains tzId then DateTimeZoneProviders.Tzdb[tzId]
else DateTimeZone.Utc
clock.GetCurrentInstant().InZone(tz).ToDateTimeUnspecified ()
/// Get the local date for this group
member this.LocalDateNow clock =
(this.LocalTimeNow clock).Date
/// Configure EF for this entity
static member internal ConfigureEF (mb : ModelBuilder) =
mb.Entity<SmallGroup> (fun it ->
@ -855,7 +826,7 @@ and [<CLIMutable; NoComparison; NoEquality>] User =
Salt : Guid option
/// The last time the user was seen (set whenever the user is loaded into a session)
LastSeen : DateTime option
LastSeen : Instant option
/// The small groups which this user is authorized
SmallGroups : ResizeArray<UserSmallGroup>
@ -900,7 +871,7 @@ with
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.Salt)
.SetValueConverter (OptionConverter<Guid> ())
mb.Model.FindEntityType(typeof<User>).FindProperty(nameof User.empty.LastSeen)
.SetValueConverter (OptionConverter<DateTime> ())
.SetValueConverter (OptionConverter<Instant> ())
/// Cross-reference between user and small group
@ -948,3 +919,43 @@ with
mb.Model.FindEntityType(typeof<UserSmallGroup>).FindProperty(nameof UserSmallGroup.empty.SmallGroupId)
.SetValueConverter (Converters.SmallGroupIdConverter ())
/// Support functions for small groups
module SmallGroup =
/// The DateTimeZone for the time zone ID for this small group
let timeZone group =
let tzId = TimeZoneId.toString group.Preferences.TimeZoneId
if DateTimeZoneProviders.Tzdb.Ids.Contains tzId then DateTimeZoneProviders.Tzdb[tzId]
else DateTimeZone.Utc
/// Get the local date/time for this group
let localTimeNow (clock : IClock) group =
if isNull clock then nullArg (nameof clock)
clock.GetCurrentInstant().InZone(timeZone group).LocalDateTime
/// Get the local date for this group
let localDateNow clock group =
(localTimeNow clock group).Date
/// Support functions for prayer requests
module PrayerRequest =
/// Is this request expired?
let isExpired (asOf : LocalDate) group req =
match req.Expiration, req.RequestType with
| Forced, _ -> true
| Manual, _
| Automatic, LongTermRequest
| Automatic, Expecting -> false
| Automatic, _ ->
// Automatic expiration
asOf.PlusDays -group.Preferences.DaysToExpire > req.UpdatedDate.InZone(SmallGroup.timeZone group).Date
/// Is an update required for this long-term request?
let updateRequired asOf group req =
if isExpired asOf group req then false
else asOf.PlusWeeks -group.Preferences.LongTermUpdateWeeks
>= req.UpdatedDate.InZone(SmallGroup.timeZone group).Date

View File

@ -17,7 +17,7 @@
<PackageReference Include="Giraffe" Version="6.0.0" />
<PackageReference Include="Microsoft.FSharpLu" Version="0.11.7" />
<PackageReference Include="NodaTime" Version="3.1.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
<PackageReference Update="FSharp.Core" Version="6.0.5" />
</ItemGroup>

View File

@ -255,13 +255,13 @@ let private messages viewInfo =
|> List.singleton
open System
open NodaTime
/// Render the <footer> at the bottom of the page
let private htmlFooter viewInfo =
let s = I18N.localizer.Force ()
let imgText = $"""%O{s["PrayerTracker"]} %O{s["from Bit Badger Solutions"]}"""
let resultTime = TimeSpan(DateTime.Now.Ticks - viewInfo.RequestStart).TotalSeconds
let resultTime = (SystemClock.Instance.GetCurrentInstant () - viewInfo.RequestStart).TotalSeconds
footer [ _class "pt-footer" ] [
div [ _id "pt-legal" ] [
a [ _href "/legal/privacy-policy" ] [ locStr s["Privacy Policy"] ]

View File

@ -154,23 +154,22 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
let l = I18N.forView "Requests/Maintain"
use sw = new StringWriter ()
let raw = rawLocText sw
let now = model.SmallGroup.LocalDateNow (ctx.GetService<IClock> ())
let prefs = model.SmallGroup.Preferences
let group = model.SmallGroup
let now = SmallGroup.localDateNow (ctx.GetService<IClock> ()) group
let types = ReferenceList.requestTypeList s |> Map.ofList
let updReq (req : PrayerRequest) =
if req.UpdateRequired now prefs.DaysToExpire prefs.LongTermUpdateWeeks then "cell pt-request-update" else "cell"
|> _class
let reqExp (req : PrayerRequest) =
_class (if req.IsExpired now prefs.DaysToExpire then "cell pt-request-expired" else "cell")
let vi = AppViewInfo.withScopedStyles [ "#requestList { grid-template-columns: repeat(5, auto); }" ] viewInfo
let vi = AppViewInfo.withScopedStyles [ "#requestList { grid-template-columns: repeat(5, auto); }" ] viewInfo
/// Iterate the sequence once, before we render, so we can get the count of it at the top of the table
let requests =
model.Requests
|> List.map (fun req ->
let reqId = shortGuid req.Id.Value
let reqText = htmlToPlainText req.Text
let delAction = $"/prayer-request/{reqId}/delete"
let delPrompt =
let updateClass =
_class (if PrayerRequest.updateRequired now group req then "cell pt-request-update" else "cell")
let isExpired = PrayerRequest.isExpired now group req
let expiredClass = _class (if isExpired then "cell pt-request-expired" else "cell")
let reqId = shortGuid req.Id.Value
let reqText = htmlToPlainText req.Text
let delAction = $"/prayer-request/{reqId}/delete"
let delPrompt =
[ s["Are you sure you want to delete this {0}? This action cannot be undone.",
s["Prayer Request"].Value.ToLower() ].Value
"\\n"
@ -183,7 +182,7 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
a [ _href $"/prayer-request/{reqId}/edit"; _title l["Edit This Prayer Request"].Value ] [
iconSized 18 "edit"
]
if req.IsExpired now prefs.DaysToExpire then
if isExpired then
a [ _href $"/prayer-request/{reqId}/restore"
_title l["Restore This Inactive Request"].Value ] [
iconSized 18 "visibility"
@ -200,11 +199,11 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
iconSized 18 "delete_forever"
]
]
div [ updReq req ] [
div [ updateClass ] [
str (req.UpdatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture))
]
div [ _class "cell" ] [ locStr types[req.RequestType] ]
div [ reqExp req ] [ str (match req.Requestor with Some r -> r | None -> " ") ]
div [ expiredClass ] [ str (match req.Requestor with Some r -> r | None -> " ") ]
div [ _class "cell" ] [
match reqText.Length with
| len when len < 60 -> rawText reqText
@ -316,7 +315,7 @@ let view model viewInfo =
let s = I18N.localizer.Force ()
let pageTitle = $"""{s["Prayer Requests"].Value} {model.SmallGroup.Name}"""
let spacer = rawText " &nbsp; &nbsp; &nbsp; "
let dtString = model.Date.ToString "yyyy-MM-dd"
let dtString = model.Date.ToString ("yyyy-MM-dd", null) // TODO: this should be invariant
[ div [ _class "pt-center-text" ] [
br []
a [ _class "pt-icon-link"
@ -327,12 +326,12 @@ let view model viewInfo =
]
if model.CanEmail then
spacer
if model.Date.DayOfWeek <> DayOfWeek.Sunday then
let rec findSunday (date : DateTime) =
if date.DayOfWeek = DayOfWeek.Sunday then date else findSunday (date.AddDays 1.)
if model.Date.DayOfWeek <> IsoDayOfWeek.Sunday then
let rec findSunday (date : LocalDate) =
if date.DayOfWeek = IsoDayOfWeek.Sunday then date else findSunday (date.PlusDays 1)
let sunday = findSunday model.Date
a [ _class "pt-icon-link"
_href $"""/prayer-requests/view/{sunday.ToString "yyyy-MM-dd"}"""
_href $"""/prayer-requests/view/{sunday.ToString ("yyyy-MM-dd", null)}""" // TODO: make invariant
_title s["List for Next Sunday"].Value ] [
icon "update"; rawText " &nbsp;"; locStr s["List for Next Sunday"]
]

View File

@ -217,7 +217,10 @@ let maintain (users : User list) ctx viewInfo =
]
div [ _class "cell" ] [ str user.Name ]
div [ _class "cell" ] [
str (match user.LastSeen with Some dt -> dt.ToString s["MMMM d, yyyy"] | None -> "--")
match user.LastSeen with
| Some dt -> dt.ToString (s["MMMM d, yyyy"].Value, null)
| None -> "--"
|> str
]
div [ _class "cell pt-center-text" ] [
if user.IsAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"]

View File

@ -116,7 +116,7 @@ type LayoutType =
| ContentOnly
open System
open NodaTime
/// View model required by the layout template, given as first parameter for all pages in PrayerTracker
[<NoComparison; NoEquality>]
@ -134,7 +134,7 @@ type AppViewInfo =
Version : string
/// The ticks when the request started
RequestStart : int64
RequestStart : Instant
/// The currently logged on user, if there is one
User : User option
@ -151,7 +151,6 @@ type AppViewInfo =
/// A JavaScript function to run on page load
OnLoadScript : string option
}
// TODO: add onload script option to this, modify layout to add it
/// Support for the AppViewInfo type
module AppViewInfo =
@ -162,7 +161,7 @@ module AppViewInfo =
HelpLink = None
Messages = []
Version = ""
RequestStart = DateTime.Now.Ticks
RequestStart = Instant.MinValue
User = None
Group = None
Layout = FullPage
@ -467,7 +466,7 @@ type EditRequest =
RequestType : string
/// The date of the request
EnteredDate : DateTime option
EnteredDate : string option
/// Whether to update the date or not
SkipDateUpdate : bool option
@ -724,6 +723,7 @@ module UserLogOn =
}
open System
open Giraffe.ViewEngine
/// This represents a list of requests
@ -732,7 +732,7 @@ type RequestList =
Requests : PrayerRequest list
/// The date for which this list is being generated
Date : DateTime
Date : LocalDate
/// The small group to which this list belongs
SmallGroup : SmallGroup
@ -767,30 +767,31 @@ with
/// Is this request new?
member this.IsNew (req : PrayerRequest) =
(this.Date - req.UpdatedDate).Days <= this.SmallGroup.Preferences.DaysToKeepNew
let reqDate = req.UpdatedDate.InZone(SmallGroup.timeZone this.SmallGroup).Date
Period.Between(this.Date, reqDate, PeriodUnits.Days).Days <= this.SmallGroup.Preferences.DaysToKeepNew
/// Generate this list as HTML
member this.AsHtml (s : IStringLocalizer) =
let prefs = this.SmallGroup.Preferences
let asOfSize = Math.Round (float prefs.TextFontSize * 0.8, 2)
let p = this.SmallGroup.Preferences
let asOfSize = Math.Round (float p.TextFontSize * 0.8, 2)
[ if this.ShowHeader then
div [ _style $"text-align:center;font-family:{prefs.Fonts}" ] [
span [ _style $"font-size:%i{prefs.HeadingFontSize}pt;" ] [
div [ _style $"text-align:center;font-family:{p.Fonts}" ] [
span [ _style $"font-size:%i{p.HeadingFontSize}pt;" ] [
strong [] [ str s["Prayer Requests"].Value ]
]
br []
span [ _style $"font-size:%i{prefs.TextFontSize}pt;" ] [
span [ _style $"font-size:%i{p.TextFontSize}pt;" ] [
strong [] [ str this.SmallGroup.Name ]
br []
str (this.Date.ToString s["MMMM d, yyyy"].Value)
str (this.Date.ToString (s["MMMM d, yyyy"].Value, null))
]
]
br []
for _, name, reqs in this.RequestsByType s do
div [ _style "padding-left:10px;padding-bottom:.5em;" ] [
table [ _style $"font-family:{prefs.Fonts};page-break-inside:avoid;" ] [
table [ _style $"font-family:{p.Fonts};page-break-inside:avoid;" ] [
tr [] [
td [ _style $"font-size:%i{prefs.HeadingFontSize}pt;color:{prefs.HeadingColor};padding:3px 0;border-top:solid 3px {prefs.LineColor};border-bottom:solid 3px {prefs.LineColor};font-weight:bold;" ] [
td [ _style $"font-size:%i{p.HeadingFontSize}pt;color:{p.HeadingColor};padding:3px 0;border-top:solid 3px {p.LineColor};border-bottom:solid 3px {p.LineColor};font-weight:bold;" ] [
rawText "&nbsp; &nbsp; "; str name.Value; rawText "&nbsp; &nbsp; "
]
]
@ -799,7 +800,7 @@ with
reqs
|> List.map (fun req ->
let bullet = if this.IsNew req then "circle" else "disc"
li [ _style $"list-style-type:{bullet};font-family:{prefs.Fonts};font-size:%i{prefs.TextFontSize}pt;padding-bottom:.25em;" ] [
li [ _style $"list-style-type:{bullet};font-family:{p.Fonts};font-size:%i{p.TextFontSize}pt;padding-bottom:.25em;" ] [
match req.Requestor with
| Some r when r <> "" ->
strong [] [ str r ]
@ -807,14 +808,14 @@ with
| Some _ -> ()
| None -> ()
rawText req.Text
match prefs.AsOfDateDisplay with
match p.AsOfDateDisplay with
| NoDisplay -> ()
| ShortDate
| LongDate ->
let dt =
match prefs.AsOfDateDisplay with
| ShortDate -> req.UpdatedDate.ToShortDateString ()
| LongDate -> req.UpdatedDate.ToLongDateString ()
match p.AsOfDateDisplay with
| ShortDate -> req.UpdatedDate.ToString ("d", null)
| LongDate -> req.UpdatedDate.ToString ("D", null)
| _ -> ""
i [ _style $"font-size:%.2f{asOfSize}pt" ] [
rawText "&nbsp; ("; str s["as of"].Value; str " "; str dt; rawText ")"
@ -830,7 +831,7 @@ with
seq {
this.SmallGroup.Name
s["Prayer Requests"].Value
this.Date.ToString s["MMMM d, yyyy"].Value
this.Date.ToString (s["MMMM d, yyyy"].Value, null)
" "
for _, name, reqs in this.RequestsByType s do
let dashes = String.replicate (name.Value.Length + 4) "-"
@ -845,8 +846,8 @@ with
| _ ->
let dt =
match this.SmallGroup.Preferences.AsOfDateDisplay with
| ShortDate -> req.UpdatedDate.ToShortDateString ()
| LongDate -> req.UpdatedDate.ToLongDateString ()
| ShortDate -> req.UpdatedDate.ToString ("d", null)
| LongDate -> req.UpdatedDate.ToString ("D", null)
| _ -> ""
$""" ({s["as of"].Value} {dt})"""
|> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.Text)

View File

@ -1,17 +1,17 @@
namespace PrayerTracker
open System
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] <- DateTime.Now.Ticks
ctx.Items[Key.startTime] <- ctx.Now
return! next.Invoke ctx
}
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
@ -71,9 +71,13 @@ module Configure =
let _ = svc.AddSingleton<IClock> SystemClock.Instance
let config = svc.BuildServiceProvider().GetRequiredService<IConfiguration> ()
//NpgsqlConnection.GlobalTypeMapper.
let _ =
svc.AddDbContext<AppDbContext> (
(fun options -> options.UseNpgsql (config.GetConnectionString "PrayerTracker") |> ignore),
(fun options ->
options.UseNpgsql (
config.GetConnectionString "PrayerTracker", fun o -> o.UseNodaTime () |> ignore)
|> ignore),
ServiceLifetime.Scoped, ServiceLifetime.Singleton)
()

View File

@ -44,6 +44,7 @@ let appVersion =
open Giraffe
open Giraffe.Htmx
open Microsoft.AspNetCore.Http
open NodaTime
open PrayerTracker
open PrayerTracker.ViewModels
@ -63,7 +64,7 @@ let viewInfo (ctx : HttpContext) =
{ AppViewInfo.fresh with
Version = appVersion
Messages = msg
RequestStart = ctx.Items[Key.startTime] :?> int64
RequestStart = ctx.Items[Key.startTime] :?> Instant
User = ctx.Session.CurrentUser
Group = ctx.Session.CurrentGroup
Layout = layout

View File

@ -1,7 +1,6 @@
[<AutoOpen>]
module PrayerTracker.Extensions
open System
open Microsoft.AspNetCore.Http
open Microsoft.FSharpLu
open Newtonsoft.Json
@ -76,6 +75,9 @@ type HttpContext with
/// The system clock (via DI)
member this.Clock = this.GetService<IClock> ()
/// The current instant
member this.Now = this.Clock.GetCurrentInstant ()
/// The currently logged on small group (sets the value in the session if it is missing)
member this.CurrentGroup () = task {
match this.Session.CurrentGroup with
@ -101,7 +103,7 @@ type HttpContext with
match! this.Db.TryUserById userId with
| Some user ->
// Set last seen for user
this.Db.UpdateEntry { user with LastSeen = Some DateTime.UtcNow }
this.Db.UpdateEntry { user with LastSeen = Some this.Now }
let! _ = this.Db.SaveChangesAsync ()
this.Session.CurrentUser <- Some user
return Some user

View File

@ -20,7 +20,7 @@ let private findRequest (ctx : HttpContext) reqId = task {
/// Generate a list of requests for the given date
let private generateRequestList (ctx : HttpContext) date = task {
let group = ctx.Session.CurrentGroup.Value
let listDate = match date with Some d -> d | None -> group.LocalDateNow ctx.Clock
let listDate = match date with Some d -> d | None -> SmallGroup.localDateNow ctx.Clock group
let! reqs = ctx.Db.AllRequestsForSmallGroup group ctx.Clock (Some listDate) true 0
return
{ Requests = reqs
@ -32,29 +32,31 @@ let private generateRequestList (ctx : HttpContext) date = task {
}
}
open System
open NodaTime.Text
/// Parse a string into a date (optionally, of course)
let private parseListDate (date : string option) =
match date with
| Some dt -> match DateTime.TryParse dt with true, d -> Some d | false, _ -> None
| Some dt -> match LocalDatePattern.Iso.Parse dt with it when it.Success -> Some it.Value | _ -> None
| None -> None
open System
/// GET /prayer-request/[request-id]/edit
let edit reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
let group = ctx.Session.CurrentGroup.Value
let now = group.LocalDateNow ctx.Clock
let now = SmallGroup.localDateNow ctx.Clock group
let requestId = PrayerRequestId reqId
if requestId.Value = Guid.Empty then
return!
{ viewInfo ctx with HelpLink = Some Help.editRequest }
|> Views.PrayerRequest.edit EditRequest.empty (now.ToString "yyyy-MM-dd") ctx
|> Views.PrayerRequest.edit EditRequest.empty (now.ToString ("R", null)) ctx
|> renderHtml next ctx
else
match! findRequest ctx requestId with
| Ok req ->
let s = Views.I18N.localizer.Force ()
if req.IsExpired now group.Preferences.DaysToExpire then
if PrayerRequest.isExpired now group req then
{ UserMessage.warning with
Text = htmlLocString s["This request is expired."]
Description =
@ -128,7 +130,7 @@ let list groupId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun ne
viewInfo ctx
|> Views.PrayerRequest.list
{ Requests = reqs
Date = group.LocalDateNow ctx.Clock
Date = SmallGroup.localDateNow ctx.Clock group
SmallGroup = group
ShowHeader = true
CanEmail = Option.isSome ctx.User.UserId
@ -199,7 +201,7 @@ let restore reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> tas
match! findRequest ctx requestId with
| Ok req ->
let s = Views.I18N.localizer.Force ()
ctx.Db.UpdateEntry { req with Expiration = Automatic; UpdatedDate = DateTime.Now }
ctx.Db.UpdateEntry { req with Expiration = Automatic; UpdatedDate = ctx.Now }
let! _ = ctx.Db.SaveChangesAsync ()
addInfo ctx s["Successfully {0} prayer request", s["Restored"].Value.ToLower ()]
return! redirectTo false "/prayer-requests" next ctx
@ -226,10 +228,13 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
Expiration = Expiration.fromCode model.Expiration
}
let group = ctx.Session.CurrentGroup.Value
let now = group.LocalDateNow ctx.Clock
let now = SmallGroup.localDateNow ctx.Clock group
match model.IsNew with
| true ->
let dt = defaultArg model.EnteredDate now
let dt =
(defaultArg (parseListDate model.EnteredDate) now)
.AtStartOfDayInZone(SmallGroup.timeZone group)
.ToInstant()
{ upd8 with
SmallGroupId = group.Id
UserId = ctx.User.UserId.Value
@ -237,7 +242,7 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
UpdatedDate = dt
}
| false when defaultArg model.SkipDateUpdate false -> upd8
| false -> { upd8 with UpdatedDate = now }
| false -> { upd8 with UpdatedDate = ctx.Now }
|> if model.IsNew then ctx.Db.AddEntry else ctx.Db.UpdateEntry
let! _ = ctx.Db.SaveChangesAsync ()
let s = Views.I18N.localizer.Force ()

View File

@ -27,8 +27,9 @@
<PackageReference Include="Giraffe" Version="6.0.0" />
<PackageReference Include="Giraffe.Htmx" Version="1.8.0" />
<PackageReference Include="NeoSmart.Caching.Sqlite" Version="6.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
<PackageReference Update="FSharp.Core" Version="6.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="6.0.6" />
</ItemGroup>
<ItemGroup>

View File

@ -261,14 +261,14 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
match! ctx.TryBindFormAsync<Announcement> () with
| Ok model ->
let group = ctx.Session.CurrentGroup.Value
let prefs = group.Preferences
let pref = group.Preferences
let usr = ctx.Session.CurrentUser.Value
let now = group.LocalTimeNow ctx.Clock
let now = SmallGroup.localTimeNow ctx.Clock group
let s = Views.I18N.localizer.Force ()
// Reformat the text to use the class's font stylings
let requestText = ckEditorToText model.Text
let htmlText =
p [ _style $"font-family:{prefs.Fonts};font-size:%d{prefs.TextFontSize}pt;" ] [ rawText requestText ]
p [ _style $"font-family:{pref.Fonts};font-size:%d{pref.TextFontSize}pt;" ] [ rawText requestText ]
|> renderHtmlNode
let plainText = (htmlToPlainText >> wordWrap 74) htmlText
// Send the e-mails
@ -282,7 +282,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
Recipients = recipients
Group = group
Subject = s["Announcement for {0} - {1:MMMM d, yyyy} {2}", group.Name, now.Date,
(now.ToString "h:mm tt").ToLower ()].Value
(now.ToString ("h:mm tt", null)).ToLower ()].Value
HtmlBody = htmlText
PlainTextBody = plainText
Strings = s
@ -293,14 +293,15 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
| _, None -> ()
| _, Some x when not x -> ()
| _, _ ->
let zone = SmallGroup.timeZone group
{ PrayerRequest.empty with
Id = (Guid.NewGuid >> PrayerRequestId) ()
SmallGroupId = group.Id
UserId = usr.Id
RequestType = (Option.get >> PrayerRequestType.fromCode) model.RequestType
Text = requestText
EnteredDate = now
UpdatedDate = now
EnteredDate = now.Date.AtStartOfDayInZone(zone).ToInstant()
UpdatedDate = now.InZoneLeniently(zone).ToInstant()
}
|> ctx.Db.AddEntry
let! _ = ctx.Db.SaveChangesAsync ()

View File

@ -145,7 +145,7 @@ let doLogOn : HttpHandler = requireAccess [ AccessLevel.Public ] >=> validateCsr
AuthenticationProperties (
IssuedUtc = DateTimeOffset.UtcNow,
IsPersistent = defaultArg model.RememberMe false))
ctx.Db.UpdateEntry { user with LastSeen = Some DateTime.UtcNow }
ctx.Db.UpdateEntry { user with LastSeen = Some ctx.Now }
let! _ = ctx.Db.SaveChangesAsync ()
addHtmlInfo ctx s["Log On Successful Welcome to {0}", s["PrayerTracker"]]
return! redirectTo false (sanitizeUrl model.RedirectUrl "/small-group") next ctx

View File

@ -94,7 +94,7 @@ ALTER TABLE pt."User" RENAME COLUMN "PasswordHash" TO password_hash;
ALTER TABLE pt."User" RENAME COLUMN "Salt" TO salt;
ALTER TABLE pt."User" RENAME CONSTRAINT "PK_User" TO pk_pt_user;
ALTER TABLE pt."User" RENAME TO pt_user;
ALTER TABLE pt.pt_user ADD COLUMN last_seen timestamp;
ALTER TABLE pt.pt_user ADD COLUMN last_seen timestamptz;
-- User / Small Group
ALTER TABLE pt."User_SmallGroup" RENAME COLUMN "UserId" TO user_id;
@ -105,3 +105,8 @@ ALTER TABLE pt."User_SmallGroup" RENAME CONSTRAINT "FK_User_SmallGroup_SmallGrou
ALTER TABLE pt."User_SmallGroup" RENAME TO user_small_group;
ALTER INDEX pt."IX_User_SmallGroup_SmallGroupId" RENAME TO ix_user_small_group_small_group_id;
-- #41 - change to timestamptz
SET TimeZone = 'UTC';
ALTER TABLE pt.prayer_request ALTER COLUMN entered_date TYPE timestamptz;
ALTER TABLE pt.prayer_request ALTER COLUMN updated_date TYPE timestamptz;