Move module funcs to properties

This commit is contained in:
Daniel J. Summers 2025-01-30 20:36:00 -05:00
parent facc294d66
commit 42e3a58131
12 changed files with 384 additions and 410 deletions

View File

@ -69,7 +69,7 @@ module private Helpers =
{ Id = SmallGroupId (row.uuid "id")
ChurchId = ChurchId (row.uuid "church_id")
Name = row.string "group_name"
Preferences = ListPreferences.empty
Preferences = ListPreferences.Empty
}
/// Map a row to a Small Group information set
@ -243,12 +243,12 @@ module PrayerRequests =
/// Get all (or active) requests for a small group as of now or the specified date
let forGroup (opts : PrayerRequestOptions) =
let theDate = defaultArg opts.ListDate (SmallGroup.localDateNow opts.Clock opts.SmallGroup)
let theDate = defaultArg opts.ListDate (opts.SmallGroup.LocalDateNow opts.Clock)
let where, parameters =
if opts.ActiveOnly then
let asOf = NpgsqlParameter (
"@asOf",
(theDate.AtStartOfDayInZone(SmallGroup.timeZone opts.SmallGroup)
(theDate.AtStartOfDayInZone(opts.SmallGroup.TimeZone)
- Duration.FromDays opts.SmallGroup.Preferences.DaysToExpire)
.ToInstant ())
" AND ( updated_date > @asOf
@ -458,7 +458,7 @@ module SmallGroups =
"@groupPassword", Sql.string pref.GroupPassword
"@defaultEmailType", Sql.string (string pref.DefaultEmailType)
"@isPublic", Sql.bool pref.IsPublic
"@timeZoneId", Sql.string (TimeZoneId.toString pref.TimeZoneId)
"@timeZoneId", Sql.string (string pref.TimeZoneId)
"@pageSize", Sql.int pref.PageSize
"@asOfDateDisplay", Sql.string (string pref.AsOfDateDisplay) ]

View File

@ -174,14 +174,11 @@ type SmallGroupId =
/// PK type for the TimeZone entity
type TimeZoneId = TimeZoneId of string
type TimeZoneId =
| TimeZoneId of string
/// Functions to support time zone IDs
module TimeZoneId =
/// Convert a time zone ID to its string value
let toString =
function
override this.ToString() =
match this with
| TimeZoneId it -> it
@ -259,12 +256,9 @@ type Church =
InterfaceAddress: string option
}
/// Functions to support churches
module Church =
/// An empty church
// aww... how sad :(
let empty =
static member Empty =
{ Id = ChurchId Guid.Empty
Name = ""
City = ""
@ -339,11 +333,8 @@ type ListPreferences =
else
this.Fonts
/// Functions to support list preferences
module ListPreferences =
/// A set of preferences with their default values
let empty =
static member Empty =
{ SmallGroupId = SmallGroupId Guid.Empty
DaysToExpire = 14
DaysToKeepNew = 7
@ -384,11 +375,8 @@ type Member =
Format: EmailFormat option
}
/// Functions to support small group members
module Member =
/// An empty member
let empty =
static member Empty =
{ Id = MemberId Guid.Empty
SmallGroupId = SmallGroupId Guid.Empty
Name = ""
@ -396,6 +384,50 @@ module Member =
Format = None }
/// This represents a small group (Sunday School class, Bible study group, etc.)
[<NoComparison; NoEquality>]
type SmallGroup =
{
/// The ID of this small group
Id: SmallGroupId
/// The church to which this group belongs
ChurchId: ChurchId
/// The name of the group
Name: string
/// The preferences for the request list
Preferences: ListPreferences
}
/// The DateTimeZone for the time zone ID for this small group
member this.TimeZone =
let tzId = string this.Preferences.TimeZoneId
if DateTimeZoneProviders.Tzdb.Ids.Contains tzId then
DateTimeZoneProviders.Tzdb[tzId]
else
DateTimeZone.Utc
/// Get the local date/time for this group
member this.LocalTimeNow(clock: IClock) =
if isNull clock then
nullArg (nameof clock)
clock.GetCurrentInstant().InZone(this.TimeZone).LocalDateTime
/// Get the local date for this group
member this.LocalDateNow clock = this.LocalTimeNow(clock).Date
/// An empty small group
static member Empty =
{ Id = SmallGroupId Guid.Empty
ChurchId = ChurchId Guid.Empty
Name = ""
Preferences = ListPreferences.Empty }
/// This represents a single prayer request
[<NoComparison; NoEquality>]
type PrayerRequest =
@ -430,61 +462,31 @@ type PrayerRequest =
/// Is this request expired?
Expiration: Expiration
}
// functions are below small group functions
/// Is this request expired?
member this.IsExpired (asOf: LocalDate) (group: SmallGroup) =
match this.Expiration, this.RequestType with
| Forced, _ -> true
| Manual, _
| Automatic, LongTermRequest
| Automatic, Expecting -> false
| Automatic, _ ->
// Automatic expiration
Period
.Between(this.UpdatedDate.InZone(group.TimeZone).Date, asOf, PeriodUnits.Days)
.Days
>= group.Preferences.DaysToExpire
/// This represents a small group (Sunday School class, Bible study group, etc.)
[<NoComparison; NoEquality>]
type SmallGroup =
{
/// The ID of this small group
Id: SmallGroupId
/// The church to which this group belongs
ChurchId: ChurchId
/// The name of the group
Name: string
/// The preferences for the request list
Preferences: ListPreferences
}
/// Functions to support small groups
module SmallGroup =
/// An empty small group
let empty =
{ Id = SmallGroupId Guid.Empty
ChurchId = ChurchId Guid.Empty
Name = ""
Preferences = ListPreferences.empty }
/// 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]
/// Is an update required for this long-term request?
member this.UpdateRequired asOf group =
if this.IsExpired asOf group then
false
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
/// Functions to support prayer requests
module PrayerRequest =
asOf.PlusWeeks -group.Preferences.LongTermUpdateWeeks
>= this.UpdatedDate.InZone(group.TimeZone).Date
/// An empty request
let empty =
static member Empty =
{ Id = PrayerRequestId Guid.Empty
RequestType = CurrentRequest
UserId = UserId Guid.Empty
@ -496,28 +498,6 @@ module PrayerRequest =
NotifyChaplain = false
Expiration = Automatic }
/// 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
Period
.Between(req.UpdatedDate.InZone(SmallGroup.timeZone group).Date, asOf, PeriodUnits.Days)
.Days
>= group.Preferences.DaysToExpire
/// 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
/// This represents a user of PrayerTracker
[<NoComparison; NoEquality>]
@ -548,11 +528,8 @@ type User =
/// The full name of the user
member this.Name = $"{this.FirstName} {this.LastName}"
/// Functions to support users
module User =
/// An empty user
let empty =
static member Empty =
{ Id = UserId Guid.Empty
FirstName = ""
LastName = ""
@ -573,10 +550,7 @@ type UserSmallGroup =
SmallGroupId: SmallGroupId
}
/// Functions to support user/small group cross-reference
module UserSmallGroup =
/// An empty user/small group xref
let empty =
static member Empty =
{ UserId = UserId Guid.Empty
SmallGroupId = SmallGroupId Guid.Empty }

View File

@ -39,8 +39,8 @@ let asOfDateDisplayTests =
[<Tests>]
let churchTests =
testList "Church" [
test "empty is as expected" {
let mt = Church.empty
test "Empty is as expected" {
let mt = Church.Empty
Expect.equal mt.Id.Value Guid.Empty "The church ID should have been an empty GUID"
Expect.equal mt.Name "" "The name should have been blank"
Expect.equal mt.City "" "The city should have been blank"
@ -111,16 +111,16 @@ let expirationTests =
let listPreferencesTests =
testList "ListPreferences" [
test "FontStack is correct for native fonts" {
Expect.equal ListPreferences.empty.FontStack
Expect.equal ListPreferences.Empty.FontStack
"""system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu,"Liberation Sans",Cantarell,"Helvetica Neue",sans-serif"""
"The expected native font stack was incorrect"
}
test "FontStack is correct for specific fonts" {
Expect.equal { ListPreferences.empty with Fonts = "Arial,sans-serif" }.FontStack "Arial,sans-serif"
Expect.equal { ListPreferences.Empty with Fonts = "Arial,sans-serif" }.FontStack "Arial,sans-serif"
"The specified fonts were not returned correctly"
}
test "empty is as expected" {
let mt = ListPreferences.empty
test "Empty is as expected" {
let mt = ListPreferences.Empty
Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID"
Expect.equal mt.DaysToExpire 14 "The default days to expire should have been 14"
Expect.equal mt.DaysToKeepNew 7 "The default days to keep new should have been 7"
@ -137,8 +137,7 @@ let listPreferencesTests =
Expect.equal mt.GroupPassword "" "The default group password should have been blank"
Expect.equal mt.DefaultEmailType HtmlFormat "The default e-mail type should have been HTML"
Expect.isFalse mt.IsPublic "The isPublic flag should not have been set"
Expect.equal (TimeZoneId.toString mt.TimeZoneId) "America/Denver"
"The default time zone should have been America/Denver"
Expect.equal (string mt.TimeZoneId) "America/Denver" "The default time zone should have been America/Denver"
Expect.equal mt.PageSize 100 "The default page size should have been 100"
Expect.equal mt.AsOfDateDisplay NoDisplay "The as-of date display should have been No Display"
}
@ -147,8 +146,8 @@ let listPreferencesTests =
[<Tests>]
let memberTests =
testList "Member" [
test "empty is as expected" {
let mt = Member.empty
test "Empty is as expected" {
let mt = Member.Empty
Expect.equal mt.Id.Value Guid.Empty "The member ID should have been an empty GUID"
Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID"
Expect.equal mt.Name "" "The member name should have been blank"
@ -162,8 +161,8 @@ let prayerRequestTests =
let instantNow = SystemClock.Instance.GetCurrentInstant
let localDateNow () = (instantNow ()).InUtc().Date
testList "PrayerRequest" [
test "empty is as expected" {
let mt = PrayerRequest.empty
test "Empty is as expected" {
let mt = PrayerRequest.Empty
Expect.equal mt.Id.Value Guid.Empty "The request ID should have been an empty GUID"
Expect.equal mt.RequestType CurrentRequest "The request type should have been Current"
Expect.equal mt.UserId.Value Guid.Empty "The user ID should have been an empty GUID"
@ -175,59 +174,60 @@ let prayerRequestTests =
Expect.isFalse mt.NotifyChaplain "The notify chaplain flag should not have been set"
Expect.equal mt.Expiration Automatic "The expiration should have been Automatic"
}
test "isExpired always returns false for expecting requests" {
PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with RequestType = Expecting }
test "IsExpired always returns false for expecting requests" {
{ PrayerRequest.Empty with RequestType = Expecting }.IsExpired (localDateNow ()) SmallGroup.Empty
|> Flip.Expect.isFalse "An expecting request should never be considered expired"
}
test "isExpired always returns false for manually-expired requests" {
PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with UpdatedDate = (instantNow ()) - Duration.FromDays 1; Expiration = Manual }
test "IsExpired always returns false for manually-expired requests" {
{ PrayerRequest.Empty with
UpdatedDate = (instantNow ()) - Duration.FromDays 1
Expiration = Manual }.IsExpired (localDateNow ()) SmallGroup.Empty
|> Flip.Expect.isFalse "A never-expired request should never be considered expired"
}
test "isExpired always returns false for long term/recurring requests" {
PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with RequestType = LongTermRequest }
test "IsExpired always returns false for long term/recurring requests" {
{ PrayerRequest.Empty with RequestType = LongTermRequest }.IsExpired (localDateNow ()) SmallGroup.Empty
|> Flip.Expect.isFalse "A recurring/long-term request should never be considered expired"
}
test "isExpired always returns true for force-expired requests" {
PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with UpdatedDate = (instantNow ()); Expiration = Forced }
test "IsExpired always returns true for force-expired requests" {
{ PrayerRequest.Empty with UpdatedDate = (instantNow ()); Expiration = Forced }.IsExpired
(localDateNow ()) SmallGroup.Empty
|> Flip.Expect.isTrue "A force-expired request should always be considered expired"
}
test "isExpired returns false for non-expired requests" {
test "IsExpired returns false for non-expired requests" {
let now = instantNow ()
PrayerRequest.isExpired (now.InUtc().Date) SmallGroup.empty
{ PrayerRequest.empty with UpdatedDate = now - Duration.FromDays 5 }
{ PrayerRequest.Empty with UpdatedDate = now - Duration.FromDays 5 }.IsExpired
(now.InUtc().Date) SmallGroup.Empty
|> Flip.Expect.isFalse "A request updated 5 days ago should not be considered expired"
}
test "isExpired returns true for expired requests" {
test "IsExpired returns true for expired requests" {
let now = instantNow ()
PrayerRequest.isExpired (now.InUtc().Date) SmallGroup.empty
{ PrayerRequest.empty with UpdatedDate = now - Duration.FromDays 15 }
{ PrayerRequest.Empty with UpdatedDate = now - Duration.FromDays 15 }.IsExpired
(now.InUtc().Date) SmallGroup.Empty
|> Flip.Expect.isTrue "A request updated 15 days ago should be considered expired"
}
test "isExpired returns true for same-day expired requests" {
test "IsExpired returns true for same-day expired requests" {
let now = instantNow ()
PrayerRequest.isExpired (now.InUtc().Date) SmallGroup.empty
{ PrayerRequest.empty with UpdatedDate = now - (Duration.FromDays 14) - (Duration.FromSeconds 1L) }
{ PrayerRequest.Empty with
UpdatedDate = now - (Duration.FromDays 14) - (Duration.FromSeconds 1L) }.IsExpired
(now.InUtc().Date) SmallGroup.Empty
|> Flip.Expect.isTrue "A request entered a second before midnight should be considered expired"
}
test "updateRequired returns false for expired requests" {
PrayerRequest.updateRequired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with Expiration = Forced }
test "UpdateRequired returns false for expired requests" {
{ PrayerRequest.Empty with Expiration = Forced }.UpdateRequired (localDateNow ()) SmallGroup.Empty
|> Flip.Expect.isFalse "An expired request should not require an update"
}
test "updateRequired returns false when an update is not required for an active request" {
test "UpdateRequired returns false when an update is not required for an active request" {
let now = instantNow ()
PrayerRequest.updateRequired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with RequestType = LongTermRequest; UpdatedDate = now - Duration.FromDays 14 }
{ PrayerRequest.Empty with
RequestType = LongTermRequest
UpdatedDate = now - Duration.FromDays 14 }.UpdateRequired (localDateNow ()) SmallGroup.Empty
|> Flip.Expect.isFalse "An active request updated 14 days ago should not require an update until 28 days"
}
test "UpdateRequired returns true when an update is required for an active request" {
let now = instantNow ()
PrayerRequest.updateRequired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with RequestType = LongTermRequest; UpdatedDate = now - Duration.FromDays 34 }
{ PrayerRequest.Empty with
RequestType = LongTermRequest
UpdatedDate = now - Duration.FromDays 34 }.UpdateRequired (localDateNow ()) SmallGroup.Empty
|> Flip.Expect.isTrue "An active request updated 34 days ago should require an update (past 28 days)"
}
]
@ -311,8 +311,8 @@ let smallGroupTests =
let now = Instant.FromDateTimeUtc (DateTime (2017, 5, 12, 12, 15, 0, DateTimeKind.Utc))
let withFakeClock f () =
FakeClock now |> f
yield test "empty is as expected" {
let mt = SmallGroup.empty
yield test "Empty is as expected" {
let mt = SmallGroup.Empty
Expect.equal mt.Id.Value Guid.Empty "The small group ID should have been an empty GUID"
Expect.equal mt.ChurchId.Value Guid.Empty "The church ID should have been an empty GUID"
Expect.equal mt.Name "" "The name should have been blank"
@ -321,31 +321,31 @@ let smallGroupTests =
"LocalTimeNow adjusts the time ahead of UTC",
fun clock ->
let grp =
{ SmallGroup.empty with
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "Europe/Berlin" }
{ SmallGroup.Empty with
Preferences = { ListPreferences.Empty with TimeZoneId = TimeZoneId "Europe/Berlin" }
}
Expect.isGreaterThan (SmallGroup.localTimeNow clock grp) (now.InUtc().LocalDateTime)
Expect.isGreaterThan (grp.LocalTimeNow clock) (now.InUtc().LocalDateTime)
"UTC to Europe/Berlin should have added hours"
"LocalTimeNow adjusts the time behind UTC",
fun clock ->
Expect.isLessThan (SmallGroup.localTimeNow clock SmallGroup.empty) (now.InUtc().LocalDateTime)
Expect.isLessThan (SmallGroup.Empty.LocalTimeNow clock) (now.InUtc().LocalDateTime)
"UTC to America/Denver should have subtracted hours"
"LocalTimeNow returns UTC when the time zone is invalid",
fun clock ->
let grp =
{ SmallGroup.empty with
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "garbage" }
{ SmallGroup.Empty with
Preferences = { ListPreferences.Empty with TimeZoneId = TimeZoneId "garbage" }
}
Expect.equal (SmallGroup.localTimeNow clock grp) (now.InUtc().LocalDateTime)
Expect.equal (grp.LocalTimeNow clock) (now.InUtc().LocalDateTime)
"UTC should have been returned for an invalid time zone"
]
yield test "localTimeNow fails when clock is not passed" {
Expect.throws (fun () -> (SmallGroup.localTimeNow null SmallGroup.empty |> ignore))
Expect.throws (fun () -> SmallGroup.Empty.LocalTimeNow null |> ignore)
"Should have raised an exception for null clock"
}
yield test "LocalDateNow returns the date portion" {
let clock = FakeClock (Instant.FromDateTimeUtc (DateTime (2017, 5, 12, 1, 15, 0, DateTimeKind.Utc)))
Expect.isLessThan (SmallGroup.localDateNow clock SmallGroup.empty) (now.InUtc().Date)
Expect.isLessThan (SmallGroup.Empty.LocalDateNow clock) (now.InUtc().Date)
"The date should have been a day earlier"
}
]
@ -353,8 +353,8 @@ let smallGroupTests =
[<Tests>]
let userTests =
testList "User" [
test "empty is as expected" {
let mt = User.empty
test "Empty is as expected" {
let mt = User.Empty
Expect.equal mt.Id.Value Guid.Empty "The user ID should have been an empty GUID"
Expect.equal mt.FirstName "" "The first name should have been blank"
Expect.equal mt.LastName "" "The last name should have been blank"
@ -363,7 +363,7 @@ let userTests =
Expect.equal mt.PasswordHash "" "The password hash should have been blank"
}
test "Name concatenates first and last names" {
let user = { User.empty with FirstName = "Unit"; LastName = "Test" }
let user = { User.Empty with FirstName = "Unit"; LastName = "Test" }
Expect.equal user.Name "Unit Test" "The full name should be the first and last, separated by a space"
}
]
@ -371,8 +371,8 @@ let userTests =
[<Tests>]
let userSmallGroupTests =
testList "UserSmallGroup" [
test "empty is as expected" {
let mt = UserSmallGroup.empty
test "Empty is as expected" {
let mt = UserSmallGroup.Empty
Expect.equal mt.UserId.Value Guid.Empty "The user ID should have been an empty GUID"
Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID"
}

View File

@ -129,7 +129,7 @@ let appViewInfoTests =
let assignGroupsTests =
testList "AssignGroups" [
test "fromUser populates correctly" {
let usr = { User.empty with Id = (Guid.NewGuid >> UserId) (); FirstName = "Alice"; LastName = "Bob" }
let usr = { User.Empty with Id = (Guid.NewGuid >> UserId) (); FirstName = "Alice"; LastName = "Bob" }
let asg = AssignGroups.fromUser usr
Expect.equal asg.UserId (shortGuid usr.Id.Value) "The user ID was not filled correctly"
Expect.equal asg.UserName usr.Name "The user's name was not filled correctly"
@ -142,7 +142,7 @@ let editChurchTests =
testList "EditChurch" [
test "fromChurch populates correctly when interface exists" {
let church =
{ Church.empty with
{ Church.Empty with
Id = (Guid.NewGuid >> ChurchId) ()
Name = "Unit Test"
City = "Testlandia"
@ -163,7 +163,7 @@ let editChurchTests =
test "fromChurch populates correctly when interface does not exist" {
let edit =
EditChurch.fromChurch
{ Church.empty with
{ Church.Empty with
Id = (Guid.NewGuid >> ChurchId) ()
Name = "Unit Test"
City = "Testlandia"
@ -198,7 +198,7 @@ let editChurchTests =
HasInterface = Some true
InterfaceAddress = Some "https://test.units"
}
let church = edit.PopulateChurch Church.empty
let church = edit.PopulateChurch Church.Empty
Expect.notEqual (shortGuid church.Id.Value) edit.ChurchId "The church ID should not have been modified"
Expect.equal church.Name edit.Name "The church name was not updated correctly"
Expect.equal church.City edit.City "The church's city was not updated correctly"
@ -213,7 +213,7 @@ let editChurchTests =
Name = "Test Baptist Church"
City = "Testerville"
State = "TE"
}.PopulateChurch Church.empty
}.PopulateChurch Church.Empty
Expect.isFalse church.HasVpsInterface "The church should show that it has an interface"
Expect.isNone church.InterfaceAddress "The interface address should exist"
}
@ -224,7 +224,7 @@ let editMemberTests =
testList "EditMember" [
test "fromMember populates with group default format" {
let mbr =
{ Member.empty with
{ Member.Empty with
Id = (Guid.NewGuid >> MemberId) ()
Name = "Test Name"
Email = "test_units@example.com"
@ -236,7 +236,7 @@ let editMemberTests =
Expect.equal edit.Format "" "The e-mail format should have been blank for group default"
}
test "fromMember populates with specific format" {
let edit = EditMember.fromMember { Member.empty with Format = Some HtmlFormat }
let edit = EditMember.fromMember { Member.Empty with Format = Some HtmlFormat }
Expect.equal edit.Format (string HtmlFormat) "The e-mail format was not filled correctly"
}
test "empty is as expected" {
@ -259,7 +259,7 @@ let editMemberTests =
let editPreferencesTests =
testList "EditPreferences" [
test "fromPreferences succeeds for native fonts, named colors, and private list" {
let prefs = ListPreferences.empty
let prefs = ListPreferences.Empty
let edit = EditPreferences.fromPreferences prefs
Expect.equal edit.ExpireDays prefs.DaysToExpire "The expiration days were not filled correctly"
Expect.equal edit.DaysToKeepNew prefs.DaysToKeepNew "The days to keep new were not filled correctly"
@ -278,7 +278,7 @@ let editPreferencesTests =
Expect.isNone edit.Fonts "The list fonts should not exist for native font stack"
Expect.equal edit.HeadingFontSize prefs.HeadingFontSize "The heading font size was not filled correctly"
Expect.equal edit.ListFontSize prefs.TextFontSize "The list text font size was not filled correctly"
Expect.equal edit.TimeZone (TimeZoneId.toString prefs.TimeZoneId) "The time zone was not filled correctly"
Expect.equal edit.TimeZone (string prefs.TimeZoneId) "The time zone was not filled correctly"
Expect.isSome edit.GroupPassword "The group password should have been set"
Expect.equal edit.GroupPassword (Some prefs.GroupPassword) "The group password was not filled correctly"
Expect.equal edit.Visibility GroupVisibility.PrivateList
@ -287,7 +287,7 @@ let editPreferencesTests =
Expect.equal edit.AsOfDate (string prefs.AsOfDateDisplay) "The as-of date display was not filled correctly"
}
test "fromPreferences succeeds for RGB line color and password-protected list" {
let prefs = { ListPreferences.empty with LineColor = "#ff0000"; GroupPassword = "pw" }
let prefs = { ListPreferences.Empty with LineColor = "#ff0000"; GroupPassword = "pw" }
let edit = EditPreferences.fromPreferences prefs
Expect.equal edit.LineColorType "RGB" "The heading line color type was not derived correctly"
Expect.equal edit.LineColor prefs.LineColor "The heading line color was not filled correctly"
@ -297,7 +297,7 @@ let editPreferencesTests =
"The list visibility was not derived correctly"
}
test "fromPreferences succeeds for RGB text color and public list" {
let prefs = { ListPreferences.empty with HeadingColor = "#0000ff"; IsPublic = true }
let prefs = { ListPreferences.Empty with HeadingColor = "#0000ff"; IsPublic = true }
let edit = EditPreferences.fromPreferences prefs
Expect.equal edit.HeadingColorType "RGB" "The heading text color type was not derived correctly"
Expect.equal edit.HeadingColor prefs.HeadingColor "The heading text color was not filled correctly"
@ -307,7 +307,7 @@ let editPreferencesTests =
"The list visibility was not derived correctly"
}
test "fromPreferences succeeds for non-native fonts" {
let prefs = { ListPreferences.empty with Fonts = "Arial,sans-serif" }
let prefs = { ListPreferences.Empty with Fonts = "Arial,sans-serif" }
let edit = EditPreferences.fromPreferences prefs
Expect.isFalse edit.IsNative "The IsNative flag should have been false"
Expect.isSome edit.Fonts "The fonts should have been filled for non-native fonts"
@ -330,7 +330,7 @@ let editRequestTests =
}
test "fromRequest succeeds" {
let req =
{ PrayerRequest.empty with
{ PrayerRequest.Empty with
Id = (Guid.NewGuid >> PrayerRequestId) ()
RequestType = CurrentRequest
Requestor = Some "Me"
@ -358,7 +358,7 @@ let editSmallGroupTests =
testList "EditSmallGroup" [
test "fromGroup succeeds" {
let grp =
{ SmallGroup.empty with
{ SmallGroup.Empty with
Id = (Guid.NewGuid >> SmallGroupId) ()
Name = "test group"
ChurchId = (Guid.NewGuid >> ChurchId) ()
@ -387,7 +387,7 @@ let editSmallGroupTests =
Name = "test name"
ChurchId = (Guid.NewGuid >> shortGuid) ()
}
let grp = edit.populateGroup SmallGroup.empty
let grp = edit.populateGroup SmallGroup.Empty
Expect.equal grp.Name edit.Name "The name was not populated correctly"
Expect.equal grp.ChurchId (idFromShort ChurchId edit.ChurchId) "The church ID was not populated correctly"
}
@ -408,7 +408,7 @@ let editUserTests =
}
test "fromUser succeeds" {
let usr =
{ User.empty with
{ User.Empty with
Id = (Guid.NewGuid >> UserId) ()
FirstName = "user"
LastName = "test"
@ -438,7 +438,7 @@ let editUserTests =
Password = "testpw"
}
let hasher = fun x -> x + "+"
let usr = edit.PopulateUser User.empty hasher
let usr = edit.PopulateUser User.Empty hasher
Expect.equal usr.FirstName edit.FirstName "The first name was not populated correctly"
Expect.equal usr.LastName edit.LastName "The last name was not populated correctly"
Expect.equal usr.Email edit.Email "The e-mail address was not populated correctly"
@ -500,26 +500,26 @@ let requestListTests =
let withRequestList f () =
let today = SystemClock.Instance.GetCurrentInstant ()
{ Requests = [
{ PrayerRequest.empty with
{ PrayerRequest.Empty with
RequestType = CurrentRequest
Requestor = Some "Zeb"
Text = "zyx"
UpdatedDate = today
}
{ PrayerRequest.empty with
{ PrayerRequest.Empty with
RequestType = CurrentRequest
Requestor = Some "Aaron"
Text = "abc"
UpdatedDate = today - Duration.FromDays 9
}
{ PrayerRequest.empty with
{ PrayerRequest.Empty with
RequestType = PraiseReport
Text = "nmo"
UpdatedDate = today
}
]
Date = today.InUtc().Date
SmallGroup = SmallGroup.empty
SmallGroup = SmallGroup.Empty
ShowHeader = false
Recipients = []
CanEmail = false
@ -596,7 +596,7 @@ let requestListTests =
}
let html = htmlList.AsHtml _s
let expected =
htmlList.Requests[0].UpdatedDate.InZone(SmallGroup.timeZone reqList.SmallGroup).Date.ToString ("d", null)
htmlList.Requests[0].UpdatedDate.InZone(reqList.SmallGroup.TimeZone).Date.ToString ("d", null)
|> sprintf """<strong>Zeb</strong> &ndash; zyx<i style="font-size:9.60pt">&nbsp; (as of %s)</i>"""
// spot check; if one request has it, they all should
Expect.stringContains html expected "Expected short as-of date not found"
@ -611,7 +611,7 @@ let requestListTests =
}
let html = htmlList.AsHtml _s
let expected =
htmlList.Requests[0].UpdatedDate.InZone(SmallGroup.timeZone reqList.SmallGroup).Date.ToString ("D", null)
htmlList.Requests[0].UpdatedDate.InZone(reqList.SmallGroup.TimeZone).Date.ToString ("D", null)
|> sprintf """<strong>Zeb</strong> &ndash; zyx<i style="font-size:9.60pt">&nbsp; (as of %s)</i>"""
// spot check; if one request has it, they all should
Expect.stringContains html expected "Expected long as-of date not found"
@ -642,7 +642,7 @@ let requestListTests =
}
let text = textList.AsText _s
let expected =
textList.Requests[0].UpdatedDate.InZone(SmallGroup.timeZone reqList.SmallGroup).Date.ToString ("d", null)
textList.Requests[0].UpdatedDate.InZone(reqList.SmallGroup.TimeZone).Date.ToString ("d", null)
|> sprintf " + Zeb - zyx (as of %s)"
// spot check; if one request has it, they all should
Expect.stringContains text expected "Expected short as-of date not found"
@ -657,7 +657,7 @@ let requestListTests =
}
let text = textList.AsText _s
let expected =
textList.Requests[0].UpdatedDate.InZone(SmallGroup.timeZone reqList.SmallGroup).Date.ToString ("D", null)
textList.Requests[0].UpdatedDate.InZone(reqList.SmallGroup.TimeZone).Date.ToString ("D", null)
|> sprintf " + Zeb - zyx (as of %s)"
// spot check; if one request has it, they all should
Expect.stringContains text expected "Expected long as-of date not found"

View File

@ -215,7 +215,7 @@ module TimeZones =
match xref |> List.tryFind (fun it -> fst it = timeZoneId) with
| Some tz -> s[snd tz]
| None ->
let tzId = TimeZoneId.toString timeZoneId
let tzId = string timeZoneId
LocalizedString (tzId, tzId)
/// All known time zones in their defined order

View File

@ -156,7 +156,7 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
use sw = new StringWriter ()
let raw = rawLocText sw
let group = model.SmallGroup
let now = SmallGroup.localDateNow (ctx.GetService<IClock> ()) group
let now = group.LocalDateNow (ctx.GetService<IClock>())
let types = ReferenceList.requestTypeList s |> Map.ofList
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
@ -164,8 +164,8 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
model.Requests
|> List.map (fun req ->
let updateClass =
_class (if PrayerRequest.updateRequired now group req then "cell pt-request-update" else "cell")
let isExpired = PrayerRequest.isExpired now group req
_class (if req.UpdateRequired now group then "cell pt-request-update" else "cell")
let isExpired = req.IsExpired now group
let expiredClass = _class (if isExpired then "cell pt-request-expired" else "cell")
let reqId = shortGuid req.Id.Value
let reqText = htmlToPlainText req.Text

View File

@ -589,7 +589,7 @@ let preferences (model : EditPreferences) ctx viewInfo =
"", selectDefault s["Select"].Value
yield!
TimeZones.all
|> List.map (fun tz -> TimeZoneId.toString tz, (TimeZones.name tz s).Value)
|> List.map (fun tz -> string tz, (TimeZones.name tz s).Value)
}
|> selectList (nameof model.TimeZone) model.TimeZone [ _required ]
]

View File

@ -448,7 +448,7 @@ module EditPreferences =
Fonts = if prefs.Fonts = "native" then None else Some prefs.Fonts
HeadingFontSize = prefs.HeadingFontSize
ListFontSize = prefs.TextFontSize
TimeZone = TimeZoneId.toString prefs.TimeZoneId
TimeZone = string prefs.TimeZoneId
GroupPassword = Some prefs.GroupPassword
PageSize = prefs.PageSize
AsOfDate = string prefs.AsOfDateDisplay
@ -670,7 +670,7 @@ module MaintainRequests =
/// An empty instance
let empty =
{ Requests = []
SmallGroup = SmallGroup.empty
SmallGroup = SmallGroup.Empty
OnlyActive = None
SearchTerm = None
PageNbr = None
@ -773,7 +773,7 @@ with
/// Is this request new?
member this.IsNew (req: PrayerRequest) =
let reqDate = req.UpdatedDate.InZone(SmallGroup.timeZone this.SmallGroup).Date
let reqDate = req.UpdatedDate.InZone(this.SmallGroup.TimeZone).Date
Period.Between(reqDate, this.Date, PeriodUnits.Days).Days <= this.SmallGroup.Preferences.DaysToKeepNew
/// Generate this list as HTML
@ -803,7 +803,7 @@ with
]
]
]
let tz = SmallGroup.timeZone this.SmallGroup
let tz = this.SmallGroup.TimeZone
reqs
|> List.map (fun req ->
let bullet = if this.IsNew req then "circle" else "disc"
@ -835,7 +835,7 @@ with
/// Generate this list as plain text
member this.AsText (s: IStringLocalizer) =
let tz = SmallGroup.timeZone this.SmallGroup
let tz = this.SmallGroup.TimeZone
seq {
this.SmallGroup.Name
s["Prayer Requests"].Value

View File

@ -63,7 +63,7 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
match! ctx.TryBindFormAsync<EditChurch> () with
| Ok model ->
let! church =
if model.IsNew then Task.FromResult(Some { Church.empty with Id = (Guid.NewGuid >> ChurchId) () })
if model.IsNew then Task.FromResult(Some { Church.Empty with Id = (Guid.NewGuid >> ChurchId) () })
else Churches.tryById (idFromShort ChurchId model.ChurchId)
match church with
| Some ch ->

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 -> SmallGroup.localDateNow ctx.Clock group
let listDate = defaultArg date (group.LocalDateNow ctx.Clock)
let! reqs =
PrayerRequests.forGroup
{ SmallGroup = group
@ -50,7 +50,7 @@ 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 = SmallGroup.localDateNow ctx.Clock group
let now = group.LocalDateNow ctx.Clock
let requestId = PrayerRequestId reqId
if requestId.Value = Guid.Empty then
return!
@ -61,7 +61,7 @@ let edit reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task {
match! findRequest ctx requestId with
| Ok req ->
let s = ctx.Strings
if PrayerRequest.isExpired now group req then
if req.IsExpired now group then
{ UserMessage.warning with
Text = htmlLocString s["This request is expired."]
Description =
@ -139,7 +139,7 @@ let list groupId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun ne
viewInfo ctx
|> Views.PrayerRequest.list
{ Requests = reqs
Date = SmallGroup.localDateNow ctx.Clock group
Date = group.LocalDateNow ctx.Clock
SmallGroup = group
ShowHeader = true
CanEmail = Option.isSome ctx.User.UserId
@ -226,7 +226,7 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
let group = ctx.Session.CurrentGroup.Value
let! req =
if model.IsNew then
{ PrayerRequest.empty with
{ PrayerRequest.Empty with
Id = (Guid.NewGuid >> PrayerRequestId) ()
SmallGroupId = group.Id
UserId = ctx.User.UserId.Value
@ -235,7 +235,7 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
else PrayerRequests.tryById (idFromShort PrayerRequestId model.RequestId)
match req with
| Some pr when pr.SmallGroupId = group.Id ->
let now = SmallGroup.localDateNow ctx.Clock group
let now = group.LocalDateNow ctx.Clock
let updated =
{ pr with
RequestType = PrayerRequestType.Parse model.RequestType
@ -247,7 +247,7 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
| it when model.IsNew ->
let dt =
(defaultArg (parseListDate model.EnteredDate) now)
.AtStartOfDayInZone(SmallGroup.timeZone group)
.AtStartOfDayInZone(group.TimeZone)
.ToInstant()
{ it with EnteredDate = dt; UpdatedDate = dt }
| it when defaultArg model.SkipDateUpdate false -> it

View File

@ -183,7 +183,7 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
match! ctx.TryBindFormAsync<EditSmallGroup>() with
| Ok model ->
let! tryGroup =
if model.IsNew then Task.FromResult(Some { SmallGroup.empty with Id = (Guid.NewGuid >> SmallGroupId) () })
if model.IsNew then Task.FromResult(Some { SmallGroup.Empty with Id = (Guid.NewGuid >> SmallGroupId) () })
else SmallGroups.tryById (idFromShort SmallGroupId model.SmallGroupId)
match tryGroup with
| Some group ->
@ -202,7 +202,7 @@ let saveMember : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun n
let group = ctx.Session.CurrentGroup.Value
let! tryMbr =
if model.IsNew then
Task.FromResult(Some { Member.empty with Id = (Guid.NewGuid >> MemberId) (); SmallGroupId = group.Id })
Task.FromResult(Some { Member.Empty with Id = (Guid.NewGuid >> MemberId) (); SmallGroupId = group.Id })
else Members.tryById (idFromShort MemberId model.MemberId)
match tryMbr with
| Some mbr when mbr.SmallGroupId = group.Id ->
@ -250,7 +250,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
let group = ctx.Session.CurrentGroup.Value
let pref = group.Preferences
let usr = ctx.Session.CurrentUser.Value
let now = SmallGroup.localTimeNow ctx.Clock group
let now = group.LocalTimeNow ctx.Clock
let s = ctx.Strings
// Reformat the text to use the class's font stylings
let requestText = ckEditorToText model.Text
@ -262,7 +262,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
let! recipients = task {
if model.SendToClass = "N" && usr.IsAdmin then
let! users = Users.all ()
return users |> List.map (fun u -> { Member.empty with Name = u.Name; Email = u.Email })
return users |> List.map (fun u -> { Member.Empty with Name = u.Name; Email = u.Email })
else return! Members.forGroup group.Id
}
use! client = Email.getConnection ()
@ -282,9 +282,9 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
| _, None -> ()
| _, Some x when not x -> ()
| _, _ ->
let zone = SmallGroup.timeZone group
let zone = group.TimeZone
do! PrayerRequests.save
{ PrayerRequest.empty with
{ PrayerRequest.Empty with
Id = (Guid.NewGuid >> PrayerRequestId) ()
SmallGroupId = group.Id
UserId = usr.Id

View File

@ -218,7 +218,7 @@ let save : HttpHandler = requireAccess [ Admin ] >=> validateCsrf >=> fun next c
match! ctx.TryBindFormAsync<EditUser>() with
| Ok model ->
let! user =
if model.IsNew then Task.FromResult(Some { User.empty with Id = (Guid.NewGuid >> UserId) () })
if model.IsNew then Task.FromResult(Some { User.Empty with Id = (Guid.NewGuid >> UserId) () })
else Users.tryById (idFromShort UserId model.UserId)
match user with
| Some usr ->